From 9bb0c35ffb9575e10a5839f551ca65c8d6573d5e Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Tue, 11 Oct 2022 13:28:10 +0700 Subject: [PATCH] [system] Fix color-scheme implementation (#34639) --- .../components/css-baseline/css-baseline.md | 167 ++++++++++++++++++ docs/data/joy/pages.ts | 5 + docs/pages/joy-ui/react-css-baseline.js | 7 + .../mui-joy/src/CssBaseline/CssBaseline.tsx | 85 +++++++++ .../src/CssBaseline/CssBaselineProps.ts | 14 ++ packages/mui-joy/src/CssBaseline/index.ts | 2 + packages/mui-joy/src/GlobalStyles/index.ts | 1 + .../ScopedCssBaseline.test.tsx | 20 +++ .../ScopedCssBaseline/ScopedCssBaseline.tsx | 127 +++++++++++++ .../ScopedCssBaselineProps.ts | 33 ++++ .../mui-joy/src/ScopedCssBaseline/index.ts | 3 + .../scopedCssBaselineClasses.ts | 16 ++ packages/mui-joy/src/index.ts | 8 + .../mui-joy/src/styles/CssVarsProvider.tsx | 1 + packages/mui-joy/src/styles/components.d.ts | 9 + .../mui-joy/src/styles/defaultTheme.test.js | 1 + packages/mui-joy/src/styles/defaultTheme.ts | 1 + .../mui-joy/src/styles/extendTheme.spec.ts | 14 ++ packages/mui-joy/src/styles/extendTheme.ts | 2 + .../mui-joy/src/styles/types/colorSystem.ts | 1 + packages/mui-joy/src/styles/types/theme.ts | 6 +- .../src/CssBaseline/CssBaseline.js | 12 +- .../ScopedCssBaseline/ScopedCssBaseline.js | 9 + .../src/styles/experimental_extendTheme.d.ts | 2 +- .../src/cssVars/createCssVarsProvider.d.ts | 5 - .../src/cssVars/createCssVarsProvider.js | 90 +++++----- .../src/cssVars/createCssVarsProvider.test.js | 109 ------------ .../src/cssVars/getInitColorSchemeScript.tsx | 9 - .../fixtures/CssBaseline/JoyCssBaseline.js | 25 +++ .../CssBaseline/JoyScopedCssBaseline.js | 32 ++++ .../CssBaseline/MaterialCssBaseline.js | 25 +++ .../CssBaseline/MaterialScopedCssBaseline.js | 41 +++++ 32 files changed, 710 insertions(+), 172 deletions(-) create mode 100644 docs/data/joy/components/css-baseline/css-baseline.md create mode 100644 docs/pages/joy-ui/react-css-baseline.js create mode 100644 packages/mui-joy/src/CssBaseline/CssBaseline.tsx create mode 100644 packages/mui-joy/src/CssBaseline/CssBaselineProps.ts create mode 100644 packages/mui-joy/src/CssBaseline/index.ts create mode 100644 packages/mui-joy/src/GlobalStyles/index.ts create mode 100644 packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.test.tsx create mode 100644 packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx create mode 100644 packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaselineProps.ts create mode 100644 packages/mui-joy/src/ScopedCssBaseline/index.ts create mode 100644 packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts create mode 100644 test/regressions/fixtures/CssBaseline/JoyCssBaseline.js create mode 100644 test/regressions/fixtures/CssBaseline/JoyScopedCssBaseline.js create mode 100644 test/regressions/fixtures/CssBaseline/MaterialCssBaseline.js create mode 100644 test/regressions/fixtures/CssBaseline/MaterialScopedCssBaseline.js diff --git a/docs/data/joy/components/css-baseline/css-baseline.md b/docs/data/joy/components/css-baseline/css-baseline.md new file mode 100644 index 00000000000000..21832fa444dfa6 --- /dev/null +++ b/docs/data/joy/components/css-baseline/css-baseline.md @@ -0,0 +1,167 @@ +--- +product: joy-ui +githubLabel: 'component: CssBaseline' +--- + +# CSS Baseline + +

Joy UI provides a CssBaseline component to kickstart an elegant, consistent, and simple baseline to build upon.

+ +{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} + +## Global reset + +You might be familiar with [normalize.css](https://github.com/necolas/normalize.css), a collection of HTML element and attribute style-normalizations. + +```jsx +import * as React from 'react'; +import { CssVarsProvider } from '@mui/joy/styles'; +import CssBaseline from '@mui/joy/CssBaseline'; + +export default function MyApp() { + return ( + + {/* must be used under CssVarsProvider */} + + + {/* The rest of your application */} + + ); +} +``` + +## Scoping on children + +However, you might be progressively migrating a website to MUI, using a global reset might not be an option. +It's possible to apply the baseline only to the children by using the `ScopedCssBaseline` component. + +```jsx +import * as React from 'react'; +import { CssVarsProvider } from '@mui/joy/styles'; +import ScopedCssBaseline from '@mui/joy/ScopedCssBaseline'; +import MyApp from './MyApp'; + +export default function MyApp() { + const [root, setRoot] = React.useState(null); + return ( + {/* target the node to ScopedCssBaseline's div */} + + {/* must be used under CssVarsProvider */} + setRoot(element)}> + {/* The rest of your application */} + + + + ); +} +``` + +⚠️ Make sure you import `ScopedCssBaseline` first to avoid box-sizing conflicts as in the above example. + +## Approach + +### Page + +The `` and `` elements are updated to provide better page-wide defaults. More specifically: + +- The margin in all browsers is removed. +- The default Material Design background color is applied. + It's using `theme.palette.background.body` for standard devices and a white background for print devices. +- The CSS [`color-scheme`](https://web.dev/color-scheme/) is applied by default. You can disable it by setting `disableColorScheme` to true on the `CssBaseline` or `ScopedCssBaseline`. + +### Layout + +- `box-sizing` is set globally on the `` element to `border-box`. + Every element—including `*::before` and `*::after` are declared to inherit this property, which ensures that the declared width of the element is never exceeded due to padding or border. + +### Color scheme + +The CSS [`color-scheme`](https://web.dev/color-scheme/) is applied by default to render proper built-in components on the web. You can disable it by setting `disableColorScheme` to true on the `CssBaseline` or `ScopedCssBaseline`. + +```jsx + + + + +// or + + + {/* The rest of your application */} + + +``` + +### Typography + +- No base font-size is declared on the ``, but 16px is assumed (the browser default). + You can learn more about the implications of changing the `` default font size in [the theme documentation](/material-ui/customization/typography/#html-font-size) page. +- Set the `theme.typography.body1` style on the `` element. +- Set the font-weight to `bold` for the `` and `` elements. +- Custom font-smoothing is enabled for better display of the default font. + +## Customization + +### CssBaseline + +To custom the styles produced by the `CssBaseline` component, append the `GlobalStyles` next to it: + +```js +import { CssVarsProvider } from '@mui/joy/styles'; +import CssBaseline from '@mui/joy/CssBaseline'; +import GlobalStyles from '@mui/joy/GlobalStyles'; + +function App() { + return ( + + {/* CssBaseline must come first */} + + + ); +} +``` + +### ScopedCssBaseline + +You can customize it using the [themed components](https://mui.com/joy-ui/customization/themed-components/) approach. The component identifier is `JoyScopedCssBaseline` which contains only the `root` slot. + +```js +import { CssVarsProvider, extendTheme } from '@mui/joy/styles'; +import ScopedCssBaseline from '@mui/joy/ScopedCssBaseline'; +import MyApp from './MyApp'; + +const theme = extendTheme({ + components: { + JoyScopedCssBaseline: { + styleOverrides: { + root: ({ theme }) => ({ + // ...CSS object styles + }) + } + } + } +}) + +export default function MyApp() { + const [root, setRoot] = React.useState(null); + return ( + {/* target the node to ScopedCssBaseline's div */} + + {/* must be used under CssVarsProvider */} + setRoot(element)}> + {/* The rest of your application */} + + + + ); +} +``` diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index ef52043712e5d2..f1d2a67bf36d0a 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -76,6 +76,11 @@ const pages = [ { pathname: '/joy-ui/react-tabs' }, ], }, + { + pathname: '/joy-ui/components/utils', + subheader: 'utils', + children: [{ pathname: '/joy-ui/react-css-baseline' }], + }, ], }, { diff --git a/docs/pages/joy-ui/react-css-baseline.js b/docs/pages/joy-ui/react-css-baseline.js new file mode 100644 index 00000000000000..f96abbeef5e398 --- /dev/null +++ b/docs/pages/joy-ui/react-css-baseline.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/components/css-baseline/css-baseline.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/mui-joy/src/CssBaseline/CssBaseline.tsx b/packages/mui-joy/src/CssBaseline/CssBaseline.tsx new file mode 100644 index 00000000000000..164e15cff7b3fe --- /dev/null +++ b/packages/mui-joy/src/CssBaseline/CssBaseline.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { GlobalStyles } from '@mui/system'; +import { Theme, DefaultColorScheme, ColorSystem } from '../styles/types'; +import { CssBaselineProps } from './CssBaselineProps'; + +/** + * Kickstart an elegant, consistent, and simple baseline to build upon. + */ +function CssBaseline({ children, disableColorScheme = false }: CssBaselineProps) { + return ( + + { + const colorSchemeStyles: Record = {}; + if (!disableColorScheme) { + // The CssBaseline is wrapped inside a CssVarsProvider + ( + Object.entries(theme.colorSchemes) as Array<[DefaultColorScheme, ColorSystem]> + ).forEach(([key, scheme]) => { + colorSchemeStyles[theme.getColorSchemeSelector(key).replace(/\s*&/, '')] = { + colorScheme: scheme.palette?.mode, + }; + }); + } + return { + html: { + WebkitFontSmoothing: 'antialiased', + MozOsxFontSmoothing: 'grayscale', + // Change from `box-sizing: content-box` so that `width` + // is not affected by `padding` or `border`. + boxSizing: 'border-box', + // Fix font resize problem in iOS + WebkitTextSizeAdjust: '100%', + }, + '*, *::before, *::after': { + boxSizing: 'inherit', + }, + 'strong, b': { + fontWeight: 'bold', + }, + body: { + margin: 0, // Remove the margin in all browsers. + color: theme.vars.palette.text.primary, + ...(theme.typography.body1 as any), + backgroundColor: theme.vars.palette.background.body, + '@media print': { + // Save printer ink. + backgroundColor: theme.vars.palette.common.white, + }, + // Add support for document.body.requestFullScreen(). + // Other elements, if background transparent, are not supported. + '&::backdrop': { + backgroundColor: theme.vars.palette.background.backdrop, + }, + }, + ...colorSchemeStyles, + }; + }} + /> + {children} + + ); +} + +CssBaseline.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * You can wrap a node. + */ + children: PropTypes.node, + /** + * Disable `color-scheme` CSS property. + * + * For more details, check out https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme + * For browser support, check out https://caniuse.com/?search=color-scheme + * @default false + */ + disableColorScheme: PropTypes.bool, +} as any; + +export default CssBaseline; diff --git a/packages/mui-joy/src/CssBaseline/CssBaselineProps.ts b/packages/mui-joy/src/CssBaseline/CssBaselineProps.ts new file mode 100644 index 00000000000000..3cf05cdd2a0c4a --- /dev/null +++ b/packages/mui-joy/src/CssBaseline/CssBaselineProps.ts @@ -0,0 +1,14 @@ +export interface CssBaselineProps { + /** + * You can wrap a node. + */ + children?: React.ReactNode; + /** + * Disable `color-scheme` CSS property. + * + * For more details, check out https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme + * For browser support, check out https://caniuse.com/?search=color-scheme + * @default false + */ + disableColorScheme?: boolean; +} diff --git a/packages/mui-joy/src/CssBaseline/index.ts b/packages/mui-joy/src/CssBaseline/index.ts new file mode 100644 index 00000000000000..179c14be74d7e1 --- /dev/null +++ b/packages/mui-joy/src/CssBaseline/index.ts @@ -0,0 +1,2 @@ +export { default } from './CssBaseline'; +export * from './CssBaselineProps'; diff --git a/packages/mui-joy/src/GlobalStyles/index.ts b/packages/mui-joy/src/GlobalStyles/index.ts new file mode 100644 index 00000000000000..63d5f8f365ad1a --- /dev/null +++ b/packages/mui-joy/src/GlobalStyles/index.ts @@ -0,0 +1 @@ +export { GlobalStyles as default } from '@mui/system'; diff --git a/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.test.tsx b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.test.tsx new file mode 100644 index 00000000000000..d31ca376a8086a --- /dev/null +++ b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.test.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { createRenderer, describeConformance } from 'test/utils'; +import { ThemeProvider } from '@mui/joy/styles'; +import ScopedCssBaseline, { scopedCssBaselineClasses as classes } from '@mui/joy/ScopedCssBaseline'; + +describe('', () => { + const { render } = createRenderer(); + + describeConformance(, () => ({ + classes, + inheritComponent: 'div', + render, + ThemeProvider, + muiName: 'JoyScopedCssBaseline', + refInstanceof: window.HTMLDivElement, + testComponentPropWith: 'span', + testVariantProps: { disableColorScheme: true }, + skip: ['classesRoot', 'componentsProp'], + })); +}); diff --git a/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx new file mode 100644 index 00000000000000..e1b1b8d6978a04 --- /dev/null +++ b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { OverridableComponent } from '@mui/types'; +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import useThemeProps from '../styles/useThemeProps'; +import styled from '../styles/styled'; +import { DefaultColorScheme, ColorSystem } from '../styles/types'; +import { + ScopedCssBaselineTypeMap, + ScopedCssBaselineOwnerState, + ScopedCssBaselineProps, +} from './ScopedCssBaselineProps'; +import { getScopedCssBaselineUtilityClass } from './scopedCssBaselineClasses'; + +const useUtilityClasses = () => { + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getScopedCssBaselineUtilityClass, {}); +}; + +const ScopedCssBaselineRoot = styled('div', { + name: 'JoyScopedCssBaseline', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})<{ ownerState: ScopedCssBaselineOwnerState }>(({ theme, ownerState }) => { + const colorSchemeStyles: Record = {}; + if (!ownerState.disableColorScheme && theme.colorSchemes) { + (Object.entries(theme.colorSchemes) as Array<[DefaultColorScheme, ColorSystem]>).forEach( + ([key, scheme]) => { + colorSchemeStyles[`&${theme.getColorSchemeSelector(key).replace(/\s*&/, '')}`] = { + colorScheme: scheme.palette?.mode, + }; + }, + ); + } + return { + WebkitFontSmoothing: 'antialiased', + MozOsxFontSmoothing: 'grayscale', + // Change from `box-sizing: content-box` so that `width` + // is not affected by `padding` or `border`. + boxSizing: 'border-box', + // Fix font resize problem in iOS + WebkitTextSizeAdjust: '100%', + color: theme.vars.palette.text.primary, + ...(theme.typography.body1 as any), + backgroundColor: theme.vars.palette.background.body, + '@media print': { + // Save printer ink. + backgroundColor: theme.vars.palette.common.white, + }, + '& *, & *::before, & *::after': { + boxSizing: 'inherit', + }, + '& strong, & b': { + fontWeight: 'bold', + }, + ...colorSchemeStyles, + }; +}); + +const ScopedCssBaseline = React.forwardRef(function ScopedCssBaseline(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'JoyScopedCssBaseline', + }); + + const { className, component = 'div', disableColorScheme = false, ...other } = props; + + const ownerState = { + ...props, + component, + disableColorScheme, + }; + + const classes = useUtilityClasses(); + + return ( + + ); +}) as OverridableComponent; + +ScopedCssBaseline.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * You can wrap a node. + */ + children: PropTypes.node, + /** + * @ignore + */ + className: PropTypes.string, + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: PropTypes.elementType, + /** + * Disable `color-scheme` CSS property. + * + * For more details, check out https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme + * For browser support, check out https://caniuse.com/?search=color-scheme + * @default false + */ + disableColorScheme: PropTypes.bool, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default ScopedCssBaseline; diff --git a/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaselineProps.ts b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaselineProps.ts new file mode 100644 index 00000000000000..653f7f0007ea2e --- /dev/null +++ b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaselineProps.ts @@ -0,0 +1,33 @@ +import { OverrideProps } from '@mui/types'; +import { SxProps } from '../styles/types'; + +export type ScopedCssBaselineSlot = 'root'; + +export interface ScopedCssBaselineTypeMap

{ + props: P & { + /** + * You can wrap a node. + */ + children?: React.ReactNode; + /** + * Disable `color-scheme` CSS property. + * + * For more details, check out https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme + * For browser support, check out https://caniuse.com/?search=color-scheme + * @default false + */ + disableColorScheme?: boolean; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + }; + defaultComponent: D; +} + +export type ScopedCssBaselineProps< + D extends React.ElementType = ScopedCssBaselineTypeMap['defaultComponent'], + P = { component?: React.ElementType }, +> = OverrideProps, D>; + +export interface ScopedCssBaselineOwnerState extends ScopedCssBaselineProps {} diff --git a/packages/mui-joy/src/ScopedCssBaseline/index.ts b/packages/mui-joy/src/ScopedCssBaseline/index.ts new file mode 100644 index 00000000000000..932d9e670454a8 --- /dev/null +++ b/packages/mui-joy/src/ScopedCssBaseline/index.ts @@ -0,0 +1,3 @@ +export { default } from './ScopedCssBaseline'; +export * from './ScopedCssBaselineProps'; +export { default as scopedCssBaselineClasses } from './scopedCssBaselineClasses'; diff --git a/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts b/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts new file mode 100644 index 00000000000000..aff1b911f67bb9 --- /dev/null +++ b/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts @@ -0,0 +1,16 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface ScopedCssBaselineClasses { + /** Styles applied to the root element. */ + root: string; +} + +export type ScopedCssBaselineClassKey = keyof ScopedCssBaselineClasses; + +export function getScopedCssBaselineUtilityClass(slot: string): string { + return generateUtilityClass('JoyScopedCssBaseline', slot); +} + +const scopedCssBaselineClasses = generateUtilityClasses('JoyScopedCssBaseline', ['root']); + +export default scopedCssBaselineClasses; diff --git a/packages/mui-joy/src/index.ts b/packages/mui-joy/src/index.ts index 92ed116c82b2b0..70c4d2017b0d73 100644 --- a/packages/mui-joy/src/index.ts +++ b/packages/mui-joy/src/index.ts @@ -1,6 +1,8 @@ export { default as colors } from './colors'; export * from './styles'; +export { default as GlobalStyles } from './GlobalStyles'; + export { default as AspectRatio } from './AspectRatio'; export * from './AspectRatio'; @@ -52,6 +54,12 @@ export * from './ChipDelete'; export { default as Container } from './Container'; export * from './Container'; +export { default as CssBaseline } from './CssBaseline'; +export * from './CssBaseline'; + +export { default as ScopedCssBaseline } from './ScopedCssBaseline'; +export * from './ScopedCssBaseline'; + export { default as Divider } from './Divider'; export * from './Divider'; diff --git a/packages/mui-joy/src/styles/CssVarsProvider.tsx b/packages/mui-joy/src/styles/CssVarsProvider.tsx index 55c61211c4e093..b86b5859a77721 100644 --- a/packages/mui-joy/src/styles/CssVarsProvider.tsx +++ b/packages/mui-joy/src/styles/CssVarsProvider.tsx @@ -4,6 +4,7 @@ import type { DefaultColorScheme, ExtendedColorScheme } from './types'; const shouldSkipGeneratingVar = (keys: string[]) => !!keys[0].match(/(typography|variants|breakpoints)/) || + (keys[0] === 'palette' && !!keys[1]?.match(/^(mode)$/)) || (keys[0] === 'focus' && keys[1] !== 'thickness'); const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssVarsProvider< diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts index 5baebedd8c0476..2af2a749419865 100644 --- a/packages/mui-joy/src/styles/components.d.ts +++ b/packages/mui-joy/src/styles/components.d.ts @@ -45,6 +45,11 @@ import { CircularProgressSlot, } from '../CircularProgress/CircularProgressProps'; import { ContainerProps, ContainerSlot } from '../Container/ContainerProps'; +import { + ScopedCssBaselineProps, + ScopedCssBaselineOwnerState, + ScopedCssBaselineSlot, +} from '../ScopedCssBaseline/ScopedCssBaselineProps'; import { DividerProps, DividerOwnerState, DividerSlot } from '../Divider/DividerProps'; import { FormControlProps, @@ -219,6 +224,10 @@ export interface Components { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; }; + JoyScopedCssBaseline?: { + defaultProps?: Partial; + styleOverrides?: OverridesStyleRules; + }; JoyDivider?: { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; diff --git a/packages/mui-joy/src/styles/defaultTheme.test.js b/packages/mui-joy/src/styles/defaultTheme.test.js index 8b322ccc577d99..cbc5e23cdd2fc9 100644 --- a/packages/mui-joy/src/styles/defaultTheme.test.js +++ b/packages/mui-joy/src/styles/defaultTheme.test.js @@ -26,6 +26,7 @@ describe('extendTheme', () => { 'variants', 'vars', 'cssVarPrefix', + 'getColorSchemeSelector', ]).to.includes(field); }); }); diff --git a/packages/mui-joy/src/styles/defaultTheme.ts b/packages/mui-joy/src/styles/defaultTheme.ts index 79b8c71965ddcc..a1ac1faca92723 100644 --- a/packages/mui-joy/src/styles/defaultTheme.ts +++ b/packages/mui-joy/src/styles/defaultTheme.ts @@ -59,6 +59,7 @@ export const getThemeWithVars = ( shadow, palette, }, + getColorSchemeSelector: () => '&', } as unknown as Theme; }; diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts index a7af37166e146d..0871826b7a4f54 100644 --- a/packages/mui-joy/src/styles/extendTheme.spec.ts +++ b/packages/mui-joy/src/styles/extendTheme.spec.ts @@ -14,6 +14,7 @@ import { ChipOwnerState } from '@mui/joy/Chip'; import { ChipDeleteOwnerState } from '@mui/joy/ChipDelete'; import { CircularProgressOwnerState } from '@mui/joy/CircularProgress'; import { ContainerProps } from '@mui/joy/Container'; +import { ScopedCssBaselineOwnerState } from '@mui/joy/ScopedCssBaseline'; import { DividerOwnerState } from '@mui/joy/Divider'; import { FormControlOwnerState } from '@mui/joy/FormControl'; import { FormHelperTextOwnerState } from '@mui/joy/FormHelperText'; @@ -353,6 +354,19 @@ extendTheme({ }, }, }, + JoyScopedCssBaseline: { + defaultProps: { + disableColorScheme: true, + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType, typeof ownerState>( + ownerState, + ); + return {}; + }, + }, + }, JoyDivider: { defaultProps: { orientation: 'vertical', diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts index 2bc8df32d808a4..afd06089b45c5b 100644 --- a/packages/mui-joy/src/styles/extendTheme.ts +++ b/packages/mui-joy/src/styles/extendTheme.ts @@ -142,6 +142,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { const lightColorSystem = { palette: { + mode: 'light', primary: { ...colors.blue, ...createLightModeVariantVariables('primary'), @@ -243,6 +244,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { }; const darkColorSystem = { palette: { + mode: 'dark', primary: { ...colors.blue, ...createDarkModeVariantVariables('primary'), diff --git a/packages/mui-joy/src/styles/types/colorSystem.ts b/packages/mui-joy/src/styles/types/colorSystem.ts index cb31adff718f05..3614b41f336417 100644 --- a/packages/mui-joy/src/styles/types/colorSystem.ts +++ b/packages/mui-joy/src/styles/types/colorSystem.ts @@ -135,6 +135,7 @@ export interface PaletteSuccess extends PaletteRange {} export interface PaletteWarning extends PaletteRange {} export interface Palette { + mode: 'light' | 'dark'; primary: PalettePrimary; neutral: PaletteNeutral; danger: PaletteDanger; diff --git a/packages/mui-joy/src/styles/types/theme.ts b/packages/mui-joy/src/styles/types/theme.ts index f278f59b7d7ae0..72847868e87b67 100644 --- a/packages/mui-joy/src/styles/types/theme.ts +++ b/packages/mui-joy/src/styles/types/theme.ts @@ -43,7 +43,6 @@ type NormalizeVars = ConcatDeep>; export interface RuntimeColorSystem extends Omit { palette: ColorSystem['palette'] & { - mode: 'light' | 'dark'; colorScheme: DefaultColorScheme | ExtendedColorScheme; }; } @@ -59,7 +58,10 @@ export interface ThemeScales { letterSpacing: LetterSpacing; } -export interface ThemeVars extends ThemeScales, ColorSystem {} +interface ColorSystemVars extends Omit { + palette: Omit; +} +export interface ThemeVars extends ThemeScales, ColorSystemVars {} export interface ThemeCssVarOverrides {} diff --git a/packages/mui-material/src/CssBaseline/CssBaseline.js b/packages/mui-material/src/CssBaseline/CssBaseline.js index 2b32bd57898e17..c2a55eb7ebbebe 100644 --- a/packages/mui-material/src/CssBaseline/CssBaseline.js +++ b/packages/mui-material/src/CssBaseline/CssBaseline.js @@ -11,7 +11,8 @@ export const html = (theme, enableColorScheme) => ({ boxSizing: 'border-box', // Fix font resize problem in iOS WebkitTextSizeAdjust: '100%', - ...(enableColorScheme && { colorScheme: theme.palette.mode }), + // When used under CssVarsProvider, colorScheme should not be applied dynamically because it will generate the stylesheet twice for server-rendered applications. + ...(enableColorScheme && !theme.vars && { colorScheme: theme.palette.mode }), }); export const body = (theme) => ({ @@ -25,6 +26,14 @@ export const body = (theme) => ({ }); export const styles = (theme, enableColorScheme = false) => { + const colorSchemeStyles = {}; + if (enableColorScheme && theme.colorSchemes) { + Object.entries(theme.colorSchemes).forEach(([key, scheme]) => { + colorSchemeStyles[theme.getColorSchemeSelector(key).replace(/\s*&/, '')] = { + colorScheme: scheme.palette?.mode, + }; + }); + } let defaultStyles = { html: html(theme, enableColorScheme), '*, *::before, *::after': { @@ -42,6 +51,7 @@ export const styles = (theme, enableColorScheme = false) => { backgroundColor: (theme.vars || theme).palette.background.default, }, }, + ...colorSchemeStyles, }; const themeOverrides = theme.components?.MuiCssBaseline?.styleOverrides; diff --git a/packages/mui-material/src/ScopedCssBaseline/ScopedCssBaseline.js b/packages/mui-material/src/ScopedCssBaseline/ScopedCssBaseline.js index 6ae0f1783954ab..f74c229df06bf9 100644 --- a/packages/mui-material/src/ScopedCssBaseline/ScopedCssBaseline.js +++ b/packages/mui-material/src/ScopedCssBaseline/ScopedCssBaseline.js @@ -22,6 +22,14 @@ const ScopedCssBaselineRoot = styled('div', { slot: 'Root', overridesResolver: (props, styles) => styles.root, })(({ theme, ownerState }) => { + const colorSchemeStyles = {}; + if (ownerState.enableColorScheme && theme.colorSchemes) { + Object.entries(theme.colorSchemes).forEach(([key, scheme]) => { + colorSchemeStyles[`&${theme.getColorSchemeSelector(key).replace(/\s*&/, '')}`] = { + colorScheme: scheme.palette?.mode, + }; + }); + } return { ...html(theme, ownerState.enableColorScheme), ...body(theme), @@ -31,6 +39,7 @@ const ScopedCssBaselineRoot = styled('div', { '& strong, & b': { fontWeight: theme.typography.fontWeightBold, }, + ...colorSchemeStyles, }; }); diff --git a/packages/mui-material/src/styles/experimental_extendTheme.d.ts b/packages/mui-material/src/styles/experimental_extendTheme.d.ts index 5ae0ea1cda08e7..fb2ceb05203afc 100644 --- a/packages/mui-material/src/styles/experimental_extendTheme.d.ts +++ b/packages/mui-material/src/styles/experimental_extendTheme.d.ts @@ -193,7 +193,7 @@ export interface PaletteTooltip { // The Palette should be sync with `../themeCssVarsAugmentation/index.d.ts` export interface ColorSystemOptions { - palette?: Omit & { + palette?: PaletteOptions & { common?: Partial; primary?: Partial; secondary?: Partial; diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts b/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts index 75b04405daf9fa..808657beebdda9 100644 --- a/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts +++ b/packages/mui-system/src/cssVars/createCssVarsProvider.d.ts @@ -39,11 +39,6 @@ export interface CssVarsProviderConfig { * @default false */ disableTransitionOnChange?: boolean; - /** - * Indicate to the browser which color scheme is used (light or dark) for rendering built-in UI - * @default true - */ - enableColorScheme?: boolean; /** * A function to determine if the key, value should be attached as CSS Variable * `keys` is an array that represents the object path keys. diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.js b/packages/mui-system/src/cssVars/createCssVarsProvider.js index 9e86d979596bb2..a867989af5e2e7 100644 --- a/packages/mui-system/src/cssVars/createCssVarsProvider.js +++ b/packages/mui-system/src/cssVars/createCssVarsProvider.js @@ -1,7 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import MuiError from '@mui/utils/macros/MuiError.macro'; -import { deepmerge, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; +import { deepmerge } from '@mui/utils'; import { GlobalStyles } from '@mui/styled-engine'; import cssVarsParser from './cssVarsParser'; import ThemeProvider from '../ThemeProvider'; @@ -24,7 +24,6 @@ export default function createCssVarsProvider(options) { defaultMode: designSystemMode = 'light', defaultColorScheme: designSystemColorScheme, disableTransitionOnChange: designSystemTransitionOnChange = false, - enableColorScheme: designSystemEnableColorScheme = true, shouldSkipGeneratingVar: designSystemShouldSkipGeneratingVar, resolveTheme, excludeVariablesFromRoot, @@ -60,7 +59,6 @@ export default function createCssVarsProvider(options) { defaultMode = designSystemMode, defaultColorScheme = designSystemColorScheme, disableTransitionOnChange = designSystemTransitionOnChange, - enableColorScheme = designSystemEnableColorScheme, storageWindow = typeof window === 'undefined' ? undefined : window, documentNode = typeof document === 'undefined' ? undefined : document, colorSchemeNode = typeof document === 'undefined' ? undefined : document.documentElement, @@ -75,6 +73,8 @@ export default function createCssVarsProvider(options) { typeof defaultColorScheme === 'string' ? defaultColorScheme : defaultColorScheme.light; const defaultDarkColorScheme = typeof defaultColorScheme === 'string' ? defaultColorScheme : defaultColorScheme.dark; + + // 1. Get the data about the `mode`, `colorScheme`, and setter functions. const { mode, setMode, @@ -92,29 +92,38 @@ export default function createCssVarsProvider(options) { defaultMode, storageWindow, }); - const resolvedColorScheme = (() => { + + const calculatedMode = (() => { + if (!mode) { + // This scope occurs on the server + if (defaultMode === 'system') { + return designSystemMode; + } + return defaultMode; + } + return mode; + })(); + const calculatedColorScheme = (() => { if (!colorScheme) { // This scope occurs on the server - if (defaultMode === 'dark') { + if (calculatedMode === 'dark') { return defaultDarkColorScheme; } - // use light color scheme, if default mode is 'light' | 'auto' + // use light color scheme, if default mode is 'light' | 'system' return defaultLightColorScheme; } return colorScheme; })(); - let theme = restThemeProp; + // 2. Create CSS variables and store them in objects (to be generated in stylesheets in the final step) const { css: rootCss, vars: rootVars, parsedTheme, - } = cssVarsParser(theme, { - prefix: cssVarPrefix, - shouldSkipGeneratingVar, - }); + } = cssVarsParser(restThemeProp, { prefix: cssVarPrefix, shouldSkipGeneratingVar }); - theme = { + // 3. Start composing the theme object + let theme = { ...parsedTheme, components, colorSchemes, @@ -123,9 +132,11 @@ export default function createCssVarsProvider(options) { getColorSchemeSelector: (targetColorScheme) => `[${attribute}="${targetColorScheme}"] &`, }; + // 4. Create color CSS variables and store them in objects (to be generated in stylesheets in the final step) + // The default color scheme stylesheet is constructed to have the least CSS specificity. + // The other color schemes uses selector, default as data attribute, to increase the CSS specificity so that they can override the default color scheme stylesheet. const defaultColorSchemeStyleSheet = {}; const otherColorSchemesStyleSheet = {}; - Object.entries(colorSchemes).forEach(([key, scheme]) => { const { css, @@ -136,15 +147,11 @@ export default function createCssVarsProvider(options) { shouldSkipGeneratingVar, }); theme.vars = deepmerge(theme.vars, vars); - if (key === resolvedColorScheme) { - theme = { - ...theme, - ...parsedScheme, - }; + if (key === calculatedColorScheme) { + // 4.1 Merge the selected color scheme to the theme + theme = { ...theme, ...parsedScheme }; if (theme.palette) { - // assign runtime mode & colorScheme - theme.palette.mode = mode; - theme.palette.colorScheme = resolvedColorScheme; + theme.palette.colorScheme = key; } } const resolvedDefaultColorScheme = (() => { @@ -173,6 +180,8 @@ export default function createCssVarsProvider(options) { } }); + // 5. Declaring effects + // 5.1 Updates the selector value to use the current color scheme which tells CSS to use the proper stylesheet. React.useEffect(() => { if (colorScheme && colorSchemeNode) { // attaches attribute to because the css variables are attached to :root (html) @@ -180,27 +189,11 @@ export default function createCssVarsProvider(options) { } }, [colorScheme, attribute, colorSchemeNode]); - useEnhancedEffect(() => { - if (!mode || !enableColorScheme || !colorSchemeNode) { - return undefined; - } - const priorColorScheme = colorSchemeNode.style.getPropertyValue('color-scheme'); - // `color-scheme` tells browser to render built-in elements according to its value: `light` or `dark` - if (mode === 'system') { - colorSchemeNode.style.setProperty('color-scheme', systemMode); - } else { - colorSchemeNode.style.setProperty('color-scheme', mode); - } - - return () => { - colorSchemeNode.style.setProperty('color-scheme', priorColorScheme); - }; - }, [mode, systemMode, enableColorScheme, colorSchemeNode]); - + // 5.2 Remove the CSS transition when color scheme changes to create instant experience. + // credit: https://github.com/pacocoursey/next-themes/blob/b5c2bad50de2d61ad7b52a9c5cdc801a78507d7a/index.tsx#L313 React.useEffect(() => { let timer; if (disableTransitionOnChange && hasMounted.current && documentNode) { - // credit: https://github.com/pacocoursey/next-themes/blob/b5c2bad50de2d61ad7b52a9c5cdc801a78507d7a/index.tsx#L313 const css = documentNode.createElement('style'); css.appendChild(documentNode.createTextNode(DISABLE_CSS_TRANSITION)); documentNode.head.appendChild(css); @@ -216,7 +209,6 @@ export default function createCssVarsProvider(options) { clearTimeout(timer); }; }, [colorScheme, disableTransitionOnChange, documentNode]); - React.useEffect(() => { hasMounted.current = true; return () => { @@ -228,6 +220,7 @@ export default function createCssVarsProvider(options) { systemGetInitColorSchemeScript({ attribute: defaultAttribute, colorSchemeStorageKey: defaultColorSchemeStorageKey, + defaultMode: designSystemMode, + defaultLightColorScheme, + defaultDarkColorScheme, modeStorageKey: defaultModeStorageKey, - enableColorScheme: designSystemEnableColorScheme, ...params, }); diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.test.js b/packages/mui-system/src/cssVars/createCssVarsProvider.test.js index 8002df94e874fe..5ff48e456d8c25 100644 --- a/packages/mui-system/src/cssVars/createCssVarsProvider.test.js +++ b/packages/mui-system/src/cssVars/createCssVarsProvider.test.js @@ -15,7 +15,6 @@ describe('createCssVarsProvider', () => { addListener: () => {}, removeListener: () => {}, }); - let shouldSupportColorScheme; beforeEach(() => { originalMatchmedia = window.matchMedia; @@ -34,11 +33,6 @@ describe('createCssVarsProvider', () => { // clear the localstorage storage = {}; window.matchMedia = createMatchMedia(false); - - // Currently supported Firefox does not support `color-scheme`. - // Instead of skipping relevant tests entirely we assert that they work differently in Firefox. - // This ensures that we're automatically notified once we remove older Firefox versions from the support matrix. - shouldSupportColorScheme = !/Firefox/.test(navigator.userAgent); }); afterEach(() => { window.matchMedia = originalMatchmedia; @@ -249,109 +243,6 @@ describe('createCssVarsProvider', () => { expect(screen.getByText('var(--palette-grey)')).not.to.equal(null); }); - describe('[option]: `enableColorScheme`', () => { - it('set `color-scheme` property on with correct mode, given `enableColorScheme` is true and `mode` is `light` or `dark`', () => { - const { CssVarsProvider, useColorScheme } = createCssVarsProvider({ - theme: { - colorSchemes: { light: {}, dark: {} }, - }, - defaultColorScheme: 'light', - enableColorScheme: true, - }); - const Consumer = () => { - const { setMode } = useColorScheme(); - return ; - }; - render( - - - , - ); - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: shouldSupportColorScheme ? 'light' : '', - }); - - fireEvent.click(screen.getByRole('button', { name: 'change to dark' })); - - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: shouldSupportColorScheme ? 'dark' : '', - }); - }); - - it('set `color-scheme` property on with correct mode, given `enableColorScheme` is true and mode is `system`', () => { - window.matchMedia = createMatchMedia(true); // system matches 'prefers-color-scheme: dark' - - const { CssVarsProvider, useColorScheme } = createCssVarsProvider({ - theme: { - colorSchemes: { light: {}, dark: {} }, - }, - defaultColorScheme: 'light', - enableColorScheme: true, - }); - const Consumer = () => { - const { setMode } = useColorScheme(); - return ; - }; - render( - - - , - ); - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: shouldSupportColorScheme ? 'light' : '', - }); - - fireEvent.click(screen.getByRole('button', { name: 'change to system' })); - - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: shouldSupportColorScheme ? 'dark' : '', - }); - }); - - it('does not set `color-scheme` property on with correct mode, given`enableColorScheme` is false', () => { - const currentColorScheme = window - .getComputedStyle(document.documentElement) - .getPropertyValue('color-scheme'); - const { CssVarsProvider } = createCssVarsProvider({ - theme: { - colorSchemes: { light: {}, dark: {} }, - }, - defaultColorScheme: 'light', - enableColorScheme: false, - }); - const Consumer = () =>

; - - render( - - - , - ); - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: shouldSupportColorScheme ? currentColorScheme : '', - }); - }); - - it('cleans up `color-scheme` property on , given`enableColorScheme` is true', () => { - const previousColorScheme = window - .getComputedStyle(document.documentElement) - .getPropertyValue('color-scheme'); - const { CssVarsProvider } = createCssVarsProvider({ - theme: { - colorSchemes: { light: {}, dark: {} }, - }, - defaultColorScheme: 'light', - enableColorScheme: true, - }); - const { unmount } = render(); - - unmount(); - - expect(document.documentElement).toHaveComputedStyle({ - colorScheme: previousColorScheme, - }); - }); - }); - describe('[option]: `disableTransitionOnChange`', () => { clock.withFakeTimers(); it('disable all css transitions when switching bewteen modes, given `disableTransitionOnChange` is true', () => { diff --git a/packages/mui-system/src/cssVars/getInitColorSchemeScript.tsx b/packages/mui-system/src/cssVars/getInitColorSchemeScript.tsx index 8c4ac844b456c4..ee5c582bf1082d 100644 --- a/packages/mui-system/src/cssVars/getInitColorSchemeScript.tsx +++ b/packages/mui-system/src/cssVars/getInitColorSchemeScript.tsx @@ -5,11 +5,6 @@ export const DEFAULT_COLOR_SCHEME_STORAGE_KEY = 'color-scheme'; export const DEFAULT_ATTRIBUTE = 'data-color-scheme'; export interface GetInitColorSchemeScriptOptions { - /** - * Indicate to the browser which color scheme is used (light or dark) for rendering built-in UI - * @default true - */ - enableColorScheme?: boolean; /** * The mode to be used for the first visit * @default 'light' @@ -49,7 +44,6 @@ export interface GetInitColorSchemeScriptOptions { export default function getInitColorSchemeScript(options?: GetInitColorSchemeScriptOptions) { const { - enableColorScheme = true, defaultMode = 'light', defaultLightColorScheme = 'light', defaultDarkColorScheme = 'dark', @@ -86,9 +80,6 @@ export default function getInitColorSchemeScript(options?: GetInitColorSchemeScr if (colorScheme) { ${colorSchemeNode}.setAttribute('${attribute}', colorScheme); } - if (${enableColorScheme} && !!cssColorScheme) { - ${colorSchemeNode}.style.setProperty('color-scheme', cssColorScheme); - } } catch (e) {} })();`, }} /> diff --git a/test/regressions/fixtures/CssBaseline/JoyCssBaseline.js b/test/regressions/fixtures/CssBaseline/JoyCssBaseline.js new file mode 100644 index 00000000000000..3c02b8620e2680 --- /dev/null +++ b/test/regressions/fixtures/CssBaseline/JoyCssBaseline.js @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { CssVarsProvider } from '@mui/joy/styles'; +import Box from '@mui/joy/Box'; +import CssBaseline from '@mui/joy/CssBaseline'; + +export default function JoyCssBaseline() { + return ( + + + + + {/* The scrollbar should be light */} + + + + {/* The scrollbar should be dark */} + + + + + ); +} diff --git a/test/regressions/fixtures/CssBaseline/JoyScopedCssBaseline.js b/test/regressions/fixtures/CssBaseline/JoyScopedCssBaseline.js new file mode 100644 index 00000000000000..60ee6cc8a6f255 --- /dev/null +++ b/test/regressions/fixtures/CssBaseline/JoyScopedCssBaseline.js @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { CssVarsProvider, extendTheme } from '@mui/joy/styles'; +import colors from '@mui/joy/colors'; +import Box from '@mui/joy/Box'; +import ScopedCssBaseline from '@mui/joy/ScopedCssBaseline'; + +const theme = extendTheme({ + colorSchemes: { + forest: { + palette: { + mode: 'dark', + background: { + body: colors.green[200], + }, + }, + }, + }, +}); + +export default function JoyScopedCssBaseline() { + return ( + + + {/* The scrollbar should be dark */} + + + + ); +} diff --git a/test/regressions/fixtures/CssBaseline/MaterialCssBaseline.js b/test/regressions/fixtures/CssBaseline/MaterialCssBaseline.js new file mode 100644 index 00000000000000..56e5b874851d5c --- /dev/null +++ b/test/regressions/fixtures/CssBaseline/MaterialCssBaseline.js @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import CssBaseline from '@mui/material/CssBaseline'; + +export default function MaterialCssBaseline() { + return ( + + + + + {/* The scrollbar should be light */} + + + + {/* The scrollbar should be dark */} + + + + + ); +} diff --git a/test/regressions/fixtures/CssBaseline/MaterialScopedCssBaseline.js b/test/regressions/fixtures/CssBaseline/MaterialScopedCssBaseline.js new file mode 100644 index 00000000000000..00aed3b0ec229d --- /dev/null +++ b/test/regressions/fixtures/CssBaseline/MaterialScopedCssBaseline.js @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { + Experimental_CssVarsProvider as CssVarsProvider, + experimental_extendTheme as extendTheme, + createTheme, +} from '@mui/material/styles'; +import { cyan } from '@mui/material/colors'; +import Box from '@mui/material/Box'; +import ScopedCssBaseline from '@mui/material/ScopedCssBaseline'; + +const ocean = createTheme({ + palette: { + mode: 'dark', + background: { + paper: cyan[200], + }, + }, +}); + +const theme = extendTheme({ + colorSchemes: { + ocean: { + palette: ocean.palette, + }, + }, +}); + +export default function MaterialScopedCssBaseline() { + return ( + + + {/* The scrollbar should be dark */} + + + + ); +}