|
193
|
1 import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
|
|
2
|
|
|
3 interface ThemeContextType {
|
|
|
4 isDark: boolean;
|
|
|
5 toggleTheme: () => void;
|
|
|
6 }
|
|
|
7
|
|
|
8 const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
|
9
|
|
|
10 // Apply theme class to document root
|
|
|
11 function applyTheme(isDark: boolean) {
|
|
|
12 const root = document.documentElement;
|
|
|
13 if (isDark) {
|
|
|
14 root.classList.add('dark');
|
|
|
15 root.classList.remove('light');
|
|
|
16 } else {
|
|
|
17 root.classList.add('light');
|
|
|
18 root.classList.remove('dark');
|
|
|
19 }
|
|
|
20 }
|
|
|
21
|
|
|
22 interface ThemeProviderProps {
|
|
|
23 children: ReactNode;
|
|
|
24 }
|
|
|
25
|
|
|
26 function ThemeProvider({ children }: ThemeProviderProps) {
|
|
|
27 const [isDark, setIsDark] = useState(() => {
|
|
|
28 const saved = localStorage.getItem('theme');
|
|
|
29 if (saved) return saved === 'dark';
|
|
|
30 return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
31 });
|
|
|
32
|
|
|
33 // Apply theme on mount and change
|
|
|
34 useEffect(() => {
|
|
|
35 applyTheme(isDark);
|
|
|
36 localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
|
37 }, [isDark]);
|
|
|
38
|
|
|
39 // Listen for system theme changes
|
|
|
40 useEffect(() => {
|
|
|
41 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
|
42 const handleChange = (e: MediaQueryListEvent) => {
|
|
|
43 // Only apply if no explicit preference is saved
|
|
|
44 if (!localStorage.getItem('theme')) {
|
|
|
45 setIsDark(e.matches);
|
|
|
46 }
|
|
|
47 };
|
|
|
48
|
|
|
49 mediaQuery.addEventListener('change', handleChange);
|
|
|
50 return () => mediaQuery.removeEventListener('change', handleChange);
|
|
|
51 }, []);
|
|
|
52
|
|
|
53 const toggleTheme = useCallback(() => {
|
|
|
54 setIsDark(prev => !prev);
|
|
|
55 }, []);
|
|
|
56
|
|
|
57 return (
|
|
|
58 <ThemeContext.Provider value={{ isDark, toggleTheme }}>
|
|
|
59 {children}
|
|
|
60 </ThemeContext.Provider>
|
|
|
61 );
|
|
|
62 }
|
|
|
63
|
|
|
64 function useTheme(): ThemeContextType {
|
|
|
65 const context = useContext(ThemeContext);
|
|
|
66 if (context === undefined) {
|
|
|
67 throw new Error('useTheme must be used within a ThemeProvider');
|
|
|
68 }
|
|
|
69 return context;
|
|
|
70 }
|
|
|
71
|
|
|
72 export { ThemeProvider, useTheme };
|