Skip to content

Commit

Permalink
[system] Fix color-scheme implementation (mui#34639)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp authored and alexfauquette committed Oct 14, 2022
1 parent 667d7db commit 9b00c0a
Show file tree
Hide file tree
Showing 32 changed files with 710 additions and 172 deletions.
167 changes: 167 additions & 0 deletions docs/data/joy/components/css-baseline/css-baseline.md
@@ -0,0 +1,167 @@
---
product: joy-ui
githubLabel: 'component: CssBaseline'
---

# CSS Baseline

<p class="description">Joy UI provides a CssBaseline component to kickstart an elegant, consistent, and simple baseline to build upon.</p>

{{"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 (
<CssVarsProvider>
{/* must be used under CssVarsProvider */}
<CssBaseline />

{/* The rest of your application */}
</CssVarsProvider>
);
}
```

## 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 */}
<CssVarsProvider colorSchemeNode={root}>
{/* must be used under CssVarsProvider */}
<ScopedCssBaseline ref={(element) => setRoot(element)}>
{/* The rest of your application */}
<MyApp />
</ScopedCssBaseline>
</CssVarsProvider>
);
}
```

⚠️ Make sure you import `ScopedCssBaseline` first to avoid box-sizing conflicts as in the above example.

## Approach

### Page

The `<html>` and `<body>` 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 `<html>` 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
<CssVarsProvider>
<CssBaseline disableColorScheme />
</CssVarsProvider>

// or
<CssVarsProvider>
<ScopedCssBaseline disableColorScheme >
{/* The rest of your application */}
</ScopedCssBaseline>
</CssVarsProvider>
```

### Typography

- No base font-size is declared on the `<html>`, but 16px is assumed (the browser default).
You can learn more about the implications of changing the `<html>` default font size in [the theme documentation](/material-ui/customization/typography/#html-font-size) page.
- Set the `theme.typography.body1` style on the `<body>` element.
- Set the font-weight to `bold` for the `<b>` and `<strong>` 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 (
<CssVarsProvider>
<CssBaseline /> {/* CssBaseline must come first */}
<GlobalStyles
styles={{
// CSS object styles
html: {
// ...
},
body: {
// ...
},
}}
/>
</CssVarsProvider>
);
}
```

### 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 */}
<CssVarsProvider colorSchemeNode={root}>
{/* must be used under CssVarsProvider */}
<ScopedCssBaseline ref={(element) => setRoot(element)}>
{/* The rest of your application */}
<MyApp />
</ScopedCssBaseline>
</CssVarsProvider>
);
}
```
5 changes: 5 additions & 0 deletions docs/data/joy/pages.ts
Expand Up @@ -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' }],
},
],
},
{
Expand Down
7 changes: 7 additions & 0 deletions 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 <MarkdownDocs {...pageProps} />;
}
85 changes: 85 additions & 0 deletions 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 (
<React.Fragment>
<GlobalStyles
styles={(theme: Theme) => {
const colorSchemeStyles: Record<string, any> = {};
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}
</React.Fragment>
);
}

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;
14 changes: 14 additions & 0 deletions 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;
}
2 changes: 2 additions & 0 deletions packages/mui-joy/src/CssBaseline/index.ts
@@ -0,0 +1,2 @@
export { default } from './CssBaseline';
export * from './CssBaselineProps';
1 change: 1 addition & 0 deletions packages/mui-joy/src/GlobalStyles/index.ts
@@ -0,0 +1 @@
export { GlobalStyles as default } from '@mui/system';
20 changes: 20 additions & 0 deletions 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('<ScopedCssBaseline />', () => {
const { render } = createRenderer();

describeConformance(<ScopedCssBaseline />, () => ({
classes,
inheritComponent: 'div',
render,
ThemeProvider,
muiName: 'JoyScopedCssBaseline',
refInstanceof: window.HTMLDivElement,
testComponentPropWith: 'span',
testVariantProps: { disableColorScheme: true },
skip: ['classesRoot', 'componentsProp'],
}));
});

0 comments on commit 9b00c0a

Please sign in to comment.