UNPKG

react-theme-system

Version:

A comprehensive React theme management system that enforces consistency, supports dark/light mode, and eliminates hardcoded styles

321 lines (320 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.applyThemeToDOM = exports.themeToCSSVariables = exports.setStoredTheme = exports.getStoredTheme = exports.isValidTheme = exports.createThemeConfig = void 0; const types_1 = require("../types"); /** * Theme configuration helper * Creates a validated theme configuration with fallbacks */ const createThemeConfig = (config) => { const defaultConfig = { light: { colors: { primary: '#4361ee', secondary: '#3f37c9', accent: '#4895ef', background: '#ffffff', surface: '#f8f9fa', text: { primary: '#212529', secondary: '#6c757d', disabled: '#adb5bd' }, border: '#dee2e6', error: '#dc3545', warning: '#ffc107', success: '#28a745', info: '#17a2b8' }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '3rem', xxl: '4rem', scale: (multiplier) => `${multiplier * 0.25}rem` }, typography: { fontFamily: { primary: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', secondary: 'Georgia, "Times New Roman", serif', mono: 'SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace' }, fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem' }, fontWeight: { light: 300, normal: 400, medium: 500, semibold: 600, bold: 700 }, lineHeight: { tight: '1.25', normal: '1.5', relaxed: '1.75' } }, shadows: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', none: 'none' }, borderRadius: { none: '0', sm: '0.125rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', full: '9999px' }, breakpoints: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px' }, transitions: { fast: '150ms ease-in-out', normal: '300ms ease-in-out', slow: '500ms ease-in-out', ease: { in: 'ease-in', out: 'ease-out', inOut: 'ease-in-out' } }, zIndex: { hide: -1, auto: 0, base: 0, docked: 10, dropdown: 1000, sticky: 1100, banner: 1200, overlay: 1300, modal: 1400, popover: 1500, skipLink: 1600, toast: 1700, tooltip: 1800 } }, dark: { colors: { primary: '#60a5fa', secondary: '#818cf8', accent: '#34d399', background: '#0f172a', surface: '#1e293b', text: { primary: '#f8fafc', secondary: '#cbd5e1', disabled: '#64748b' }, border: '#334155', error: '#f87171', warning: '#fbbf24', success: '#4ade80', info: '#60a5fa' }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '3rem', xxl: '4rem', scale: (multiplier) => `${multiplier * 0.25}rem` }, typography: { fontFamily: { primary: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', secondary: 'Georgia, "Times New Roman", serif', mono: 'SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace' }, fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem' }, fontWeight: { light: 300, normal: 400, medium: 500, semibold: 600, bold: 700 }, lineHeight: { tight: '1.25', normal: '1.5', relaxed: '1.75' } }, shadows: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.5)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.3)', none: 'none' }, borderRadius: { none: '0', sm: '0.125rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', full: '9999px' }, breakpoints: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px' }, transitions: { fast: '150ms ease-in-out', normal: '300ms ease-in-out', slow: '500ms ease-in-out', ease: { in: 'ease-in', out: 'ease-out', inOut: 'ease-in-out' } }, zIndex: { hide: -1, auto: 0, base: 0, docked: 10, dropdown: 1000, sticky: 1100, banner: 1200, overlay: 1300, modal: 1400, popover: 1500, skipLink: 1600, toast: 1700, tooltip: 1800 } } }; return { light: { ...defaultConfig.light, ...config.light }, dark: { ...defaultConfig.dark, ...config.dark } }; }; exports.createThemeConfig = createThemeConfig; /** * Theme validation function */ const isValidTheme = (theme) => { return types_1.VALID_THEMES.includes(theme); }; exports.isValidTheme = isValidTheme; /** * Get theme from localStorage with fallback */ const getStoredTheme = (defaultTheme = 'light') => { if (typeof window === 'undefined') return defaultTheme; try { const stored = localStorage.getItem('react-theme-system-theme'); return stored && (0, exports.isValidTheme)(stored) ? stored : defaultTheme; } catch { return defaultTheme; } }; exports.getStoredTheme = getStoredTheme; /** * Set theme in localStorage */ const setStoredTheme = (theme) => { if (typeof window === 'undefined') return; try { localStorage.setItem('react-theme-system-theme', theme); } catch (error) { console.warn('Failed to persist theme:', error); } }; exports.setStoredTheme = setStoredTheme; /** * Convert theme object to CSS variables */ const themeToCSSVariables = (theme) => { const cssVars = {}; // Colors Object.entries(theme.colors).forEach(([key, value]) => { cssVars[`--color-${key}`] = value; }); // Spacing Object.entries(theme.spacing).forEach(([key, value]) => { cssVars[`--spacing-${key}`] = value; }); // Typography Object.entries(theme.typography.fontSize).forEach(([key, value]) => { cssVars[`--font-size-${key}`] = value; }); Object.entries(theme.typography.fontWeight).forEach(([key, value]) => { cssVars[`--font-weight-${key}`] = value.toString(); }); Object.entries(theme.typography.fontFamily).forEach(([key, value]) => { cssVars[`--font-family-${key}`] = value; }); // Shadows Object.entries(theme.shadows).forEach(([key, value]) => { cssVars[`--shadow-${key}`] = value; }); // Border radius Object.entries(theme.borderRadius).forEach(([key, value]) => { cssVars[`--radius-${key}`] = value; }); // Transitions Object.entries(theme.transitions).forEach(([key, value]) => { cssVars[`--transition-${key}`] = value; }); // Z-index Object.entries(theme.zIndex).forEach(([key, value]) => { cssVars[`--z-${key}`] = value.toString(); }); return cssVars; }; exports.themeToCSSVariables = themeToCSSVariables; /** * Apply theme CSS variables to document */ const applyThemeToDOM = (theme) => { if (typeof document === 'undefined') return; const cssVars = (0, exports.themeToCSSVariables)(theme); const root = document.documentElement; Object.entries(cssVars).forEach(([key, value]) => { root.style.setProperty(key, value); }); }; exports.applyThemeToDOM = applyThemeToDOM;