22
33//context provider for app to make accessible theme setting, toggle function, etc.
44
5- import React , { createContext , useState , useEffect } from "react" ;
5+ import React , { createContext , useState , useEffect , useCallback } from "react" ;
66
77export const ThemeSetting = {
88 LIGHT : "light" ,
@@ -28,10 +28,15 @@ const isBrowser = typeof window !== "undefined";
2828const systemDarkModeSetting = ( ) =>
2929 isBrowser && window . matchMedia ? window . matchMedia ( "(prefers-color-scheme: dark)" ) : null ;
3030const isDarkModeActive = ( ) => {
31- // Assume that dark mode is not active if there's no system dark mode setting available
3231 return ! ! systemDarkModeSetting ( ) ?. matches ;
3332} ;
3433
34+ const applyThemeToDOM = ( theme ) => {
35+ if ( ! isBrowser ) return ;
36+ const root = window . document . documentElement ;
37+ root . style . setProperty ( "--initial-color-mode" , theme ) ;
38+ root . setAttribute ( "data-theme" , theme ) ;
39+ } ;
3540
3641export const ThemeManagerProvider = ( props ) => {
3742 const [ themeSetting , setThemeSetting ] = useState ( ThemeSetting . SYSTEM ) ;
@@ -42,49 +47,100 @@ export const ThemeManagerProvider = (props) => {
4247 if ( ! isBrowser ) return ;
4348
4449 const root = window . document . documentElement ;
45- const initialColorValue = root . style . getPropertyValue (
46- "--initial-color-mode"
47- ) ;
48- setIsDark ( initialColorValue === ThemeSetting . DARK ) ;
50+ const initialColorValue = root . style . getPropertyValue ( "--initial-color-mode" ) ;
51+
52+ // Get stored theme from localStorage
53+ const storedTheme = localStorage . getItem ( DarkThemeKey ) ;
54+
55+ if ( storedTheme && storedTheme !== ThemeSetting . SYSTEM ) {
56+ const isDarkTheme = storedTheme === ThemeSetting . DARK ;
57+ setIsDark ( isDarkTheme ) ;
58+ setThemeSetting ( storedTheme ) ;
59+ applyThemeToDOM ( storedTheme ) ;
60+ } else if ( initialColorValue ) {
61+ setIsDark ( initialColorValue === ThemeSetting . DARK ) ;
62+ setThemeSetting ( ThemeSetting . SYSTEM ) ;
63+ } else {
64+ // Fallback to system preference
65+ const systemIsDark = isDarkModeActive ( ) ;
66+ setIsDark ( systemIsDark ) ;
67+ const theme = systemIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
68+ applyThemeToDOM ( theme ) ;
69+ }
70+
4971 setDidLoad ( true ) ;
72+ } , [ ] ) ;
73+
74+ // Listen to system color scheme changes only when on SYSTEM mode
75+ useEffect ( ( ) => {
76+ if ( ! isBrowser || themeSetting !== ThemeSetting . SYSTEM ) return ;
5077
51- // Add listener for system color scheme changes
5278 const darkModeMediaQuery = systemDarkModeSetting ( ) ;
53- if ( darkModeMediaQuery && themeSetting === ThemeSetting . SYSTEM ) {
54- const handleChange = ( e ) => {
55- setIsDark ( e . matches ) ;
56- } ;
57- darkModeMediaQuery . addEventListener ( "change" , handleChange ) ;
58- return ( ) => darkModeMediaQuery . removeEventListener ( "change" , handleChange ) ;
59- }
79+ if ( ! darkModeMediaQuery ) return ;
80+
81+ const handleChange = ( e ) => {
82+ setIsDark ( e . matches ) ;
83+ applyThemeToDOM ( e . matches ? ThemeSetting . DARK : ThemeSetting . LIGHT ) ;
84+ } ;
85+
86+ darkModeMediaQuery . addEventListener ( "change" , handleChange ) ;
87+ return ( ) => darkModeMediaQuery . removeEventListener ( "change" , handleChange ) ;
6088 } , [ themeSetting ] ) ;
6189
62- const toggleDark = ( value ) => {
90+ const toggleDark = useCallback ( ( ) => {
6391 if ( ! isBrowser ) return ;
6492
65- const newIsDark = value ?? ! isDark ;
66- const theme = newIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
93+ const newIsDark = ! isDark ;
94+ const newTheme = newIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
95+
96+ // Update state
6797 setIsDark ( newIsDark ) ;
68- setThemeSetting ( theme ) ;
69- localStorage . setItem ( DarkThemeKey , theme ) ;
70- } ;
98+ setThemeSetting ( newTheme ) ;
99+
100+ // Apply to DOM immediately
101+ applyThemeToDOM ( newTheme ) ;
102+
103+ // Persist to localStorage
104+ localStorage . setItem ( DarkThemeKey , newTheme ) ;
105+ } , [ isDark ] ) ;
106+
107+ const changeThemeSetting = useCallback (
108+ ( setting ) => {
109+ if ( ! isBrowser ) return ;
110+
111+ let newIsDark = isDark ;
112+ let themeToApply = setting ;
113+
114+ switch ( setting ) {
115+ case ThemeSetting . SYSTEM : {
116+ newIsDark = isDarkModeActive ( ) ;
117+ themeToApply = newIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
118+ break ;
119+ }
120+ case ThemeSetting . LIGHT :
121+ newIsDark = false ;
122+ themeToApply = ThemeSetting . LIGHT ;
123+ break ;
124+ case ThemeSetting . DARK :
125+ newIsDark = true ;
126+ themeToApply = ThemeSetting . DARK ;
127+ break ;
128+ default :
129+ return ;
130+ }
71131
72- const changeThemeSetting = ( setting ) => {
73- if ( ! isBrowser ) return ;
132+ // Update state
133+ setIsDark ( newIsDark ) ;
134+ setThemeSetting ( setting ) ;
74135
75- switch ( setting ) {
76- case ThemeSetting . SYSTEM : {
77- setIsDark ( isDarkModeActive ( ) ) ;
78- break ;
79- }
80- case ThemeSetting . LIGHT :
81- case ThemeSetting . DARK :
82- setIsDark ( setting === ThemeSetting . DARK ) ;
83- break ;
84- }
85- setThemeSetting ( setting ) ;
86- localStorage . setItem ( DarkThemeKey , setting ) ;
87- } ;
136+ // Apply to DOM immediately
137+ applyThemeToDOM ( themeToApply ) ;
138+
139+ // Persist to localStorage
140+ localStorage . setItem ( DarkThemeKey , setting ) ;
141+ } ,
142+ [ isDark ]
143+ ) ;
88144
89145 return (
90146 < ThemeManagerContext . Provider
0 commit comments