EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
ThemeContext.tsx
Go to the documentation of this file.
1"use client";
2
3import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
4import { Theme, getTheme, defaultTheme } from '@/lib/themes';
5
6// Helper to convert hex to RGB
7function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
8 const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
9 return result ? {
10 r: parseInt(result[1], 16),
11 g: parseInt(result[2], 16),
12 b: parseInt(result[3], 16)
13 } : null;
14}
15
16interface ThemeContextType {
17 theme: Theme;
18 themeId: string;
19 setTheme: (themeId: string) => void;
20 isLoading: boolean;
21}
22
23const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
24
25const THEME_STORAGE_KEY = 'everydaypos_theme';
26
27export function ThemeProvider({ children }: { children: ReactNode }) {
28 const [themeId, setThemeId] = useState<string>(defaultTheme);
29 const [isLoading, setIsLoading] = useState(true);
30
31 // Load theme from localStorage on mount
32 useEffect(() => {
33 if (typeof window !== 'undefined') {
34 const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
35 if (savedTheme) {
36 setThemeId(savedTheme);
37 }
38 setIsLoading(false);
39 }
40 }, []);
41
42 // Apply theme CSS variables whenever theme changes
43 useEffect(() => {
44 if (typeof window === 'undefined') return;
45
46 const theme = getTheme(themeId);
47 const root = document.documentElement;
48
49 // Apply color variables
50 root.style.setProperty('--bg', theme.colors.bg);
51 root.style.setProperty('--surface', theme.colors.surface);
52 root.style.setProperty('--surface-2', theme.colors.surface2);
53 root.style.setProperty('--text', theme.colors.text);
54 root.style.setProperty('--muted', theme.colors.muted);
55 root.style.setProperty('--border', theme.colors.border);
56 root.style.setProperty('--brand', theme.colors.brand);
57 root.style.setProperty('--brand-2', theme.colors.brand2);
58 root.style.setProperty('--warn', theme.colors.warn);
59 root.style.setProperty('--danger', theme.colors.danger);
60
61 // Computed hover states for better interactivity
62 root.style.setProperty('--border-hover', theme.colors.brand);
63 root.style.setProperty('--surface-hover', theme.colors.surface2);
64
65 // Apply optional colors
66 if (theme.colors.success) {
67 root.style.setProperty('--success', theme.colors.success);
68 }
69 if (theme.colors.info) {
70 root.style.setProperty('--info', theme.colors.info);
71 }
72
73 // Apply accent colors for visual effects
74 const accent = theme.colors.brand2 || theme.colors.brand;
75 root.style.setProperty('--accent', accent);
76
77 // Create accent variations
78 const brandRgb = hexToRgb(theme.colors.brand);
79 if (brandRgb) {
80 root.style.setProperty('--accent-light', `rgba(${brandRgb.r}, ${brandRgb.g}, ${brandRgb.b}, 0.1)`);
81 root.style.setProperty('--accent-glow', `rgba(${brandRgb.r}, ${brandRgb.g}, ${brandRgb.b}, 0.3)`);
82 }
83
84 // Apply shadows
85 root.style.setProperty('--shadow', theme.shadows.shadow);
86 root.style.setProperty('--shadow-sm', theme.shadows.shadowSm);
87
88 // Apply radius
89 root.style.setProperty('--radius', theme.radius.radius);
90 root.style.setProperty('--radius-sm', theme.radius.radiusSm);
91
92 // Apply focus
93 root.style.setProperty('--focus', theme.focus);
94
95 // Add theme class to body for conditional styling
96 document.body.className = document.body.className.replace(/theme-\w+/g, '');
97 document.body.classList.add(`theme-${themeId}`);
98 }, [themeId]);
99
100 const handleSetTheme = (newThemeId: string) => {
101 setThemeId(newThemeId);
102 if (typeof window !== 'undefined') {
103 localStorage.setItem(THEME_STORAGE_KEY, newThemeId);
104 }
105 };
106
107 const theme = getTheme(themeId);
108
109 return (
110 <ThemeContext.Provider value={{ theme, themeId, setTheme: handleSetTheme, isLoading }}>
111 {children}
112 </ThemeContext.Provider>
113 );
114}
115
116export function useTheme() {
117 const context = useContext(ThemeContext);
118 if (context === undefined) {
119 throw new Error('useTheme must be used within a ThemeProvider');
120 }
121 return context;
122}