1+ // majority of code from https://www.joshwcomeau.com/react/dark-mode/ and https://github.com/gperl27/gatsby-styled-components-dark-mode
2+ // context provider for app to make accessible theme setting, toggle function, etc.
3+
14import React , { createContext , useState , useEffect , useCallback } from "react" ;
25
36export const ThemeSetting = {
@@ -18,6 +21,7 @@ const defaultState = {
1821
1922export const ThemeManagerContext = createContext ( defaultState ) ;
2023
24+ // Safe check for browser environment
2125const isBrowser = typeof window !== "undefined" ;
2226
2327const systemDarkModeSetting = ( ) =>
@@ -32,13 +36,16 @@ const applyThemeToDOM = (theme) => {
3236 const root = window . document . documentElement ;
3337 root . style . setProperty ( "--initial-color-mode" , theme ) ;
3438 root . setAttribute ( "data-theme" , theme ) ;
39+
40+ // Sync with SSR injected state
3541 window . __theme = theme ;
3642} ;
3743
3844export const ThemeManagerProvider = ( props ) => {
3945 const [ themeSetting , setThemeSetting ] = useState ( ThemeSetting . SYSTEM ) ;
4046 const [ didLoad , setDidLoad ] = useState ( false ) ;
4147
48+ // Initialize state from SSR script to prevent hydration mismatch
4249 const [ isDark , setIsDark ] = useState ( ( ) => {
4350 if ( isBrowser ) {
4451 if ( window . __theme === ThemeSetting . DARK ) return true ;
@@ -52,8 +59,11 @@ export const ThemeManagerProvider = (props) => {
5259
5360 const root = window . document . documentElement ;
5461 const initialColorValue = ( root . style . getPropertyValue ( "--initial-color-mode" ) || "" ) . trim ( ) ;
62+
63+ // Prioritize SSR-injected theme
5564 const actualTheme = window . __theme || initialColorValue || ThemeSetting . LIGHT ;
5665
66+ // Get stored theme from localStorage
5767 const storedTheme = localStorage . getItem ( DarkThemeKey ) ;
5868
5969 if ( storedTheme && storedTheme !== ThemeSetting . SYSTEM ) {
@@ -65,6 +75,7 @@ export const ThemeManagerProvider = (props) => {
6575 setIsDark ( actualTheme === ThemeSetting . DARK ) ;
6676 setThemeSetting ( ThemeSetting . SYSTEM ) ;
6777 } else {
78+ // Fallback to system preference
6879 const systemIsDark = isDarkModeActive ( ) ;
6980 setIsDark ( systemIsDark ) ;
7081 const theme = systemIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
@@ -74,6 +85,7 @@ export const ThemeManagerProvider = (props) => {
7485 setDidLoad ( true ) ;
7586 } , [ ] ) ;
7687
88+ // Listen to system color scheme changes only when on SYSTEM mode
7789 useEffect ( ( ) => {
7890 if ( ! isBrowser || themeSetting !== ThemeSetting . SYSTEM ) return ;
7991
@@ -95,11 +107,14 @@ export const ThemeManagerProvider = (props) => {
95107 const newIsDark = ! isDark ;
96108 const newTheme = newIsDark ? ThemeSetting . DARK : ThemeSetting . LIGHT ;
97109
110+ // Update state
98111 setIsDark ( newIsDark ) ;
99112 setThemeSetting ( newTheme ) ;
100113
114+ // Apply to DOM immediately
101115 applyThemeToDOM ( newTheme ) ;
102116
117+ // Persist to localStorage
103118 localStorage . setItem ( DarkThemeKey , newTheme ) ;
104119 } , [ isDark ] ) ;
105120
@@ -128,10 +143,14 @@ export const ThemeManagerProvider = (props) => {
128143 return ;
129144 }
130145
146+ // Update state
131147 setIsDark ( newIsDark ) ;
132148 setThemeSetting ( setting ) ;
133149
150+ // Apply to DOM immediately
134151 applyThemeToDOM ( themeToApply ) ;
152+
153+ // Persist to localStorage
135154 localStorage . setItem ( DarkThemeKey , setting ) ;
136155 } ,
137156 [ isDark ]
0 commit comments