diff --git a/docs/src/modules/brandingTheme.ts b/docs/src/modules/brandingTheme.ts index 425888bde3c4f0..0682897ca5c1f0 100644 --- a/docs/src/modules/brandingTheme.ts +++ b/docs/src/modules/brandingTheme.ts @@ -403,21 +403,7 @@ export const getDesignTokens = (mode: 'light' | 'dark') => * ] */ applyDarkStyles(css: Parameters[0]) { - if ((this as Theme).vars) { - // If CssVarsProvider is used as a provider, - // returns ':where([data-mui-color-scheme="light|dark"]) &' - const selector = (this as Theme) - .getColorSchemeSelector('dark') - .replace(/(\[[^\]]+\])/, ':where($1)'); - return { - [selector]: css, - }; - } - if ((this as Theme).palette.mode === 'dark') { - return css; - } - - return undefined; + return (this as Theme).applyStyles('dark', css); }, } as ThemeOptions); diff --git a/packages/mui-joy/src/styles/defaultTheme.test.js b/packages/mui-joy/src/styles/defaultTheme.test.js index 9caf86ae5ed530..f45402d3a1629a 100644 --- a/packages/mui-joy/src/styles/defaultTheme.test.js +++ b/packages/mui-joy/src/styles/defaultTheme.test.js @@ -31,7 +31,7 @@ describe('defaultTheme', () => { 'unstable_sx', 'shouldSkipGeneratingVar', 'generateCssVars', - 'applyDarkStyles', + 'applyStyles', ]).to.includes(field); }); }); diff --git a/packages/mui-joy/src/styles/extendTheme.test.js b/packages/mui-joy/src/styles/extendTheme.test.js index 8e1dd13a4cdf05..d6fe75ac3aa5b3 100644 --- a/packages/mui-joy/src/styles/extendTheme.test.js +++ b/packages/mui-joy/src/styles/extendTheme.test.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { createRenderer } from '@mui-internal/test-utils'; -import { extendTheme, useTheme, CssVarsProvider } from '@mui/joy/styles'; +import { extendTheme, useTheme, CssVarsProvider, styled } from '@mui/joy/styles'; describe('extendTheme', () => { it('the output contains required fields', () => { @@ -31,7 +31,7 @@ describe('extendTheme', () => { 'unstable_sx', 'shouldSkipGeneratingVar', 'generateCssVars', - 'applyDarkStyles', + 'applyStyles', ]).to.includes(field); }); }); @@ -177,5 +177,28 @@ describe('extendTheme', () => { borderRadius: 'var(--joy-radius-md)', }); }); + + it('applyStyles', () => { + const attribute = 'data-custom-color-scheme'; + let darkStyles = {}; + const Test = styled('div')(({ theme }) => { + darkStyles = theme.applyStyles('dark', { + backgroundColor: 'rgba(0, 0, 0, 0)', + }); + return null; + }); + + render( + + + , + ); + + expect(darkStyles).to.deep.equal({ + [`*:where([${attribute}="dark"]) &`]: { + backgroundColor: 'rgba(0, 0, 0, 0)', + }, + }); + }); }); }); diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts index 4c0692664567cd..b1e85193334932 100644 --- a/packages/mui-joy/src/styles/extendTheme.ts +++ b/packages/mui-joy/src/styles/extendTheme.ts @@ -9,8 +9,8 @@ import { unstable_createGetCssVar as systemCreateGetCssVar, unstable_styleFunctionSx as styleFunctionSx, SxConfig, - CSSObject, } from '@mui/system'; +import { unstable_applyStyles as applyStyles } from '@mui/system/createTheme'; import defaultSxConfig from './sxConfig'; import colors from '../colors'; import defaultShouldSkipGeneratingVar from './shouldSkipGeneratingVar'; @@ -565,23 +565,6 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { cssVarPrefix, getCssVar, spacing: createSpacing(spacing), - applyDarkStyles(css: CSSObject) { - if ((this as Theme).vars) { - // If CssVarsProvider is used as a provider, - // returns ':where([data-mui-color-scheme="light|dark"]) &' - const selector = (this as Theme) - .getColorSchemeSelector('dark') - .replace(/(\[[^\]]+\])/, ':where($1)'); - return { - [selector]: css, - }; - } - if ((this as Theme).palette.mode === 'dark') { - return css; - } - - return {}; - }, } as unknown as Theme; // Need type casting due to module augmentation inside the repo /** @@ -680,6 +663,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { }; theme.shouldSkipGeneratingVar = shouldSkipGeneratingVar; + theme.applyStyles = applyStyles; return theme; } diff --git a/packages/mui-joy/src/styles/types/theme.ts b/packages/mui-joy/src/styles/types/theme.ts index 68fdd0c39e7f24..9d94dfc53113d1 100644 --- a/packages/mui-joy/src/styles/types/theme.ts +++ b/packages/mui-joy/src/styles/types/theme.ts @@ -6,6 +6,7 @@ import { SystemProps as SystemSystemProps, CSSObject, SxConfig, + ApplyStyles, } from '@mui/system'; import { DefaultColorScheme, ExtendedColorScheme } from './colorScheme'; import { ColorSystem } from './colorSystem'; @@ -118,7 +119,7 @@ export interface Theme extends ThemeScales, RuntimeColorSystem { shouldSkipGeneratingVar: (keys: string[], value: string | number) => boolean; unstable_sxConfig: SxConfig; unstable_sx: (props: SxProps) => CSSObject; - applyDarkStyles: (css: CSSObject) => CSSObject; + applyStyles: ApplyStyles; } export type SxProps = SystemSxProps; diff --git a/packages/mui-material/src/Avatar/Avatar.js b/packages/mui-material/src/Avatar/Avatar.js index e1635672b37cb7..0c7fbea987d98e 100644 --- a/packages/mui-material/src/Avatar/Avatar.js +++ b/packages/mui-material/src/Avatar/Avatar.js @@ -69,7 +69,7 @@ const AvatarRoot = styled('div', { } : { backgroundColor: theme.palette.grey[400], - ...theme.applyDarkStyles({ backgroundColor: theme.palette.grey[600] }), + ...theme.applyStyles('dark', { backgroundColor: theme.palette.grey[600] }), }), }, }, diff --git a/packages/mui-material/src/styles/createTheme.d.ts b/packages/mui-material/src/styles/createTheme.d.ts index 922db0c76cf64b..ef8cb895b1fb14 100644 --- a/packages/mui-material/src/styles/createTheme.d.ts +++ b/packages/mui-material/src/styles/createTheme.d.ts @@ -45,7 +45,6 @@ export interface Theme extends BaseTheme { components?: Components; unstable_sx: (props: SxProps) => CSSObject; unstable_sxConfig: SxConfig; - applyDarkStyles: (css: CSSObject) => CSSObject; } /** diff --git a/packages/mui-material/src/styles/createTheme.js b/packages/mui-material/src/styles/createTheme.js index dc30e2cee9bf97..39cba1f2731a1f 100644 --- a/packages/mui-material/src/styles/createTheme.js +++ b/packages/mui-material/src/styles/createTheme.js @@ -43,21 +43,6 @@ function createTheme(options = {}, ...args) { typography: createTypography(palette, typographyInput), transitions: createTransitions(transitionsInput), zIndex: { ...zIndex }, - applyDarkStyles(css) { - if (this.vars) { - // If CssVarsProvider is used as a provider, - // returns ':where([data-mui-color-scheme="light|dark"]) &' - const selector = this.getColorSchemeSelector('dark').replace(/(\[[^\]]+\])/, ':where($1)'); - return { - [selector]: css, - }; - } - if (this.palette.mode === 'dark') { - return css; - } - - return {}; - }, }); muiTheme = deepmerge(muiTheme, other); diff --git a/packages/mui-material/src/styles/createTheme.test.js b/packages/mui-material/src/styles/createTheme.test.js index 953c0f70a02725..28db852c08c2fa 100644 --- a/packages/mui-material/src/styles/createTheme.test.js +++ b/packages/mui-material/src/styles/createTheme.test.js @@ -251,7 +251,7 @@ describe('createTheme', () => { }); }); - it('should apply dark styles when using applyDarkStyles if mode="dark"', function test() { + it('should apply dark styles when using applyStyles if mode="dark"', function test() { const darkTheme = createTheme({ palette: { mode: 'dark', @@ -260,7 +260,7 @@ describe('createTheme', () => { const Test = styled('div')(({ theme }) => ({ backgroundColor: 'rgb(255, 255, 255)', - ...theme.applyDarkStyles({ + ...theme.applyStyles('dark', { backgroundColor: 'rgb(0, 0, 0)', }), })); @@ -276,12 +276,12 @@ describe('createTheme', () => { }); }); - it('should apply dark styles when using applyDarkStyles if mode="light"', function test() { + it('should not apply dark styles when using applyStyles if mode="light"', function test() { const lightTheme = createTheme(); const Test = styled('div')(({ theme }) => ({ backgroundColor: 'rgb(255, 255, 255)', - ...theme.applyDarkStyles({ + ...theme.applyStyles('dark', { backgroundColor: 'rgb(0, 0, 0)', }), })); diff --git a/packages/mui-material/src/styles/experimental_extendTheme.d.ts b/packages/mui-material/src/styles/experimental_extendTheme.d.ts index c5315639fd53b9..98a1f97b62c94e 100644 --- a/packages/mui-material/src/styles/experimental_extendTheme.d.ts +++ b/packages/mui-material/src/styles/experimental_extendTheme.d.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { OverridableStringUnion } from '@mui/types'; -import { SxConfig, SxProps, CSSObject } from '@mui/system'; +import { SxConfig, SxProps, CSSObject, ApplyStyles } from '@mui/system'; import { ThemeOptions, Theme } from './createTheme'; import { Palette, PaletteOptions } from './createPalette'; import { Shadows } from './shadows'; @@ -432,6 +432,7 @@ export interface CssVarsTheme extends ColorSystem { shouldSkipGeneratingVar: (keys: string[], value: string | number) => boolean; unstable_sxConfig: SxConfig; unstable_sx: (props: SxProps) => CSSObject; + applyStyles: ApplyStyles; } /** @@ -443,4 +444,4 @@ export interface CssVarsTheme extends ColorSystem { export default function experimental_extendTheme( options?: CssVarsThemeOptions, ...args: object[] -): Omit & CssVarsTheme; +): Omit & CssVarsTheme; diff --git a/packages/mui-material/src/styles/experimental_extendTheme.test.js b/packages/mui-material/src/styles/experimental_extendTheme.test.js index a0642c14681398..dd0e43920b96a1 100644 --- a/packages/mui-material/src/styles/experimental_extendTheme.test.js +++ b/packages/mui-material/src/styles/experimental_extendTheme.test.js @@ -505,25 +505,24 @@ describe('experimental_extendTheme', () => { }); }); - it('should use the right selector with applyDarkStyles', function test() { - const defaultTheme = extendTheme(); + it('should use the right selector with applyStyles', function test() { const attribute = 'data-custom-color-scheme'; let darkStyles = {}; const Test = styled('div')(({ theme }) => { - darkStyles = theme.applyDarkStyles({ + darkStyles = theme.applyStyles('dark', { backgroundColor: 'rgba(0, 0, 0, 0)', }); return null; }); render( - + , ); expect(darkStyles).to.deep.equal({ - [`:where([${attribute}="dark"]) &`]: { + [`*:where([${attribute}="dark"]) &`]: { backgroundColor: 'rgba(0, 0, 0, 0)', }, }); diff --git a/packages/mui-system/src/createTheme/applyStyles.ts b/packages/mui-system/src/createTheme/applyStyles.ts new file mode 100644 index 00000000000000..d0278951b7a822 --- /dev/null +++ b/packages/mui-system/src/createTheme/applyStyles.ts @@ -0,0 +1,85 @@ +import { CSSObject } from '@mui/styled-engine'; + +export interface ApplyStyles { + (key: K, styles: CSSObject): CSSObject; +} + +/** + * A universal utility to style components with multiple color modes. Always use it from the theme object. + * It works with: + * - [Basic theme](https://mui.com/material-ui/customization/dark-mode/) + * - [CSS theme variables](https://mui.com/material-ui/experimental-api/css-theme-variables/overview/) + * - Zero-runtime engine + * + * Tips: Use an array over object spread and place `theme.applyStyles()` last. + * + * ✅ [{ background: '#e5e5e5' }, theme.applyStyles('dark', { background: '#1c1c1c' })] + * + * 🚫 { background: '#e5e5e5', ...theme.applyStyles('dark', { background: '#1c1c1c' })} + * + * @example + * 1. using with `styled`: + * ```jsx + * const Component = styled('div')(({ theme }) => [ + * { background: '#e5e5e5' }, + * theme.applyStyles('dark', { + * background: '#1c1c1c', + * color: '#fff', + * }), + * ]); + * ``` + * + * @example + * 2. using with `sx` prop: + * ```jsx + * [ + * { background: '#e5e5e5' }, + * theme.applyStyles('dark', { + * background: '#1c1c1c', + * color: '#fff', + * }), + * ]} + * /> + * ``` + * + * @example + * 3. theming a component: + * ```jsx + * extendTheme({ + * components: { + * MuiButton: { + * styleOverrides: { + * root: ({ theme }) => [ + * { background: '#e5e5e5' }, + * theme.applyStyles('dark', { + * background: '#1c1c1c', + * color: '#fff', + * }), + * ], + * }, + * } + * } + * }) + *``` + */ +export default function applyStyles(key: K, styles: CSSObject) { + // @ts-expect-error this is 'any' type + const theme = this as { + palette: { mode: 'light' | 'dark' }; + vars?: any; + getColorSchemeSelector?: (scheme: string) => string; + }; + if (theme.vars && typeof theme.getColorSchemeSelector === 'function') { + // If CssVarsProvider is used as a provider, + // returns '* :where([data-mui-color-scheme="light|dark"]) &' + const selector = theme.getColorSchemeSelector(key).replace(/(\[[^\]]+\])/, '*:where($1)'); + return { + [selector]: styles, + }; + } + if (theme.palette.mode === key) { + return styles; + } + + return {}; +} diff --git a/packages/mui-system/src/createTheme/createTheme.d.ts b/packages/mui-system/src/createTheme/createTheme.d.ts index 8de5e3c8d6340e..aee763ec114398 100644 --- a/packages/mui-system/src/createTheme/createTheme.d.ts +++ b/packages/mui-system/src/createTheme/createTheme.d.ts @@ -3,6 +3,7 @@ import { Breakpoints, BreakpointsOptions } from './createBreakpoints'; import { Shape, ShapeOptions } from './shape'; import { Spacing, SpacingOptions } from './createSpacing'; import { SxConfig, SxProps } from '../styleFunctionSx'; +import { ApplyStyles } from './applyStyles'; export { Breakpoint, BreakpointOverrides } from './createBreakpoints'; @@ -35,6 +36,7 @@ export interface Theme { mixins?: unknown; typography?: unknown; zIndex?: unknown; + applyStyles: ApplyStyles<'light' | 'dark'>; unstable_sxConfig: SxConfig; unstable_sx: (props: SxProps) => CSSObject; } diff --git a/packages/mui-system/src/createTheme/createTheme.js b/packages/mui-system/src/createTheme/createTheme.js index 2cb236a1e2ac28..a0727253b00bcf 100644 --- a/packages/mui-system/src/createTheme/createTheme.js +++ b/packages/mui-system/src/createTheme/createTheme.js @@ -4,6 +4,7 @@ import shape from './shape'; import createSpacing from './createSpacing'; import styleFunctionSx from '../styleFunctionSx/styleFunctionSx'; import defaultSxConfig from '../styleFunctionSx/defaultSxConfig'; +import applyStyles from './applyStyles'; function createTheme(options = {}, ...args) { const { @@ -29,6 +30,8 @@ function createTheme(options = {}, ...args) { other, ); + muiTheme.applyStyles = applyStyles; + muiTheme = args.reduce((acc, argument) => deepmerge(acc, argument), muiTheme); muiTheme.unstable_sxConfig = { diff --git a/packages/mui-system/src/createTheme/createTheme.test.js b/packages/mui-system/src/createTheme/createTheme.test.js index 9393292b1eb666..3698f1e0c0e9d0 100644 --- a/packages/mui-system/src/createTheme/createTheme.test.js +++ b/packages/mui-system/src/createTheme/createTheme.test.js @@ -152,6 +152,52 @@ describe('createTheme', () => { maxWidth: '600px', }); }); + + it('apply correct styles', () => { + const darkTheme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: 'rgb(0, 0, 255)', + }, + secondary: { + main: 'rgb(0, 255, 0)', + }, + }, + }); + + expect(darkTheme.applyStyles('dark', { color: 'red' })).to.deep.equal({ + color: 'red', + }); + expect(darkTheme.applyStyles('light', { color: 'salmon' })).to.deep.equal({}); + + // assume switching to light theme + darkTheme.palette.mode = 'light'; + expect(darkTheme.applyStyles('dark', { color: 'red' })).to.deep.equal({}); + expect(darkTheme.applyStyles('light', { color: 'salmon' })).to.deep.equal({ + color: 'salmon', + }); + }); + + it('apply correct styles with new theme', () => { + const darkTheme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: 'rgb(0, 0, 255)', + }, + secondary: { + main: 'rgb(0, 255, 0)', + }, + }, + }); + + const newTheme = { ...darkTheme, palette: { mode: 'light' } }; + expect(newTheme.applyStyles('dark', { color: 'red' })).to.deep.equal({}); + expect(newTheme.applyStyles('light', { color: 'salmon' })).to.deep.equal({ + color: 'salmon', + }); + }); }); it('does not throw if used without ThemeProvider', function test() { diff --git a/packages/mui-system/src/createTheme/index.d.ts b/packages/mui-system/src/createTheme/index.d.ts index def750883a1b37..fe33e5ab4d0224 100644 --- a/packages/mui-system/src/createTheme/index.d.ts +++ b/packages/mui-system/src/createTheme/index.d.ts @@ -1,2 +1,4 @@ export { default } from './createTheme'; export * from './createTheme'; +export { default as unstable_applyStyles } from './applyStyles'; +export * from './applyStyles'; diff --git a/packages/mui-system/src/createTheme/index.js b/packages/mui-system/src/createTheme/index.js index 5684efceff7316..069e60c60e5b13 100644 --- a/packages/mui-system/src/createTheme/index.js +++ b/packages/mui-system/src/createTheme/index.js @@ -1,2 +1,3 @@ export { default } from './createTheme'; export { default as private_createBreakpoints } from './createBreakpoints'; +export { default as unstable_applyStyles } from './applyStyles';