1+ import * as React from "react" ;
2+
3+ type Theme = 'light' | 'dark' | 'system' ;
4+ type ThemeContextType = {
5+ theme : Theme ;
6+ resolvedTheme : 'light' | 'dark' ;
7+ setTheme : ( theme : Theme ) => void ;
8+ } ;
9+
10+ const ThemeContext = React . createContext < ThemeContextType | undefined > ( undefined ) ;
11+
12+ export const ThemeProvider : React . FC < { children : React . ReactNode } > = ( { children } ) => {
13+ const [ theme , setThemeState ] = React . useState < Theme > ( ( ) => {
14+ if ( typeof window === 'undefined' ) return 'system' ;
15+ return ( localStorage . getItem ( 'theme' ) as Theme ) || 'system' ;
16+ } ) ;
17+
18+ const [ resolvedTheme , setResolvedTheme ] = React . useState < 'light' | 'dark' > ( ( ) => {
19+ if ( typeof window === 'undefined' ) return 'light' ;
20+ if ( theme === 'system' ) {
21+ return window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ? 'dark' : 'light' ;
22+ }
23+ return theme ;
24+ } ) ;
25+
26+ // Handle system preference changes
27+ React . useEffect ( ( ) => {
28+ const mediaQuery = window . matchMedia ( '(prefers-color-scheme: dark)' ) ;
29+
30+ const handleChange = ( ) => {
31+ if ( theme === 'system' ) {
32+ const newTheme = mediaQuery . matches ? 'dark' : 'light' ;
33+ setResolvedTheme ( newTheme ) ;
34+ updateDocumentClass ( newTheme ) ;
35+ }
36+ } ;
37+
38+ mediaQuery . addEventListener ( 'change' , handleChange ) ;
39+ return ( ) => mediaQuery . removeEventListener ( 'change' , handleChange ) ;
40+ } , [ theme ] ) ;
41+
42+ // Update the DOM and resolved theme when theme changes
43+ React . useEffect ( ( ) => {
44+ if ( theme === 'system' ) {
45+ const systemTheme = window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ? 'dark' : 'light' ;
46+ setResolvedTheme ( systemTheme ) ;
47+ updateDocumentClass ( systemTheme ) ;
48+ } else {
49+ setResolvedTheme ( theme ) ;
50+ updateDocumentClass ( theme ) ;
51+ }
52+ } , [ theme ] ) ;
53+
54+ const updateDocumentClass = ( resolvedTheme : string ) => {
55+ if ( resolvedTheme === 'dark' ) {
56+ document . documentElement . classList . add ( 'dark' ) ;
57+ } else {
58+ document . documentElement . classList . remove ( 'dark' ) ;
59+ }
60+ } ;
61+
62+ const setTheme = ( newTheme : Theme ) => {
63+ setThemeState ( newTheme ) ;
64+ if ( newTheme === 'system' ) {
65+ localStorage . removeItem ( 'theme' ) ;
66+ } else {
67+ localStorage . setItem ( 'theme' , newTheme ) ;
68+ }
69+ } ;
70+
71+ return (
72+ < ThemeContext . Provider value = { { theme, resolvedTheme, setTheme } } >
73+ { children }
74+ </ ThemeContext . Provider >
75+ ) ;
76+ } ;
77+
78+ export const useTheme = ( ) => {
79+ const context = React . useContext ( ThemeContext ) ;
80+ if ( context === undefined ) {
81+ throw new Error ( 'useTheme must be used within a ThemeProvider' ) ;
82+ }
83+ return context ;
84+ } ;
0 commit comments