UNPKG

canoejs

Version:

A lightweight, widget-based UI framework

231 lines (215 loc) 5.31 kB
import { Canoe } from "../../canoe"; export interface Theme { name: string; colors: { primary: string; secondary: string; success: string; danger: string; warning: string; info: string; light: string; dark: string; white: string; black: string; gray: { 50: string; 100: string; 200: string; 300: string; 400: string; 500: string; 600: string; 700: string; 800: string; 900: string; }; }; typography: { fontFamily: string; fontSize: { xs: string; sm: string; base: string; lg: string; xl: string; '2xl': string; '3xl': string; '4xl': string; '5xl': string; }; fontWeight: { light: string; normal: string; medium: string; semibold: string; bold: string; }; }; spacing: { xs: string; sm: string; md: string; lg: string; xl: string; '2xl': string; '3xl': string; }; borderRadius: { none: string; sm: string; md: string; lg: string; xl: string; full: string; }; shadows: { sm: string; md: string; lg: string; xl: string; }; } export const defaultTheme: Theme = { name: 'default', colors: { primary: '#3b82f6', secondary: '#6b7280', success: '#10b981', danger: '#ef4444', warning: '#f59e0b', info: '#06b6d4', light: '#f8fafc', dark: '#1e293b', white: '#ffffff', black: '#000000', gray: { 50: '#f8fafc', 100: '#f1f5f9', 200: '#e2e8f0', 300: '#cbd5e1', 400: '#94a3b8', 500: '#64748b', 600: '#475569', 700: '#334155', 800: '#1e293b', 900: '#0f172a', }, }, typography: { fontFamily: 'system-ui, -apple-system, sans-serif', fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', }, fontWeight: { light: '300', normal: '400', medium: '500', semibold: '600', bold: '700', }, }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem', '3xl': '4rem', }, borderRadius: { none: '0', sm: '0.125rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', full: '9999px', }, shadows: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', }, }; export const darkTheme: Theme = { ...defaultTheme, name: 'dark', colors: { ...defaultTheme.colors, primary: '#60a5fa', secondary: '#9ca3af', light: '#1e293b', dark: '#f8fafc', white: '#000000', black: '#ffffff', }, }; export class ThemeProvider { private static currentTheme: Theme = defaultTheme; private static themeChangeCallbacks: ((theme: Theme) => void)[] = []; static getTheme(): Theme { return this.currentTheme; } static setTheme(theme: Theme): void { this.currentTheme = theme; this.applyTheme(theme); this.themeChangeCallbacks.forEach(callback => callback(theme)); } static onThemeChange(callback: (theme: Theme) => void): void { this.themeChangeCallbacks.push(callback); } static applyTheme(theme: Theme): void { const root = document.documentElement; // Apply CSS custom properties Object.entries(theme.colors).forEach(([key, value]) => { if (typeof value === 'object') { Object.entries(value).forEach(([subKey, subValue]) => { root.style.setProperty(`--color-${key}-${subKey}`, subValue); }); } else { root.style.setProperty(`--color-${key}`, value); } }); Object.entries(theme.typography).forEach(([key, value]) => { if (typeof value === 'object') { Object.entries(value).forEach(([subKey, subValue]) => { root.style.setProperty(`--font-${key}-${subKey}`, subValue); }); } else { root.style.setProperty(`--font-${key}`, value); } }); Object.entries(theme.spacing).forEach(([key, value]) => { root.style.setProperty(`--spacing-${key}`, value); }); Object.entries(theme.borderRadius).forEach(([key, value]) => { root.style.setProperty(`--radius-${key}`, value); }); Object.entries(theme.shadows).forEach(([key, value]) => { root.style.setProperty(`--shadow-${key}`, value); }); // Add theme class to body document.body.className = document.body.className .replace(/theme-\w+/g, '') .trim(); document.body.classList.add(`theme-${theme.name}`); } static toggleDarkMode(): void { const isDark = this.currentTheme.name === 'dark'; this.setTheme(isDark ? defaultTheme : darkTheme); } } // Initialize theme on load if (typeof window !== 'undefined') { ThemeProvider.applyTheme(defaultTheme); }