/** * ThemeSystem * Provides consistent design system and theming across all CMS components */ import React, { createContext, useContext, useState, useCallback, useMemo, } from "react"; import { ThemeConfig, StyleConfig, ComponentType, } from "../types/components.js"; // ======================================== // Types & Interfaces // ======================================== export interface ThemeSystemProps { children: React.ReactNode; defaultTheme?: ThemeConfig; themes?: Record; onThemeChange?: (theme: ThemeConfig) => void; } export interface ThemeContextValue { currentTheme: ThemeConfig; availableThemes: Record; setTheme: (themeId: string) => void; updateTheme: (updates: Partial) => void; getComponentStyles: ( componentType: ComponentType, variant?: string ) => StyleConfig; applyThemeToStyles: (styles: StyleConfig) => StyleConfig; } export interface ThemeControlsProps { position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; showPreview?: boolean; editable?: boolean; } export interface ColorPaletteProps { theme: ThemeConfig; onColorChange: (colorKey: keyof ThemeConfig, color: string) => void; editable: boolean; } export interface TypographyControlsProps { theme: ThemeConfig; onTypographyChange: (updates: Partial) => void; editable: boolean; } // ======================================== // Default Themes // ======================================== const DEFAULT_THEME: ThemeConfig = { primary: "#3b82f6", secondary: "#6b7280", accent: "#10b981", background: "#ffffff", surface: "#f9fafb", text: "#1f2937", textSecondary: "#6b7280", border: "#e5e7eb", error: "#ef4444", warning: "#f59e0b", success: "#10b981", info: "#3b82f6", shadow: "0 1px 3px rgba(0, 0, 0, 0.1)", }; const AVAILABLE_THEMES: Record = { default: DEFAULT_THEME, dark: { primary: "#60a5fa", secondary: "#9ca3af", accent: "#34d399", background: "#111827", surface: "#1f2937", text: "#f9fafb", textSecondary: "#d1d5db", border: "#374151", error: "#f87171", warning: "#fbbf24", success: "#34d399", info: "#60a5fa", shadow: "0 1px 3px rgba(0, 0, 0, 0.3)", }, minimal: { primary: "#000000", secondary: "#666666", accent: "#000000", background: "#ffffff", surface: "#fafafa", text: "#000000", textSecondary: "#666666", border: "#e0e0e0", error: "#d32f2f", warning: "#ff9800", success: "#388e3c", info: "#1976d2", shadow: "0 1px 2px rgba(0, 0, 0, 0.05)", }, }; // ======================================== // Theme Context // ======================================== const ThemeContext = createContext(null); export const useTheme = (): ThemeContextValue => { const context = useContext(ThemeContext); if (!context) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; }; // ======================================== // Color Palette Component // ======================================== export const ColorPalette: React.FC = ({ theme, onColorChange, editable, }) => { const colorKeys = Object.keys(theme) as Array; if (!editable) { return (
{colorKeys.map((key) => (
))}
); } return (
{colorKeys.map((key) => (
onColorChange(key, e.target.value)} style={{ width: "2rem", height: "2rem", border: "none", borderRadius: "0.25rem", cursor: "pointer", }} /> onColorChange(key, e.target.value)} style={{ flex: 1, padding: "0.25rem 0.5rem", border: "1px solid #d1d5db", borderRadius: "0.25rem", fontSize: "0.875rem", fontFamily: "monospace", }} />
))}
); }; // ======================================== // Theme Controls Component // ======================================== export const ThemeControls: React.FC = ({ position = "top-right", showPreview = true, editable = true, }) => { const { currentTheme, availableThemes, setTheme, updateTheme } = useTheme(); const [showControls, setShowControls] = useState(false); const [activeTab, setActiveTab] = useState< "themes" | "colors" | "typography" >("themes"); const positionStyles = useMemo(() => { const base: React.CSSProperties = { position: "fixed", zIndex: 1000, }; switch (position) { case "top-left": return { ...base, top: "1rem", left: "1rem" }; case "top-right": return { ...base, top: "1rem", right: "1rem" }; case "bottom-left": return { ...base, bottom: "1rem", left: "1rem" }; case "bottom-right": return { ...base, bottom: "1rem", right: "1rem" }; default: return { ...base, top: "1rem", right: "1rem" }; } }, [position]); const handleColorChange = useCallback( (colorKey: keyof ThemeConfig, color: string) => { updateTheme({ [colorKey]: color }); }, [updateTheme] ); if (!editable) return null; return (
{showControls && (
{/* Header */}

Theme Settings

{/* Tabs */}
{(["themes", "colors", "typography"] as const).map((tab) => ( ))}
{/* Content */}
{activeTab === "themes" && (
{Object.entries(availableThemes).map(([themeId, theme]) => ( ))}
)} {activeTab === "colors" && ( )} {activeTab === "typography" && (
Typography controls coming soon...
)}
{/* Preview */} {showPreview && (
Preview
Surface
)}
)}
); }; // ======================================== // Main ThemeSystem Component // ======================================== export const ThemeSystem: React.FC = ({ children, defaultTheme = DEFAULT_THEME, themes = AVAILABLE_THEMES, onThemeChange, }) => { const [currentTheme, setCurrentTheme] = useState(defaultTheme); const setTheme = useCallback( (themeId: string) => { const theme = themes[themeId]; if (theme) { setCurrentTheme(theme); if (onThemeChange) { onThemeChange(theme); } } }, [themes, onThemeChange] ); const updateTheme = useCallback( (updates: Partial) => { const newTheme = { ...currentTheme, ...updates }; setCurrentTheme(newTheme); if (onThemeChange) { onThemeChange(newTheme); } }, [currentTheme, onThemeChange] ); const getComponentStyles = useCallback( (_componentType: ComponentType, _variant?: string): StyleConfig => { // Simplified component styles for now return { color: currentTheme.text, backgroundColor: currentTheme.background, }; }, [currentTheme] ); const applyThemeToStyles = useCallback( (styles: StyleConfig): StyleConfig => { return { ...styles, // Note: CSS custom properties would be applied at runtime // This is a simplified version for TypeScript compatibility }; }, [currentTheme] ); const contextValue: ThemeContextValue = useMemo( () => ({ currentTheme, availableThemes: themes, setTheme, updateTheme, getComponentStyles, applyThemeToStyles, }), [ currentTheme, themes, setTheme, updateTheme, getComponentStyles, applyThemeToStyles, ] ); return (
{children}
); };