diff --git a/docs/data/material/customization/dark-mode/dark-mode.md b/docs/data/material/customization/dark-mode/dark-mode.md
index 248750d01716ad..ce0eb9e7f793b0 100644
--- a/docs/data/material/customization/dark-mode/dark-mode.md
+++ b/docs/data/material/customization/dark-mode/dark-mode.md
@@ -122,6 +122,78 @@ The `mode` is always `undefined` on first render, so make sure to handle this ca
{{"demo": "ToggleColorMode.js", "defaultCodeOpen": false}}
+## Storage manager
+
+By default, the [built-in support](#built-in-support) for color schemes uses the browser's `localStorage` API to store the user's mode and scheme preference.
+
+To use a different storage manager, create a custom function with this signature:
+
+```ts
+type Unsubscribe = () => void;
+
+function storageManager(params: { key: string }): {
+ get: (defaultValue: any) => any;
+ set: (value: any) => void;
+ subscribe: (handler: (value: any) => void) => Unsubscribe;
+};
+```
+
+Then pass it to the `storageManager` prop of the `ThemeProvider` component:
+
+```tsx
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+import type { StorageManager } from '@mui/material/styles';
+
+const theme = createTheme({
+ colorSchemes: {
+ dark: true,
+ },
+});
+
+function storageManager(params): StorageManager {
+ return {
+ get: (defaultValue) => {
+ // Your implementation
+ },
+ set: (value) => {
+ // Your implementation
+ },
+ subscribe: (handler) => {
+ // Your implementation
+ return () => {
+ // cleanup
+ };
+ },
+ };
+}
+
+function App() {
+ return (
+
+ ...
+
+ );
+}
+```
+
+:::warning
+If you are using the `InitColorSchemeScript` component to [prevent SSR flickering](/material-ui/customization/css-theme-variables/configuration/#preventing-ssr-flickering), you have to include the `localStorage` implementation in your custom storage manager.
+:::
+
+### Disable storage
+
+To disable the storage manager, pass `null` to the `storageManager` prop:
+
+```tsx
+
+ ...
+
+```
+
+:::warning
+Disabling the storage manager will cause the app to reset to its default mode whenever the user refreshes the page.
+:::
+
## Disable transitions
To instantly switch between color schemes with no transition, apply the `disableTransitionOnChange` prop to the `ThemeProvider` component:
diff --git a/packages/mui-material/src/styles/ThemeProvider.test.tsx b/packages/mui-material/src/styles/ThemeProvider.test.tsx
index b5828595030b49..33a77b7e3c2642 100644
--- a/packages/mui-material/src/styles/ThemeProvider.test.tsx
+++ b/packages/mui-material/src/styles/ThemeProvider.test.tsx
@@ -12,7 +12,7 @@ describe('ThemeProvider', () => {
originalMatchmedia = window.matchMedia;
// Create mocks of localStorage getItem and setItem functions
storage = {};
- Object.defineProperty(global, 'localStorage', {
+ Object.defineProperty(window, 'localStorage', {
value: {
getItem: (key: string) => storage[key],
setItem: (key: string, value: string) => {
diff --git a/packages/mui-material/src/styles/ThemeProvider.tsx b/packages/mui-material/src/styles/ThemeProvider.tsx
index e9e0dc5fb94c82..25a7f013f43308 100644
--- a/packages/mui-material/src/styles/ThemeProvider.tsx
+++ b/packages/mui-material/src/styles/ThemeProvider.tsx
@@ -1,6 +1,7 @@
'use client';
import * as React from 'react';
import { DefaultTheme } from '@mui/system';
+import { StorageManager } from '@mui/system/cssVars';
import ThemeProviderNoVars from './ThemeProviderNoVars';
import { CssThemeVariables } from './createThemeNoVars';
import { CssVarsProvider } from './ThemeProviderWithVars';
@@ -47,6 +48,11 @@ export interface ThemeProviderProps extends ThemeProviderC
* @default window
*/
storageWindow?: Window | null;
+ /**
+ * The storage manager to be used for storing the mode and color scheme
+ * @default using `window.localStorage`
+ */
+ storageManager?: StorageManager | null;
/**
* localStorage key used to store application `mode`
* @default 'mui-mode'
diff --git a/packages/mui-material/src/styles/ThemeProviderWithVars.spec.tsx b/packages/mui-material/src/styles/ThemeProviderWithVars.spec.tsx
index ebd779e2085610..0f879463a8c0ca 100644
--- a/packages/mui-material/src/styles/ThemeProviderWithVars.spec.tsx
+++ b/packages/mui-material/src/styles/ThemeProviderWithVars.spec.tsx
@@ -1,5 +1,12 @@
import * as React from 'react';
-import { extendTheme, CssVarsProvider, styled, useTheme, Overlays } from '@mui/material/styles';
+import {
+ extendTheme,
+ ThemeProvider,
+ styled,
+ useTheme,
+ Overlays,
+ StorageManager,
+} from '@mui/material/styles';
import type {} from '@mui/material/themeCssVarsAugmentation';
const customTheme = extendTheme({
@@ -53,7 +60,7 @@ function TestUseTheme() {
return test
;
}
-
+
({
// test that `theme` in sx has access to CSS vars
@@ -63,4 +70,16 @@ function TestUseTheme() {
},
})}
/>
-;
+;
+
+;
+
+const storageManager: StorageManager = () => {
+ return {
+ get: () => 'light',
+ set: () => {},
+ subscribe: () => () => {},
+ };
+};
+
+;
diff --git a/packages/mui-material/src/styles/index.d.ts b/packages/mui-material/src/styles/index.d.ts
index 362e18e8759271..672e5d3342e8c5 100644
--- a/packages/mui-material/src/styles/index.d.ts
+++ b/packages/mui-material/src/styles/index.d.ts
@@ -98,6 +98,7 @@ export { default as withStyles } from './withStyles';
export { default as withTheme } from './withTheme';
export * from './ThemeProviderWithVars';
+export type { StorageManager } from '@mui/system/cssVars';
export { default as extendTheme } from './createThemeWithVars';
diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts b/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts
index 5dee405542060e..0b78935613dce0 100644
--- a/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts
+++ b/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts
@@ -1,6 +1,7 @@
import * as React from 'react';
import InitColorSchemeScript from '../InitColorSchemeScript';
import { Result } from './useCurrentColorScheme';
+import type { StorageManager } from './localStorageManager';
export interface ColorSchemeContextValue
extends Result {
@@ -70,6 +71,11 @@ export interface CreateCssVarsProviderResult<
* @default document
*/
colorSchemeNode?: Element | null;
+ /**
+ * The storage manager to be used for storing the mode and color scheme.
+ * @default using `window.localStorage`
+ */
+ storageManager?: StorageManager | null;
/**
* The window that attaches the 'storage' event listener
* @default window
diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.js b/packages/mui-system/src/cssVars/createCssVarsProvider.js
index 3946722bd4574d..fa9246284c414d 100644
--- a/packages/mui-system/src/cssVars/createCssVarsProvider.js
+++ b/packages/mui-system/src/cssVars/createCssVarsProvider.js
@@ -60,6 +60,7 @@ export default function createCssVarsProvider(options) {
modeStorageKey = defaultModeStorageKey,
colorSchemeStorageKey = defaultColorSchemeStorageKey,
disableTransitionOnChange = designSystemTransitionOnChange,
+ storageManager,
storageWindow = typeof window === 'undefined' ? undefined : window,
documentNode = typeof document === 'undefined' ? undefined : document,
colorSchemeNode = typeof document === 'undefined' ? undefined : document.documentElement,
@@ -119,6 +120,7 @@ export default function createCssVarsProvider(options) {
modeStorageKey,
colorSchemeStorageKey,
defaultMode,
+ storageManager,
storageWindow,
noSsr,
});
@@ -357,6 +359,11 @@ export default function createCssVarsProvider(options) {
* You should use this option in conjuction with `InitColorSchemeScript` component.
*/
noSsr: PropTypes.bool,
+ /**
+ * The storage manager to be used for storing the mode and color scheme
+ * @default using `window.localStorage`
+ */
+ storageManager: PropTypes.func,
/**
* The window that attaches the 'storage' event listener.
* @default window
diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.test.js b/packages/mui-system/src/cssVars/createCssVarsProvider.test.js
index 4accde568ba8ca..9c699b7f1ff7ee 100644
--- a/packages/mui-system/src/cssVars/createCssVarsProvider.test.js
+++ b/packages/mui-system/src/cssVars/createCssVarsProvider.test.js
@@ -28,7 +28,7 @@ describe('createCssVarsProvider', () => {
originalMatchmedia = window.matchMedia;
// Create mocks of localStorage getItem and setItem functions
- Object.defineProperty(global, 'localStorage', {
+ Object.defineProperty(window, 'localStorage', {
value: {
getItem: spy((key) => storage[key]),
setItem: spy((key, value) => {
@@ -584,13 +584,9 @@ describe('createCssVarsProvider', () => {
,
);
- expect(global.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'system')).to.equal(
- true,
- );
-
fireEvent.click(screen.getByRole('button', { name: 'change to dark' }));
- expect(global.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark')).to.equal(
+ expect(window.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark')).to.equal(
true,
);
});
diff --git a/packages/mui-system/src/cssVars/index.ts b/packages/mui-system/src/cssVars/index.ts
index e20a5bec491de3..63df8b222a9817 100644
--- a/packages/mui-system/src/cssVars/index.ts
+++ b/packages/mui-system/src/cssVars/index.ts
@@ -10,3 +10,4 @@ export { default as prepareTypographyVars } from './prepareTypographyVars';
export type { ExtractTypographyTokens } from './prepareTypographyVars';
export { default as createCssVarsTheme } from './createCssVarsTheme';
export { createGetColorSchemeSelector } from './getColorSchemeSelector';
+export type { StorageManager } from './localStorageManager';
diff --git a/packages/mui-system/src/cssVars/localStorageManager.ts b/packages/mui-system/src/cssVars/localStorageManager.ts
new file mode 100644
index 00000000000000..c1e793c53179f8
--- /dev/null
+++ b/packages/mui-system/src/cssVars/localStorageManager.ts
@@ -0,0 +1,80 @@
+export interface StorageManager {
+ (options: { key: string; storageWindow?: Window | null }): {
+ /**
+ * Function to get the value from the storage
+ * @param defaultValue The default value to be returned if the key is not found
+ * @returns The value from the storage or the default value
+ */
+ get(defaultValue: any): any;
+ /**
+ * Function to set the value in the storage
+ * @param value The value to be set
+ * @returns void
+ */
+ set(value: any): void;
+ /**
+ * Function to subscribe to the value of the specified key triggered by external events
+ * @param handler The function to be called when the value changes
+ * @returns A function to unsubscribe the handler
+ * @example
+ * React.useEffect(() => {
+ * const unsubscribe = storageManager.subscribe((value) => {
+ * console.log(value);
+ * });
+ * return unsubscribe;
+ * }, []);
+ */
+ subscribe(handler: (value: any) => void): () => void;
+ };
+}
+
+function noop() {}
+
+const localStorageManager: StorageManager = ({ key, storageWindow }) => {
+ if (!storageWindow && typeof window !== 'undefined') {
+ storageWindow = window;
+ }
+ return {
+ get(defaultValue) {
+ if (typeof window === 'undefined') {
+ return undefined;
+ }
+ if (!storageWindow) {
+ return defaultValue;
+ }
+ let value;
+ try {
+ value = storageWindow.localStorage.getItem(key);
+ } catch {
+ // Unsupported
+ }
+ return value || defaultValue;
+ },
+ set: (value) => {
+ if (storageWindow) {
+ try {
+ storageWindow.localStorage.setItem(key, value);
+ } catch {
+ // Unsupported
+ }
+ }
+ },
+ subscribe: (handler) => {
+ if (!storageWindow) {
+ return noop;
+ }
+ const listener = (event: StorageEvent) => {
+ const value = event.newValue;
+ if (event.key === key) {
+ handler(value);
+ }
+ };
+ storageWindow.addEventListener('storage', listener);
+ return () => {
+ storageWindow.removeEventListener('storage', listener);
+ };
+ },
+ };
+};
+
+export default localStorageManager;
diff --git a/packages/mui-system/src/cssVars/useCurrentColorScheme.test.js b/packages/mui-system/src/cssVars/useCurrentColorScheme.test.js
index 7143167618502f..a6846765e0babb 100644
--- a/packages/mui-system/src/cssVars/useCurrentColorScheme.test.js
+++ b/packages/mui-system/src/cssVars/useCurrentColorScheme.test.js
@@ -13,7 +13,7 @@ describe('useCurrentColorScheme', () => {
let originalMatchmedia;
let originalAddEventListener;
let storage = {};
- let storageHandler = {};
+ const eventHandlers = new Map();
let trigger;
const createMatchMedia = (matches) => () => ({
@@ -32,7 +32,18 @@ describe('useCurrentColorScheme', () => {
before(() => {
originalAddEventListener = window.addEventListener;
window.addEventListener = (key, handler) => {
- storageHandler[key] = handler;
+ if (eventHandlers.has(key)) {
+ eventHandlers.get(key).listeners.push(handler);
+ } else {
+ eventHandlers.set(key, {
+ listeners: [handler],
+ broadcastEvent(event) {
+ this.listeners.forEach((listener) => {
+ listener(event);
+ });
+ },
+ });
+ }
};
});
@@ -45,7 +56,7 @@ describe('useCurrentColorScheme', () => {
// clear the localstorage
storage = {};
// Create mocks of localStorage getItem and setItem functions
- Object.defineProperty(global, 'localStorage', {
+ Object.defineProperty(window, 'localStorage', {
value: {
getItem: spy((key) => storage[key]),
setItem: spy((key, value) => {
@@ -55,7 +66,6 @@ describe('useCurrentColorScheme', () => {
configurable: true,
});
- storageHandler = {};
window.matchMedia = createMatchMedia(false);
});
@@ -550,26 +560,10 @@ describe('useCurrentColorScheme', () => {
fireEvent.click(container.firstChild);
expect(
- global.localStorage.setItem.lastCall.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark'),
+ window.localStorage.setItem.lastCall.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark'),
).to.equal(true);
});
- it('save system mode', () => {
- function Data() {
- useCurrentColorScheme({
- defaultMode: 'system',
- defaultLightColorScheme: 'light',
- defaultDarkColorScheme: 'dark',
- supportedColorSchemes: ['light', 'dark'],
- });
- return null;
- }
- render();
- expect(global.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'system')).to.equal(
- true,
- );
- });
-
it('save lightColorScheme and darkColorScheme', () => {
function Data() {
const { setMode, setColorScheme, ...data } = useCurrentColorScheme({
@@ -593,11 +587,11 @@ describe('useCurrentColorScheme', () => {
fireEvent.click(container.firstChild);
- expect(global.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark')).to.equal(
+ expect(window.localStorage.setItem.calledWith(DEFAULT_MODE_STORAGE_KEY, 'dark')).to.equal(
true,
);
expect(
- global.localStorage.setItem.calledWith(`${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-dark`, 'dim'),
+ window.localStorage.setItem.calledWith(`${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-dark`, 'dim'),
).to.equal(true);
});
@@ -654,7 +648,9 @@ describe('useCurrentColorScheme', () => {
const { container } = render();
act(() => {
- storageHandler.storage?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: 'dark' });
+ eventHandlers
+ .get('storage')
+ .broadcastEvent?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: 'dark' });
});
expect(JSON.parse(container.firstChild.textContent)).to.deep.equal({
@@ -678,7 +674,9 @@ describe('useCurrentColorScheme', () => {
const { container } = render();
act(() => {
- storageHandler.storage?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: 'system' });
+ eventHandlers
+ .get('storage')
+ .broadcastEvent?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: 'system' });
});
expect(JSON.parse(container.firstChild.textContent)).to.deep.equal({
@@ -704,7 +702,9 @@ describe('useCurrentColorScheme', () => {
const { container } = render();
act(() => {
- storageHandler.storage?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: null });
+ eventHandlers
+ .get('storage')
+ .broadcastEvent?.({ key: DEFAULT_MODE_STORAGE_KEY, newValue: null });
});
expect(JSON.parse(container.firstChild.textContent)).to.deep.equal({
@@ -729,7 +729,7 @@ describe('useCurrentColorScheme', () => {
const { container } = render();
act(() => {
- storageHandler.storage?.({
+ eventHandlers.get('storage').broadcastEvent?.({
key: `${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-light`,
newValue: 'light-dim',
});
@@ -744,7 +744,7 @@ describe('useCurrentColorScheme', () => {
});
act(() => {
- storageHandler.storage?.({
+ eventHandlers.get('storage').broadcastEvent?.({
key: `${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-dark`,
newValue: 'dark-dim',
});
@@ -785,7 +785,7 @@ describe('useCurrentColorScheme', () => {
fireEvent.click(screen.getByTestId('reset'));
expect(
- global.localStorage.setItem.lastCall.calledWith(DEFAULT_MODE_STORAGE_KEY, 'system'),
+ window.localStorage.setItem.lastCall.calledWith(DEFAULT_MODE_STORAGE_KEY, 'system'),
).to.equal(true);
});
@@ -808,20 +808,133 @@ describe('useCurrentColorScheme', () => {
fireEvent.click(screen.getByTestId('dark'));
- global.localStorage.setItem.resetHistory();
- expect(global.localStorage.setItem.callCount).to.equal(0); // reset the calls to neglect initial setItem in the assertion below
+ window.localStorage.setItem.resetHistory();
+ expect(window.localStorage.setItem.callCount).to.equal(0); // reset the calls to neglect initial setItem in the assertion below
fireEvent.click(screen.getByTestId('reset'));
expect(
- global.localStorage.setItem.calledWith(
+ window.localStorage.setItem.calledWith(
`${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-light`,
'light',
),
).to.equal(true);
expect(
- global.localStorage.setItem.calledWith(`${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-dark`, 'dark'),
+ window.localStorage.setItem.calledWith(`${DEFAULT_COLOR_SCHEME_STORAGE_KEY}-dark`, 'dark'),
).to.equal(true);
});
});
+
+ describe('Custom storage', () => {
+ let cache = {};
+
+ beforeEach(() => {
+ cache = {};
+ });
+
+ const storageManager = ({ key }) => ({
+ get(defaultValue) {
+ return cache[key] || defaultValue;
+ },
+ set(value) {
+ cache[key] = value;
+ },
+ subscribe: (handler) => {
+ const listener = (event) => {
+ const value = event.newValue;
+ if (event.key === key) {
+ handler(value);
+ }
+ };
+ window.addEventListener('storage', listener);
+ return () => {
+ window.removeEventListener('storage', listener);
+ };
+ },
+ });
+
+ it('use custom storage', () => {
+ function Data() {
+ const { setMode, ...data } = useCurrentColorScheme({
+ storageManager,
+ defaultMode: 'light',
+ defaultLightColorScheme: 'light',
+ defaultDarkColorScheme: 'dark',
+ supportedColorSchemes: ['light', 'dark'],
+ });
+ return (
+
+ );
+ }
+ const { container } = render();
+
+ fireEvent.click(container.firstChild);
+
+ expect(storageManager({ key: DEFAULT_MODE_STORAGE_KEY }).get()).to.equal('dark');
+ });
+
+ it('handle subscription', () => {
+ function Data() {
+ const { setMode, ...data } = useCurrentColorScheme({
+ storageManager,
+ defaultMode: 'light',
+ defaultLightColorScheme: 'light',
+ defaultDarkColorScheme: 'dark',
+ supportedColorSchemes: ['light', 'dark'],
+ });
+ return (
+
+ );
+ }
+ const { container } = render();
+
+ act(() => {
+ eventHandlers.get('storage').broadcastEvent?.({
+ key: DEFAULT_MODE_STORAGE_KEY,
+ newValue: 'dark',
+ });
+ });
+
+ expect(JSON.parse(container.firstChild.textContent)).to.deep.equal({
+ mode: 'dark',
+ lightColorScheme: 'light',
+ darkColorScheme: 'dark',
+ colorScheme: 'dark',
+ });
+ });
+
+ it('able to disable storage manager', () => {
+ function Data() {
+ const { setMode, ...data } = useCurrentColorScheme({
+ storageManager: null,
+ defaultMode: 'light',
+ defaultLightColorScheme: 'light',
+ defaultDarkColorScheme: 'dark',
+ supportedColorSchemes: ['light', 'dark'],
+ });
+ return (
+
+ );
+ }
+ expect(() => render()).not.to.throw();
+ });
+ });
});
diff --git a/packages/mui-system/src/cssVars/useCurrentColorScheme.ts b/packages/mui-system/src/cssVars/useCurrentColorScheme.ts
index f504172a6e2807..4a214cd092c43f 100644
--- a/packages/mui-system/src/cssVars/useCurrentColorScheme.ts
+++ b/packages/mui-system/src/cssVars/useCurrentColorScheme.ts
@@ -4,6 +4,8 @@ import {
DEFAULT_MODE_STORAGE_KEY,
DEFAULT_COLOR_SCHEME_STORAGE_KEY,
} from '../InitColorSchemeScript/InitColorSchemeScript';
+import type { StorageManager } from './localStorageManager';
+import localStorageManager from './localStorageManager';
export type Mode = 'light' | 'dark' | 'system';
export type SystemMode = Exclude;
@@ -53,6 +55,8 @@ export type Result = State void;
};
+function noop() {}
+
export function getSystemMode(mode: undefined | string): SystemMode | undefined {
if (
typeof window !== 'undefined' &&
@@ -95,23 +99,6 @@ export function getColorScheme(
});
}
-function initializeValue(key: string, defaultValue: string) {
- if (typeof window === 'undefined') {
- return undefined;
- }
- let value;
- try {
- value = localStorage.getItem(key) || undefined;
- if (!value) {
- // the first time that user enters the site.
- localStorage.setItem(key, defaultValue);
- }
- } catch {
- // Unsupported
- }
- return value || defaultValue;
-}
-
interface UseCurrentColoSchemeOptions {
defaultLightColorScheme: SupportedColorScheme;
defaultDarkColorScheme: SupportedColorScheme;
@@ -120,6 +107,7 @@ interface UseCurrentColoSchemeOptions {
modeStorageKey?: string;
colorSchemeStorageKey?: string;
storageWindow?: Window | null;
+ storageManager?: StorageManager | null;
noSsr?: boolean;
}
@@ -134,22 +122,29 @@ export default function useCurrentColorScheme 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 = initializeValue(modeStorageKey, defaultMode);
- const lightColorScheme = initializeValue(
- `${colorSchemeStorageKey}-light`,
- defaultLightColorScheme,
- );
- const darkColorScheme = initializeValue(
- `${colorSchemeStorageKey}-dark`,
- defaultDarkColorScheme,
- );
+ const initialMode = modeStorage?.get(defaultMode) || defaultMode;
+ const lightColorScheme = lightStorage?.get(defaultLightColorScheme) || defaultLightColorScheme;
+ const darkColorScheme = darkStorage?.get(defaultDarkColorScheme) || defaultDarkColorScheme;
return {
mode: initialMode,
systemMode: getSystemMode(initialMode),
@@ -172,11 +167,7 @@ export default function useCurrentColorScheme['setColorScheme'] = React.useCallback(
(value) => {
if (!value) {
setState((currentState) => {
- try {
- localStorage.setItem(`${colorSchemeStorageKey}-light`, defaultLightColorScheme);
- localStorage.setItem(`${colorSchemeStorageKey}-dark`, defaultDarkColorScheme);
- } catch {
- // Unsupported
- }
+ lightStorage?.set(defaultLightColorScheme);
+ darkStorage?.set(defaultDarkColorScheme);
return {
...currentState,
lightColorScheme: defaultLightColorScheme,
@@ -210,15 +197,12 @@ export default function useCurrentColorScheme {
const newState = { ...currentState };
processState(currentState, (mode) => {
- try {
- localStorage.setItem(`${colorSchemeStorageKey}-${mode}`, value);
- } catch {
- // Unsupported
- }
if (mode === 'light') {
+ lightStorage?.set(value);
newState.lightColorScheme = value;
}
if (mode === 'dark') {
+ darkStorage?.set(value);
newState.darkColorScheme = value;
}
});
@@ -236,11 +220,7 @@ export default function useCurrentColorScheme {
- if (storageWindow && isMultiSchemes) {
- const handleStorage = (event: StorageEvent) => {
- const value = event.newValue;
- if (
- typeof event.key === 'string' &&
- event.key.startsWith(colorSchemeStorageKey) &&
- (!value || joinedColorSchemes.match(value))
- ) {
- // If the key is deleted, value will be null then reset color scheme to the default one.
- if (event.key.endsWith('light')) {
+ if (isMultiSchemes) {
+ const unsubscribeMode =
+ modeStorage?.subscribe((value: Mode) => {
+ if (!value || ['light', 'dark', 'system'].includes(value)) {
+ setMode((value as Mode) || defaultMode);
+ }
+ }) || noop;
+ const unsubscribeLight =
+ lightStorage?.subscribe((value: SupportedColorScheme) => {
+ if (!value || joinedColorSchemes.match(value)) {
setColorScheme({ light: value as SupportedColorScheme | null });
}
- if (event.key.endsWith('dark')) {
+ }) || noop;
+ const unsubscribeDark =
+ darkStorage?.subscribe((value: SupportedColorScheme) => {
+ if (!value || joinedColorSchemes.match(value)) {
setColorScheme({ dark: value as SupportedColorScheme | null });
}
- }
- if (
- event.key === modeStorageKey &&
- (!value || ['light', 'dark', 'system'].includes(value))
- ) {
- setMode((value as Mode) || defaultMode);
- }
- };
- // For syncing color-scheme changes between iframes
- storageWindow.addEventListener('storage', handleStorage);
+ }) || noop;
return () => {
- storageWindow.removeEventListener('storage', handleStorage);
+ unsubscribeMode();
+ unsubscribeLight();
+ unsubscribeDark();
};
}
return undefined;
}, [
setColorScheme,
setMode,
- modeStorageKey,
- colorSchemeStorageKey,
joinedColorSchemes,
defaultMode,
storageWindow,
isMultiSchemes,
+ modeStorage,
+ lightStorage,
+ darkStorage,
]);
return {