Files
dss/tools/storybook/theme.py
Digital Production Factory 276ed71f31 Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
2025-12-09 18:45:48 -03:00

375 lines
11 KiB
Python

"""
Storybook Theme Generator
Generates Storybook theme configurations from design tokens.
"""
import json
from pathlib import Path
from typing import List, Dict, Any, Optional
from dataclasses import dataclass, field
@dataclass
class StorybookTheme:
"""Storybook theme configuration."""
name: str = "dss-theme"
base: str = "light" # 'light' or 'dark'
# Brand
brand_title: str = "Design System"
brand_url: str = ""
brand_image: str = ""
brand_target: str = "_self"
# Colors
color_primary: str = "#3B82F6"
color_secondary: str = "#10B981"
# UI Colors
app_bg: str = "#FFFFFF"
app_content_bg: str = "#FFFFFF"
app_border_color: str = "#E5E7EB"
# Text colors
text_color: str = "#1F2937"
text_inverse_color: str = "#FFFFFF"
text_muted_color: str = "#6B7280"
# Toolbar
bar_text_color: str = "#6B7280"
bar_selected_color: str = "#3B82F6"
bar_bg: str = "#FFFFFF"
# Form colors
input_bg: str = "#FFFFFF"
input_border: str = "#D1D5DB"
input_text_color: str = "#1F2937"
input_border_radius: int = 4
# Typography
font_base: str = '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
font_code: str = '"Fira Code", "Monaco", monospace'
def to_dict(self) -> Dict[str, Any]:
return {
"base": self.base,
"brandTitle": self.brand_title,
"brandUrl": self.brand_url,
"brandImage": self.brand_image,
"brandTarget": self.brand_target,
"colorPrimary": self.color_primary,
"colorSecondary": self.color_secondary,
"appBg": self.app_bg,
"appContentBg": self.app_content_bg,
"appBorderColor": self.app_border_color,
"textColor": self.text_color,
"textInverseColor": self.text_inverse_color,
"textMutedColor": self.text_muted_color,
"barTextColor": self.bar_text_color,
"barSelectedColor": self.bar_selected_color,
"barBg": self.bar_bg,
"inputBg": self.input_bg,
"inputBorder": self.input_border,
"inputTextColor": self.input_text_color,
"inputBorderRadius": self.input_border_radius,
"fontBase": self.font_base,
"fontCode": self.font_code,
}
class ThemeGenerator:
"""
Generates Storybook theme configurations from design tokens.
"""
# Token name mappings to Storybook theme properties
TOKEN_MAPPINGS = {
# Primary/Secondary
"color.primary.500": "color_primary",
"color.primary.600": "color_primary",
"color.secondary.500": "color_secondary",
"color.accent.500": "color_secondary",
# Backgrounds
"color.neutral.50": "app_bg",
"color.background": "app_bg",
"color.surface": "app_content_bg",
# Borders
"color.neutral.200": "app_border_color",
"color.border": "app_border_color",
# Text
"color.neutral.900": "text_color",
"color.neutral.800": "text_color",
"color.foreground": "text_color",
"color.neutral.500": "text_muted_color",
"color.muted": "text_muted_color",
# Input
"color.neutral.300": "input_border",
"radius.md": "input_border_radius",
}
def __init__(self):
pass
def generate_from_tokens(
self,
tokens: List[Dict[str, Any]],
brand_title: str = "Design System",
base: str = "light",
) -> StorybookTheme:
"""
Generate Storybook theme from design tokens.
Args:
tokens: List of token dicts with 'name' and 'value'
brand_title: Brand title for Storybook
base: Base theme ('light' or 'dark')
Returns:
StorybookTheme configured from tokens
"""
theme = StorybookTheme(
name="dss-theme",
base=base,
brand_title=brand_title,
)
# Map tokens to theme properties
for token in tokens:
name = token.get("name", "")
value = token.get("value", "")
# Check direct mappings
if name in self.TOKEN_MAPPINGS:
prop = self.TOKEN_MAPPINGS[name]
setattr(theme, prop, value)
continue
# Check partial matches
name_lower = name.lower()
if "primary" in name_lower and "500" in name_lower:
theme.color_primary = value
elif "secondary" in name_lower and "500" in name_lower:
theme.color_secondary = value
elif "background" in name_lower and self._is_light_color(value):
theme.app_bg = value
elif "foreground" in name_lower or ("text" in name_lower and "color" in name_lower):
theme.text_color = value
# Adjust for dark mode
if base == "dark":
theme = self._adjust_for_dark_mode(theme)
return theme
def _is_light_color(self, value: str) -> bool:
"""Check if a color value is light (for background suitability)."""
if not value.startswith("#"):
return True # Assume light if not hex
# Parse hex color
hex_color = value.lstrip("#")
if len(hex_color) == 3:
hex_color = "".join(c * 2 for c in hex_color)
try:
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
# Calculate luminance
luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return luminance > 0.5
except (ValueError, IndexError):
return True
def _adjust_for_dark_mode(self, theme: StorybookTheme) -> StorybookTheme:
"""Adjust theme for dark mode if colors aren't already dark."""
# Swap light/dark if needed
if self._is_light_color(theme.app_bg):
theme.app_bg = "#1F2937"
theme.app_content_bg = "#111827"
theme.app_border_color = "#374151"
theme.text_color = "#F9FAFB"
theme.text_muted_color = "#9CA3AF"
theme.bar_bg = "#1F2937"
theme.bar_text_color = "#9CA3AF"
theme.input_bg = "#374151"
theme.input_border = "#4B5563"
theme.input_text_color = "#F9FAFB"
return theme
def generate_theme_file(
self,
theme: StorybookTheme,
format: str = "ts",
) -> str:
"""
Generate Storybook theme file content.
Args:
theme: StorybookTheme to export
format: Output format ('ts', 'js', 'json')
Returns:
Theme file content as string
"""
if format == "json":
return json.dumps(theme.to_dict(), indent=2)
theme_dict = theme.to_dict()
if format == "ts":
lines = [
"import { create } from '@storybook/theming/create';",
"",
"export const dssTheme = create({",
]
else: # js
lines = [
"const { create } = require('@storybook/theming/create');",
"",
"module.exports = create({",
]
for key, value in theme_dict.items():
if isinstance(value, str):
lines.append(f" {key}: '{value}',")
else:
lines.append(f" {key}: {value},")
lines.extend([
"});",
"",
])
return "\n".join(lines)
def generate_manager_file(self, theme_import: str = "./dss-theme") -> str:
"""
Generate Storybook manager.ts file.
Args:
theme_import: Import path for theme
Returns:
Manager file content
"""
return f"""import {{ addons }} from '@storybook/manager-api';
import {{ dssTheme }} from '{theme_import}';
addons.setConfig({{
theme: dssTheme,
}});
"""
def generate_preview_file(
self,
tokens: List[Dict[str, Any]],
include_css_vars: bool = True,
) -> str:
"""
Generate Storybook preview.ts file with token CSS variables.
Args:
tokens: List of token dicts
include_css_vars: Include CSS variable injection
Returns:
Preview file content
"""
lines = [
"import type { Preview } from '@storybook/react';",
"",
]
if include_css_vars:
# Generate CSS variables from tokens
css_vars = []
for token in tokens:
name = token.get("name", "").replace(".", "-")
value = token.get("value", "")
css_vars.append(f" --{name}: {value};")
lines.extend([
"// Inject design tokens as CSS variables",
"const tokenStyles = `",
":root {",
])
lines.extend(css_vars)
lines.extend([
"}",
"`;",
"",
"// Add styles to document",
"const styleSheet = document.createElement('style');",
"styleSheet.textContent = tokenStyles;",
"document.head.appendChild(styleSheet);",
"",
])
lines.extend([
"const preview: Preview = {",
" parameters: {",
" controls: {",
" matchers: {",
" color: /(background|color)$/i,",
" date: /Date$/i,",
" },",
" },",
" backgrounds: {",
" default: 'light',",
" values: [",
" { name: 'light', value: '#FFFFFF' },",
" { name: 'dark', value: '#1F2937' },",
" ],",
" },",
" },",
"};",
"",
"export default preview;",
])
return "\n".join(lines)
def generate_full_config(
self,
tokens: List[Dict[str, Any]],
brand_title: str = "Design System",
output_dir: Optional[str] = None,
) -> Dict[str, str]:
"""
Generate complete Storybook configuration files.
Args:
tokens: List of token dicts
brand_title: Brand title
output_dir: Optional directory to write files
Returns:
Dict mapping filenames to content
"""
# Generate theme
theme = self.generate_from_tokens(tokens, brand_title)
files = {
"dss-theme.ts": self.generate_theme_file(theme, "ts"),
"manager.ts": self.generate_manager_file(),
"preview.ts": self.generate_preview_file(tokens),
}
# Write files if output_dir provided
if output_dir:
out_path = Path(output_dir)
out_path.mkdir(parents=True, exist_ok=True)
for filename, content in files.items():
(out_path / filename).write_text(content)
return files