Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[material-ui] Add storageManager prop to ThemeProvider #45136

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions packages/mui-material/src/styles/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -47,6 +48,11 @@ export interface ThemeProviderProps<Theme = DefaultTheme> 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'
Expand Down
25 changes: 22 additions & 3 deletions packages/mui-material/src/styles/ThemeProviderWithVars.spec.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -53,7 +60,7 @@ function TestUseTheme() {
return <div style={{ background: theme.vars.palette.common.background }}>test</div>;
}

<CssVarsProvider theme={customTheme}>
<ThemeProvider theme={customTheme}>
<TestStyled
sx={(theme) => ({
// test that `theme` in sx has access to CSS vars
Expand All @@ -63,4 +70,16 @@ function TestUseTheme() {
},
})}
/>
</CssVarsProvider>;
</ThemeProvider>;

<ThemeProvider theme={customTheme} storageManager={null} />;

const storageManager: StorageManager = () => {
return {
get: () => 'light',
set: () => {},
subscribe: () => () => {},
};
};

<ThemeProvider theme={customTheme} storageManager={storageManager} />;
1 change: 1 addition & 0 deletions packages/mui-material/src/styles/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,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';

Expand Down
6 changes: 6 additions & 0 deletions packages/mui-system/src/cssVars/createCssVarsProvider.d.ts
Original file line number Diff line number Diff line change
@@ -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<SupportedColorScheme extends string>
extends Result<SupportedColorScheme> {
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions packages/mui-system/src/cssVars/createCssVarsProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -119,6 +120,7 @@ export default function createCssVarsProvider(options) {
modeStorageKey,
colorSchemeStorageKey,
defaultMode,
storageManager,
storageWindow,
noSsr,
});
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/mui-system/src/cssVars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
82 changes: 82 additions & 0 deletions packages/mui-system/src/cssVars/localStorageManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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
* @example
*/
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 = window }) => {
return {
get(defaultValue) {
if (typeof window === 'undefined') {
return undefined;
}
if (!storageWindow) {
return defaultValue;
}
let value;
try {
value = storageWindow.localStorage.getItem(key) || undefined;
if (!value) {
// the first time that user enters the site.
storageWindow.localStorage.setItem(key, defaultValue);
}
} 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;
Loading