Skip to content

Commit

Permalink
Add InitColorSchemeScript for Next.js App Router (mui#42247)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp authored and joserodolfofreitas committed Jul 29, 2024
1 parent 78731a4 commit 7240fca
Show file tree
Hide file tree
Showing 32 changed files with 249 additions and 118 deletions.
43 changes: 16 additions & 27 deletions docs/data/joy/customization/dark-mode/dark-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ When you change the `defaultMode` to another value, you must clear the local sto

{{"demo": "DarkModeByDefault.js"}}

For server-side applications, check out the framework setup in [the section below](#server-side-rendering) and provide the same value to the `getInitColorSchemeScript` function:
For server-side applications, check out the framework setup in [the section below](#server-side-rendering) and provide the same value to the `InitColorSchemeScript` component:

```js
getInitColorSchemeScript({ defaultMode: 'dark' });
<InitColorSchemeScript defaultMode="dark" />
```

## Matching device's preference
Expand All @@ -28,10 +28,10 @@ import { CssVarsProvider } from '@mui/joy/styles';
<CssVarsProvider defaultMode="system">...</CssVarsProvider>;
```

For server-side applications, check out the framework setup in [the section below](#server-side-rendering) and provide the same value to the `getInitColorSchemeScript` function:
For server-side applications, check out the framework setup in [the section below](#server-side-rendering) and provide the same value to the `InitColorSchemeScript` component:

```js
getInitColorSchemeScript({ defaultMode: 'system' });
<InitColorSchemeScript defaultMode="system" />
```

### Identify the system mode
Expand Down Expand Up @@ -121,23 +121,23 @@ If you try to render your UI based on the server, before mounting on the client,

### Avoiding screen flickering

To [prevent the UI from flickering](/joy-ui/main-features/dark-mode-optimization/#the-problem-flickering-on-first-load), apply `getInitColorSchemeScript()` before the main application script-it varies across frameworks:
To [prevent the UI from flickering](/joy-ui/main-features/dark-mode-optimization/#the-problem-flickering-on-first-load), apply `<InitColorSchemeScript />` before the main application script-it varies across frameworks:

### Next.js Pages Router

To use the Joy UI API with a Next.js project, add the following code to the custom [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document) file:

```jsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { getInitColorSchemeScript } from '@mui/joy/styles';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';

export default class MyDocument extends Document {
render() {
return (
<Html data-color-scheme="light">
<Head>...</Head>
<body>
{getInitColorSchemeScript()}
<InitColorSchemeScript />
<Main />
<NextScript />
</body>
Expand All @@ -149,34 +149,23 @@ export default class MyDocument extends Document {

### Next.js App Router

To use the Joy UI API with a Next.js project with the App Router, create a separate [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to utilize the [`getInitColorSchemeScript`](https://mui.com/joy-ui/main-features/dark-mode-optimization/#the-solution-css-variables) function:

```jsx title="colorInit.js"
'use client';

import { getInitColorSchemeScript } from '@mui/joy/styles';

export default function ColorInit() {
return <>{getInitColorSchemeScript()}</>;
}
```

Now, you can use the it in your [`app/layout.js`](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts) file in order to prevent flickering:
To use the Joy UI API with a Next.js project with the App Router, add the following code to the [`app/layout.js`](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts) file in order to prevent flickering:

```jsx title="layout.js"
import ColorInit from './colorInit';
import { CssBaseline, CssVarsProvider } from '@mui/joy';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
import { CssVarsProvider } from '@mui/joy/styles';
import CssBaseline from '@mui/joy/CssBaseline';

export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning={true}>
<CssVarsProvider>
<body>
<body>
<InitColorSchemeScript />
<CssVarsProvider>
<CssBaseline />
<ColorInit />
{children}
</body>
</CssVarsProvider>
</CssVarsProvider>
</body>
</html>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,22 @@ Solving this problem required us to take a novel approach to styling and theming

Thanks to Joy UI's built-in support for CSS variables, your app can render all of its color schemes at build time, so that the user's preference can be injected _before_ the DOM is rendered in the browser.

Joy UI provides the `getInitColorSchemeScript()` function to make this flash-free dark mode possible with React frameworks like Next.js or Remix.
Joy UI provides the `InitColorSchemeScript` component to make this flash-free dark mode possible with React frameworks like Next.js or Remix.
This function must be placed before the main script so it can apply the correct stylesheet before your components are rendered.

The code snippet below shows how this works with the Next.js Pages Router:

```jsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { getInitColorSchemeScript } from '@mui/joy/styles';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';

export default class MyDocument extends Document {
render() {
return (
<Html data-color-scheme="light">
<Head>...</Head>
<body>
{getInitColorSchemeScript()}
<InitColorSchemeScript />
<Main />
<NextScript />
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,10 @@ function App() {
}
```

For a server-side application, provide the same value to [`getInitColorSchemeScript()`](/material-ui/customization/css-theme-variables/usage/#server-side-rendering):
For a server-side application, provide the same value to [`InitColorSchemeScript`](/material-ui/customization/css-theme-variables/usage/#server-side-rendering):

```js
getInitColorSchemeScript({
defaultMode: 'dark',
});
<InitColorSchemeScript defaultMode="dark" />
```

:::warning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,42 @@ The structure of this object is nearly identical to the theme structure, the onl

## Server-side rendering

Place `getInitColorSchemeScript()` before the `<Main />` tag to prevent the dark-mode SSR flickering during the hydration phase.
Place `<InitColorSchemeScript />` before the `<Main />` tag to prevent the dark-mode SSR flickering during the hydration phase.

### Next.js App Router

Add the following code to the [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required) file:

```jsx title="app/layout.js"
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<InitColorSchemeScript /> {/* must come before the <main> element */}
<main>{children}</main>
</body>
</html>
);
}
```

### Next.js Pages Router

Add the following code to the custom [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document) file:

```jsx
```jsx title="pages/_document.js"
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { getInitColorSchemeScript } from '@mui/material/styles';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default class MyDocument extends Document {
render() {
return (
<Html data-color-scheme="light">
<Head>...</Head>
<body>
{getInitColorSchemeScript()}
<InitColorSchemeScript /> {/* must come before the <Main> element */}
<Main />
<NextScript />
</body>
Expand All @@ -158,7 +177,7 @@ const StyledComponent = styled('button')(({ theme }) => ({

## API

### `<CssVarsProvider>` props
### `<CssVarsProvider>` &nbsp;props

- `defaultMode?: 'light' | 'dark' | 'system'` - Application's default mode (`light` by default)
- `disableTransitionOnChange : boolean` - Disable CSS transitions when switching between modes
Expand All @@ -171,10 +190,9 @@ const StyledComponent = styled('button')(({ theme }) => ({
- `mode: string` - The user's selected mode
- `setMode: mode => {…}` - Function for setting the `mode`. The `mode` is saved to internal state and local storage; if `mode` is null, it will be reset to the default mode

### `getInitColorSchemeScript: (options) => React.ReactElement`

**options**
### `<InitColorSchemeScript>` &nbsp;props

- `defaultMode?: 'light' | 'dark' | 'system'`: - Application's default mode before React renders the tree (`light` by default)
- `modeStorageKey?: string`: - localStorage key used to store application `mode`
- `attribute?: string` - DOM attribute for applying color scheme
- `nonce?: string` - Optional nonce passed to the injected script tag, used to allow-list the next-themes script in your CSP
Original file line number Diff line number Diff line change
Expand Up @@ -176,23 +176,23 @@ The `mode` is stored inside `CssVarsProvider` which handles local storage synchr
## 3. Prevent dark-mode flickering in server-side applications
The `getInitColorSchemeScript()` API prevents dark-mode flickering by returning a script that must be run before React.
The `InitColorSchemeScript` component prevents dark-mode flickering by returning a script that must be run before React.
### Next.js Pages Router
Place the script before `<Main />` in your [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document):
Place the component before `<Main />` in your [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document):
```jsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { getInitColorSchemeScript } from '@mui/material/styles';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default class MyDocument extends Document {
render() {
return (
<Html data-color-scheme="light">
<Head>...</Head>
<body>
{getInitColorSchemeScript()}
<InitColorSchemeScript />
<Main />
<NextScript />
</body>
Expand All @@ -208,10 +208,10 @@ Place the script in your [`gatsby-ssr.js`](https://www.gatsbyjs.com/docs/referen
```jsx
import * as React from 'react';
import { getInitColorSchemeScript } from '@mui/material/styles';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export function onRenderBody({ setPreBodyComponents }) {
setPreBodyComponents([getInitColorSchemeScript()]);
setPreBodyComponents([<InitColorSchemeScript />]);
}
```
Expand Down
8 changes: 4 additions & 4 deletions docs/pages/_document.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ServerStyleSheets as JSSServerStyleSheets } from '@mui/styles';
import { ServerStyleSheet } from 'styled-components';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import GlobalStyles from '@mui/material/GlobalStyles';
import { getInitColorSchemeScript as getMuiInitColorSchemeScript } from '@mui/material/styles';
import { getInitColorSchemeScript as getJoyInitColorSchemeScript } from '@mui/joy/styles';
import MuiInitColorSchemeScript from '@mui/material/InitColorSchemeScript';
import JoyInitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
import { pathnameToLanguage } from 'docs/src/modules/utils/helpers';
import createEmotionCache from 'docs/src/createEmotionCache';
import { getMetaThemeColor } from '@mui/docs/branding';
Expand Down Expand Up @@ -188,8 +188,8 @@ export default class MyDocument extends Document {
/>
</Head>
<body>
{getMuiInitColorSchemeScript({ defaultMode: 'system' })}
{getJoyInitColorSchemeScript({ defaultMode: 'system' })}
<MuiInitColorSchemeScript defaultMode="system" />
<JoyInitColorSchemeScript defaultMode="system" />
<Main />
<script
// eslint-disable-next-line react/no-danger
Expand Down
6 changes: 3 additions & 3 deletions docs/pages/blog/first-look-at-joy.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,20 @@ You're still able to override the style completely via the usual CSS overrides,
Joy UI provides an effective way to prevent UI flicker when users refresh or re-enter a page with dark mode enabled.
The out-of-the-box CSS variables support allows every color scheme to be rendered at build time, inserting the selected color scheme and mode before the browser renders the DOM.

What's more, it provides a function called `getInitColorSchemeScript()` that enables you to have perfect functioning dark mode in various React frameworks, such as Next.js, Gatsby, and Remix.
What's more, it provides a component called `InitColorSchemeScript` that enables you to have perfect functioning dark mode in various React frameworks, such as Next.js, Gatsby, and Remix.

```js
// A Next.js example
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { getInitColorSchemeScript } from '@mui/joy/styles';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';

export default class MyDocument extends Document {
render() {
return (
<Html data-color-scheme="light">
<Head>...</Head>
<body>
{getInitColorSchemeScript()}
<InitColorSchemeScript />
<Main />
<NextScript />
</body>
Expand Down
5 changes: 3 additions & 2 deletions packages/api-docs-builder-core/joyUi/projectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export const projectSettings: ProjectSettings = {
// Container's demo isn't ready
// GlobalStyles's demo isn't ready
return (
filename.match(/(ThemeProvider|CssVarsProvider|Container|ColorInversion|GlobalStyles)/) !==
null
filename.match(
/(ThemeProvider|CssVarsProvider|Container|ColorInversion|GlobalStyles|InitColorSchemeScript)/,
) !== null
);
},
translationPagesDirectory: 'docs/translations/api-docs-joy',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const projectSettings: ProjectSettings = {
getComponentInfo: getMaterialUiComponentInfo,
translationLanguages: LANGUAGES,
skipComponent(filename: string) {
return filename.match(/(ThemeProvider|CssVarsProvider|Grid2)/) !== null;
return filename.match(/(ThemeProvider|CssVarsProvider|InitColorSchemeScript|Grid2)/) !== null;
},
translationPagesDirectory: 'docs/translations/api-docs',
generateClassName: generateUtilityClass,
Expand Down
4 changes: 3 additions & 1 deletion packages/api-docs-builder-core/muiSystem/projectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const projectSettings: ProjectSettings = {
getComponentInfo: getSystemComponentInfo,
translationLanguages: LANGUAGES,
skipComponent(filename) {
return filename.match(/(ThemeProvider|CssVarsProvider|GlobalStyles)/) !== null;
return (
filename.match(/(ThemeProvider|CssVarsProvider|GlobalStyles|InitColorSchemeScript)/) !== null
);
},
translationPagesDirectory: 'docs/translations/api-docs',
generateClassName: generateUtilityClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ export default async function generateComponentApi(
throw new Error(
'Unable to find demos. \n' +
`Be sure to include \`components: ${reactApi.name}\` in the markdown pages where the \`${reactApi.name}\` component is relevant. ` +
'Every public component should have a demo. ',
'Every public component should have a demo.\nFor internal component, add the name of the component to the `skipComponent` method of the product.',
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as React from 'react';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';

<InitColorSchemeScript nonce="foo-bar" />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer } from '@mui/internal-test-utils';
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';

describe('InitColorSchemeScript', () => {
const { render } = createRenderer();

it('should render as expected', () => {
const { container } = render(<InitColorSchemeScript />);
expect(container.firstChild).to.have.tagName('script');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import SystemInitColorSchemeScript from '@mui/system/InitColorSchemeScript';

export const defaultConfig = {
attribute: 'data-joy-color-scheme',
colorSchemeStorageKey: 'joy-color-scheme',
defaultLightColorScheme: 'light',
defaultDarkColorScheme: 'dark',
modeStorageKey: 'joy-mode',
} as const;

export default (function InitColorSchemeScript(props) {
return <SystemInitColorSchemeScript {...defaultConfig} {...props} />;
} as typeof SystemInitColorSchemeScript);
1 change: 1 addition & 0 deletions packages/mui-joy/src/InitColorSchemeScript/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './InitColorSchemeScript';
Loading

0 comments on commit 7240fca

Please sign in to comment.