UNPKG

@mui/system

Version:

MUI System is a set of CSS utilities to help you build custom designs more efficiently. It makes it possible to rapidly lay out custom designs.

228 lines (222 loc) 7.88 kB
'use client'; import * as React from 'react'; import { DEFAULT_MODE_STORAGE_KEY, DEFAULT_COLOR_SCHEME_STORAGE_KEY } from "../InitColorSchemeScript/InitColorSchemeScript.js"; import localStorageManager from "./localStorageManager.js"; function noop() {} export function getSystemMode(mode) { if (typeof window !== 'undefined' && typeof window.matchMedia === 'function' && mode === 'system') { const mql = window.matchMedia('(prefers-color-scheme: dark)'); if (mql.matches) { return 'dark'; } return 'light'; } return undefined; } function processState(state, callback) { if (state.mode === 'light' || state.mode === 'system' && state.systemMode === 'light') { return callback('light'); } if (state.mode === 'dark' || state.mode === 'system' && state.systemMode === 'dark') { return callback('dark'); } return undefined; } export function getColorScheme(state) { return processState(state, mode => { if (mode === 'light') { return state.lightColorScheme; } if (mode === 'dark') { return state.darkColorScheme; } return undefined; }); } export default function useCurrentColorScheme(options) { const { defaultMode = 'light', defaultLightColorScheme, defaultDarkColorScheme, supportedColorSchemes = [], modeStorageKey = DEFAULT_MODE_STORAGE_KEY, colorSchemeStorageKey = DEFAULT_COLOR_SCHEME_STORAGE_KEY, storageWindow = typeof window === 'undefined' ? undefined : window, storageManager = localStorageManager, noSsr = false } = options; const joinedColorSchemes = supportedColorSchemes.join(','); const isMultiSchemes = supportedColorSchemes.length > 1; const modeStorage = React.useMemo(() => storageManager?.({ key: modeStorageKey, storageWindow }), [storageManager, modeStorageKey, storageWindow]); const lightStorage = React.useMemo(() => storageManager?.({ key: `${colorSchemeStorageKey}-light`, storageWindow }), [storageManager, colorSchemeStorageKey, storageWindow]); const darkStorage = React.useMemo(() => storageManager?.({ key: `${colorSchemeStorageKey}-dark`, storageWindow }), [storageManager, colorSchemeStorageKey, storageWindow]); const [state, setState] = React.useState(() => { const initialMode = modeStorage?.get(defaultMode) || defaultMode; const lightColorScheme = lightStorage?.get(defaultLightColorScheme) || defaultLightColorScheme; const darkColorScheme = darkStorage?.get(defaultDarkColorScheme) || defaultDarkColorScheme; return { mode: initialMode, systemMode: getSystemMode(initialMode), lightColorScheme, darkColorScheme }; }); const [isClient, setIsClient] = React.useState(noSsr || !isMultiSchemes); React.useEffect(() => { setIsClient(true); // to rerender the component after hydration }, []); const colorScheme = getColorScheme(state); const setMode = React.useCallback(mode => { setState(currentState => { if (mode === currentState.mode) { // do nothing if mode does not change return currentState; } const newMode = mode ?? defaultMode; modeStorage?.set(newMode); return { ...currentState, mode: newMode, systemMode: getSystemMode(newMode) }; }); }, [modeStorage, defaultMode]); const setColorScheme = React.useCallback(value => { if (!value) { setState(currentState => { lightStorage?.set(defaultLightColorScheme); darkStorage?.set(defaultDarkColorScheme); return { ...currentState, lightColorScheme: defaultLightColorScheme, darkColorScheme: defaultDarkColorScheme }; }); } else if (typeof value === 'string') { if (value && !joinedColorSchemes.includes(value)) { console.error(`\`${value}\` does not exist in \`theme.colorSchemes\`.`); } else { setState(currentState => { const newState = { ...currentState }; processState(currentState, mode => { if (mode === 'light') { lightStorage?.set(value); newState.lightColorScheme = value; } if (mode === 'dark') { darkStorage?.set(value); newState.darkColorScheme = value; } }); return newState; }); } } else { setState(currentState => { const newState = { ...currentState }; const newLightColorScheme = value.light === null ? defaultLightColorScheme : value.light; const newDarkColorScheme = value.dark === null ? defaultDarkColorScheme : value.dark; if (newLightColorScheme) { if (!joinedColorSchemes.includes(newLightColorScheme)) { console.error(`\`${newLightColorScheme}\` does not exist in \`theme.colorSchemes\`.`); } else { newState.lightColorScheme = newLightColorScheme; lightStorage?.set(newLightColorScheme); } } if (newDarkColorScheme) { if (!joinedColorSchemes.includes(newDarkColorScheme)) { console.error(`\`${newDarkColorScheme}\` does not exist in \`theme.colorSchemes\`.`); } else { newState.darkColorScheme = newDarkColorScheme; darkStorage?.set(newDarkColorScheme); } } return newState; }); } }, [joinedColorSchemes, lightStorage, darkStorage, defaultLightColorScheme, defaultDarkColorScheme]); const handleMediaQuery = React.useCallback(event => { if (state.mode === 'system') { setState(currentState => { const systemMode = event?.matches ? 'dark' : 'light'; // Early exit, nothing changed. if (currentState.systemMode === systemMode) { return currentState; } return { ...currentState, systemMode }; }); } }, [state.mode]); // Ref hack to avoid adding handleMediaQuery as a dep const mediaListener = React.useRef(handleMediaQuery); mediaListener.current = handleMediaQuery; React.useEffect(() => { if (typeof window.matchMedia !== 'function' || !isMultiSchemes) { return undefined; } const handler = (...args) => mediaListener.current(...args); // Always listen to System preference const media = window.matchMedia('(prefers-color-scheme: dark)'); // Intentionally use deprecated listener methods to support iOS & old browsers media.addListener(handler); handler(media); return () => { media.removeListener(handler); }; }, [isMultiSchemes]); // Handle when localStorage has changed React.useEffect(() => { if (isMultiSchemes) { const unsubscribeMode = modeStorage?.subscribe(value => { if (!value || ['light', 'dark', 'system'].includes(value)) { setMode(value || defaultMode); } }) || noop; const unsubscribeLight = lightStorage?.subscribe(value => { if (!value || joinedColorSchemes.match(value)) { setColorScheme({ light: value }); } }) || noop; const unsubscribeDark = darkStorage?.subscribe(value => { if (!value || joinedColorSchemes.match(value)) { setColorScheme({ dark: value }); } }) || noop; return () => { unsubscribeMode(); unsubscribeLight(); unsubscribeDark(); }; } return undefined; }, [setColorScheme, setMode, joinedColorSchemes, defaultMode, storageWindow, isMultiSchemes, modeStorage, lightStorage, darkStorage]); return { ...state, mode: isClient ? state.mode : undefined, systemMode: isClient ? state.systemMode : undefined, colorScheme: isClient ? colorScheme : undefined, setMode, setColorScheme }; }