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

Add InitColorSchemeScript for Next.js App Router #42247

Merged
merged 25 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
82d7240
add InitColorSchemeScript
siriwatknp May 15, 2024
38de969
Revert "add InitColorSchemeScript"
siriwatknp May 17, 2024
c2ce63d
Revert "Revert "add InitColorSchemeScript""
siriwatknp May 17, 2024
f876485
Merge branch 'next' of https://github.com/mui/material-ui into feat/i…
siriwatknp May 17, 2024
8006268
Merge branch 'next' of https://github.com/mui/material-ui into feat/i…
siriwatknp Jun 10, 2024
e18a6d0
reexport InitColorSchemeScript as component
siriwatknp Jun 10, 2024
10683e6
add test
siriwatknp Jun 10, 2024
394b59e
update docs
siriwatknp Jun 10, 2024
1ef5f65
spec
siriwatknp Jun 10, 2024
bef0a77
add nonce type
siriwatknp Jun 10, 2024
9582d24
ignore CssVarsProvider from rsc
siriwatknp Jun 10, 2024
285b3cc
Merge branch 'next' of https://github.com/mui/material-ui into feat/i…
siriwatknp Jun 13, 2024
8fa07a0
split InitColorSchemeScript
siriwatknp Jun 13, 2024
34eab69
fix path
siriwatknp Jun 13, 2024
d480e9c
fix ignore path
siriwatknp Jun 13, 2024
00129fe
fix lint
siriwatknp Jun 13, 2024
334d09e
update docs
siriwatknp Jun 13, 2024
d3859a0
add InitColorSchemeScript to joy
siriwatknp Jun 13, 2024
92493b2
use InitColorSchemeScript in docs
siriwatknp Jun 13, 2024
3256350
update docs
siriwatknp Jun 13, 2024
2471c88
fix joy rsc ignore
siriwatknp Jun 13, 2024
52dddfd
trigger build
siriwatknp Jun 13, 2024
1b3cd47
Merge branch 'next' of https://github.com/mui/material-ui into feat/i…
siriwatknp Jun 14, 2024
c11fb6b
skip demo for InitColorSchemeScript
siriwatknp Jun 14, 2024
0e38328
pnpm install
siriwatknp Jun 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@siriwatknp This change blocks the monorepo upgrade for MUI X because we're using @mui/material@5:
https://app.netlify.com/sites/material-ui-x/deploys/66851d4bfe59880008524540#L240

Do you think we can keep using getInitColorSchemeScript here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I found a PR that should address this and left a comment there 🙂
#42829 (comment)

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