Files
fn_registry/frontend/functions/ui/theme_provider.tsx
T
egutierrez dc78d8fea3 feat: funciones frontend React/TS — componentes UI, hooks Wails, charts y tipos
Componentes React reutilizables: card, dialog, tabs, select, alert, badge, button, input, label,
skeleton, tooltip, progress_bar, page_header, form_field, settings_page, crud_page, analytics_page,
dashboard_layout. Charts: area, bar, line, sparkline, kpi_card, chart_container.
Hooks Wails: use_wails_query, use_wails_mutation, use_wails_stream, use_wails_event, use_animated_canvas.
Funciones core: cn, format_compact, chart_colors, get_series_color, wails_cache, theme_config_to_colors.
Tipos: chart_series, wails_ipc, theme_config, component_variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:55:34 +02:00

102 lines
3.8 KiB
TypeScript

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<string, Theme>
}
const STORAGE_KEY = 'frontend-library-theme'
const ThemeContext = createContext<ThemeContextValue | null>(null)
function applyThemeColors(theme: Theme) {
const root = document.documentElement
const cssVarMap: Record<string, string> = {
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<string, Theme>
defaultTheme?: string
}
export function ThemeProvider({ children, themes, defaultTheme: initialTheme }: ThemeProviderProps) {
const [themeName, setThemeName] = useState<string>(() => {
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 <ThemeContext.Provider value={{ theme, themeName, setTheme, themes }}>{children}</ThemeContext.Provider>
}
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 }