import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react' interface ThemeColors { [key: string]: string } interface Theme { name: string label: string colors: ThemeColors } interface ThemeContextValue { theme: Theme themeName: string setTheme: (name: string) => void themes: Record } const STORAGE_KEY = 'frontend-library-theme' const ThemeContext = createContext(null) function applyThemeColors(theme: Theme) { const root = document.documentElement const cssVarMap: Record = { background: '--background', foreground: '--foreground', card: '--card', cardForeground: '--card-foreground', popover: '--popover', popoverForeground: '--popover-foreground', primary: '--primary', primaryForeground: '--primary-foreground', secondary: '--secondary', secondaryForeground: '--secondary-foreground', muted: '--muted', mutedForeground: '--muted-foreground', accent: '--accent', accentForeground: '--accent-foreground', destructive: '--destructive', destructiveForeground: '--destructive-foreground', success: '--success', successForeground: '--success-foreground', warning: '--warning', warningForeground: '--warning-foreground', info: '--info', infoForeground: '--info-foreground', surface: '--surface', surfaceHover: '--surface-hover', overlay: '--overlay', border: '--border', input: '--input', ring: '--ring', chart1: '--chart-1', chart2: '--chart-2', chart3: '--chart-3', chart4: '--chart-4', chart5: '--chart-5', sidebar: '--sidebar', sidebarForeground: '--sidebar-foreground', sidebarPrimary: '--sidebar-primary', sidebarPrimaryForeground: '--sidebar-primary-foreground', sidebarAccent: '--sidebar-accent', sidebarAccentForeground: '--sidebar-accent-foreground', sidebarBorder: '--sidebar-border', sidebarRing: '--sidebar-ring', } Object.entries(cssVarMap).forEach(([key, cssVar]) => { if (theme.colors[key]) root.style.setProperty(cssVar, theme.colors[key]) }) if (theme.name === 'dark' || theme.name === 'midnight' || theme.name === 'sunset') { root.classList.add('dark') } else { root.classList.remove('dark') } } interface ThemeProviderProps { children: ReactNode themes: Record defaultTheme?: string } export function ThemeProvider({ children, themes, defaultTheme: initialTheme }: ThemeProviderProps) { const [themeName, setThemeName] = useState(() => { if (initialTheme) return initialTheme if (typeof window === 'undefined') return 'default' const stored = localStorage.getItem(STORAGE_KEY) if (stored && themes[stored]) return stored if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark' return 'default' }) const theme = themes[themeName] ?? themes['default'] ?? Object.values(themes)[0] const setTheme = useCallback((name: string) => { if (themes[name]) { setThemeName(name) localStorage.setItem(STORAGE_KEY, name) } }, [themes]) useEffect(() => { applyThemeColors(theme) }, [theme]) useEffect(() => { const mq = window.matchMedia('(prefers-color-scheme: dark)') const handle = (e: MediaQueryListEvent) => { if (!localStorage.getItem(STORAGE_KEY)) setThemeName(e.matches ? 'dark' : 'default') } mq.addEventListener('change', handle) return () => mq.removeEventListener('change', handle) }, []) return {children} } export function useTheme() { const ctx = useContext(ThemeContext) if (!ctx) throw new Error('useTheme must be used within a ThemeProvider') return ctx } export { ThemeContext } export type { ThemeContextValue, ThemeProviderProps, Theme, ThemeColors }