1- import React , { useEffect , useState , createContext } from 'react' ;
1+ import React , { useEffect , useState , createContext , useCallback } from 'react' ;
22import {
33 Button ,
44 Page ,
@@ -232,6 +232,26 @@ export function attachDocSearch(algolia, inputSelector, timeout) {
232232 }
233233}
234234
235+ const DARK_MODE_CLASS = "pf-v6-theme-dark" ;
236+ const DARK_MODE_STORAGE_KEY = "dark-mode" ;
237+
238+ /**
239+ * Determines if dark mode is enabled based on the stored value or the system preference.
240+ * @returns {boolean } true if dark mode is enabled, false otherwise
241+ */
242+ function isDarkModeEnabled ( ) {
243+ // When running in prerender mode we can't access the browser APIs.
244+ if ( process . env . PRERENDER ) {
245+ return false ;
246+ }
247+
248+ const mediaQuery = window . matchMedia ( "(prefers-color-scheme: dark)" ) ;
249+ const storedValue = localStorage . getItem ( DARK_MODE_STORAGE_KEY ) ;
250+ const isEnabled = storedValue === null ? mediaQuery . matches : storedValue === "true" ;
251+
252+ return isEnabled ;
253+ }
254+
235255export const SideNavLayout = ( { children, groupedRoutes, navOpen : navOpenProp } ) => {
236256 const algolia = process . env . algolia ;
237257 const hasGdprBanner = process . env . hasGdprBanner ;
@@ -245,7 +265,63 @@ export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp })
245265
246266 const [ versions , setVersions ] = useState ( { ...staticVersions } ) ;
247267 const [ isRTL , setIsRTL ] = useState ( false ) ;
248- const [ isDarkTheme , setIsDarkTheme ] = React . useState ( false ) ;
268+ const [ isDarkTheme , setIsDarkThemeInternal ] = useState ( ( ) => isDarkModeEnabled ( ) ) ;
269+
270+ /**
271+ * Stores the dark mode preference in local storage and updates the dark mode class.
272+ */
273+ const setIsDarkTheme = useCallback ( ( value ) => {
274+ localStorage . setItem ( DARK_MODE_STORAGE_KEY , value . toString ( ) ) ;
275+ updateDarkMode ( ) ;
276+ } , [ ] ) ;
277+
278+ /**
279+ * Updates the dark mode class to the root element depending on whether dark mode is enabled.
280+ */
281+ const updateDarkMode = useCallback ( ( ) => {
282+ const isEnabled = isDarkModeEnabled ( ) ;
283+ const root = document . documentElement ;
284+
285+ if ( isEnabled ) {
286+ root . classList . add ( DARK_MODE_CLASS ) ;
287+ } else {
288+ root . classList . remove ( DARK_MODE_CLASS ) ;
289+ }
290+
291+ setIsDarkThemeInternal ( isEnabled ) ;
292+ } , [ ] ) ;
293+
294+ useEffect ( ( ) => {
295+ // When running in prerender mode we can't access the browser APIs.
296+ if ( process . env . PRERENDER ) {
297+ return ;
298+ }
299+
300+ // Update the dark mode when the the user changes their system/browser preference.
301+ const onQueryChange = ( ) => {
302+ // Remove the stored value to defer to the system preference.
303+ localStorage . removeItem ( DARK_MODE_STORAGE_KEY ) ;
304+ updateDarkMode ( ) ;
305+ } ;
306+
307+ // Update the dark mode when the user changes the preference in another context (e.g. tab or window).
308+ /** @type {(event: StorageEvent) => void } */
309+ const onStorageChange = ( event ) => {
310+ if ( event . key === DARK_MODE_STORAGE_KEY ) {
311+ updateDarkMode ( ) ;
312+ }
313+ } ;
314+
315+ const mediaQuery = window . matchMedia ( "(prefers-color-scheme: dark)" ) ;
316+
317+ mediaQuery . addEventListener ( "change" , onQueryChange ) ;
318+ window . addEventListener ( "storage" , onStorageChange ) ;
319+
320+ return ( ) => {
321+ mediaQuery . removeEventListener ( "change" , onQueryChange ) ;
322+ window . removeEventListener ( "storage" , onStorageChange ) ;
323+ } ;
324+ } , [ ] ) ;
249325
250326 useEffect ( ( ) => {
251327 if ( typeof window === 'undefined' ) {
0 commit comments