Skip to content

Commit a2972e8

Browse files
authored
[material-ui][joy-ui] Add InitColorSchemeScript for Next.js App Router (#42829)
1 parent 5dc161b commit a2972e8

32 files changed

+354
-175
lines changed

docs/data/joy/customization/dark-mode/dark-mode.md

+31-7
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ When you change the `defaultMode` to another value, you must clear the local sto
1212

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

15-
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:
15+
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:
1616

1717
```js
18-
getInitColorSchemeScript({ defaultMode: 'dark' });
18+
<InitColorSchemeScript defaultMode="dark" />
1919
```
2020

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

31-
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:
31+
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:
3232

3333
```js
34-
getInitColorSchemeScript({ defaultMode: 'system' });
34+
<InitColorSchemeScript defaultMode="system" />
3535
```
3636

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

122122
### Avoiding screen flickering
123123

124-
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:
124+
To [prevent the UI from flickering](/joy-ui/main-features/dark-mode-optimization/#the-problem-flickering-on-first-load), add the `<InitColorSchemeScript />` component before the `<CssVarsProvider />` component:
125+
126+
### Next.js App Router
127+
128+
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:
129+
130+
```jsx title="layout.js"
131+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
132+
import { CssVarsProvider } from '@mui/joy/styles';
133+
import CssBaseline from '@mui/joy/CssBaseline';
134+
135+
export default function RootLayout({ children }) {
136+
return (
137+
<html lang="en" suppressHydrationWarning={true}>
138+
<body>
139+
<InitColorSchemeScript />
140+
<CssVarsProvider>
141+
<CssBaseline />
142+
{children}
143+
</CssVarsProvider>
144+
</body>
145+
</html>
146+
);
147+
}
148+
```
125149

126150
### Next.js Pages Router
127151

128152
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:
129153

130154
```jsx
131155
import Document, { Html, Head, Main, NextScript } from 'next/document';
132-
import { getInitColorSchemeScript } from '@mui/joy/styles';
156+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
133157

134158
export default class MyDocument extends Document {
135159
render() {
136160
return (
137161
<Html data-color-scheme="light">
138162
<Head>...</Head>
139163
<body>
140-
{getInitColorSchemeScript()}
164+
<InitColorSchemeScript />
141165
<Main />
142166
<NextScript />
143167
</body>

docs/data/joy/main-features/dark-mode-optimization/dark-mode-optimization.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,22 @@ Solving this problem required us to take a novel approach to styling and theming
2727

2828
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.
2929

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

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

3535
```jsx
3636
import Document, { Html, Head, Main, NextScript } from 'next/document';
37-
import { getInitColorSchemeScript } from '@mui/joy/styles';
37+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
3838

3939
export default class MyDocument extends Document {
4040
render() {
4141
return (
4242
<Html data-color-scheme="light">
4343
<Head>...</Head>
4444
<body>
45-
{getInitColorSchemeScript()}
45+
<InitColorSchemeScript />
4646
<Main />
4747
<NextScript />
4848
</body>

docs/data/material/experimental-api/css-theme-variables/customization.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,10 @@ function App() {
289289
}
290290
```
291291

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

294294
```js
295-
getInitColorSchemeScript({
296-
defaultMode: 'dark',
297-
});
295+
<InitColorSchemeScript defaultMode="dark" />
298296
```
299297

300298
:::warning

docs/data/material/experimental-api/css-theme-variables/migration.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -180,23 +180,23 @@ The `mode` is stored inside `CssVarsProvider` which handles local storage synchr
180180
181181
## 3. Prevent dark-mode flickering in server-side applications
182182
183-
The `getInitColorSchemeScript()` API prevents dark-mode flickering by returning a script that must be run before React.
183+
The `InitColorSchemeScript` component prevents dark-mode flickering by returning a script that must be run before React.
184184
185185
### Next.js Pages Router
186186
187-
Place the script before `<Main />` in your [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document):
187+
Place the component before `<Main />` in your [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document):
188188
189189
```jsx
190190
import Document, { Html, Head, Main, NextScript } from 'next/document';
191-
import { getInitColorSchemeScript } from '@mui/material/styles';
191+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
192192

193193
export default class MyDocument extends Document {
194194
render() {
195195
return (
196196
<Html data-color-scheme="light">
197197
<Head>...</Head>
198198
<body>
199-
{getInitColorSchemeScript()}
199+
<InitColorSchemeScript />
200200
<Main />
201201
<NextScript />
202202
</body>
@@ -212,10 +212,10 @@ Place the script in your [`gatsby-ssr.js`](https://www.gatsbyjs.com/docs/referen
212212
213213
```jsx
214214
import * as React from 'react';
215-
import { getInitColorSchemeScript } from '@mui/material/styles';
215+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
216216

217217
export function onRenderBody({ setPreBodyComponents }) {
218-
setPreBodyComponents([getInitColorSchemeScript()]);
218+
setPreBodyComponents([<InitColorSchemeScript />]);
219219
}
220220
```
221221

docs/data/material/experimental-api/css-theme-variables/usage/usage.md

+26-8
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,42 @@ The structure of this object is nearly identical to the theme structure, the onl
116116

117117
## Server-side rendering
118118

119-
Place `getInitColorSchemeScript()` before the `<Main />` tag to prevent the dark-mode SSR flickering during the hydration phase.
119+
Place `<InitColorSchemeScript />` before the `<Main />` tag to prevent the dark-mode SSR flickering during the hydration phase.
120+
121+
### Next.js App Router
122+
123+
Add the following code to the [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required) file:
124+
125+
```jsx title="app/layout.js"
126+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
127+
128+
export default function RootLayout({ children }) {
129+
return (
130+
<html lang="en">
131+
<body>
132+
<InitColorSchemeScript /> {/* must come before the <main> element */}
133+
<main>{children}</main>
134+
</body>
135+
</html>
136+
);
137+
}
138+
```
120139

121140
### Next.js Pages Router
122141

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

125-
```jsx
144+
```jsx title="pages/_document.js"
126145
import Document, { Html, Head, Main, NextScript } from 'next/document';
127-
import { getInitColorSchemeScript } from '@mui/material/styles';
146+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
128147

129148
export default class MyDocument extends Document {
130149
render() {
131150
return (
132151
<Html data-color-scheme="light">
133152
<Head>...</Head>
134153
<body>
135-
{getInitColorSchemeScript()}
154+
<InitColorSchemeScript /> {/* must come before the <Main> element */}
136155
<Main />
137156
<NextScript />
138157
</body>
@@ -159,7 +178,7 @@ const StyledComponent = styled('button')(({ theme }) => ({
159178

160179
## API
161180

162-
### `<CssVarsProvider>` props
181+
### `<CssVarsProvider>` &nbsp;props
163182

164183
- `defaultMode?: 'light' | 'dark' | 'system'` - Application's default mode (`light` by default)
165184
- `disableTransitionOnChange : boolean` - Disable CSS transitions when switching between modes
@@ -172,10 +191,9 @@ const StyledComponent = styled('button')(({ theme }) => ({
172191
- `mode: string` - The user's selected mode
173192
- `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
174193

175-
### `getInitColorSchemeScript: (options) => React.ReactElement`
176-
177-
**options**
194+
### `<InitColorSchemeScript>` &nbsp;props
178195

179196
- `defaultMode?: 'light' | 'dark' | 'system'`: - Application's default mode before React renders the tree (`light` by default)
180197
- `modeStorageKey?: string`: - localStorage key used to store application `mode`
181198
- `attribute?: string` - DOM attribute for applying color scheme
199+
- `nonce?: string` - Optional nonce passed to the injected script tag, used to allow-list the next-themes script in your CSP

docs/pages/_document.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { ServerStyleSheets as JSSServerStyleSheets } from '@mui/styles';
55
import { ServerStyleSheet } from 'styled-components';
66
import Document, { Html, Head, Main, NextScript } from 'next/document';
77
import GlobalStyles from '@mui/material/GlobalStyles';
8-
import { getInitColorSchemeScript as getMuiInitColorSchemeScript } from '@mui/material/styles';
9-
import { getInitColorSchemeScript as getJoyInitColorSchemeScript } from '@mui/joy/styles';
8+
import MuiInitColorSchemeScript from '@mui/material/InitColorSchemeScript';
9+
import JoyInitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
1010
import { pathnameToLanguage } from 'docs/src/modules/utils/helpers';
1111
import createEmotionCache from 'docs/src/createEmotionCache';
1212
import { getMetaThemeColor } from '@mui/docs/branding';
@@ -173,8 +173,8 @@ export default class MyDocument extends Document {
173173
/>
174174
</Head>
175175
<body>
176-
{getMuiInitColorSchemeScript({ defaultMode: 'system' })}
177-
{getJoyInitColorSchemeScript({ defaultMode: 'system' })}
176+
<MuiInitColorSchemeScript defaultMode="system" />
177+
<JoyInitColorSchemeScript defaultMode="system" />
178178
<Main />
179179
<script
180180
// eslint-disable-next-line react/no-danger

docs/pages/blog/first-look-at-joy.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,20 @@ You're still able to override the style completely via the usual CSS overrides,
9393
Joy UI provides an effective way to prevent UI flicker when users refresh or re-enter a page with dark mode enabled.
9494
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.
9595

96-
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.
96+
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.
9797

9898
```js
9999
// A Next.js example
100100
import Document, { Html, Head, Main, NextScript } from 'next/document';
101-
import { getInitColorSchemeScript } from '@mui/joy/styles';
101+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
102102

103103
export default class MyDocument extends Document {
104104
render() {
105105
return (
106106
<Html data-color-scheme="light">
107107
<Head>...</Head>
108108
<body>
109-
{getInitColorSchemeScript()}
109+
<InitColorSchemeScript />
110110
<Main />
111111
<NextScript />
112112
</body>

packages/api-docs-builder-core/joyUi/projectSettings.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ export const projectSettings: ProjectSettings = {
2626
// Container's demo isn't ready
2727
// GlobalStyles's demo isn't ready
2828
return (
29-
filename.match(/(ThemeProvider|CssVarsProvider|Container|ColorInversion|GlobalStyles)/) !==
30-
null
29+
filename.match(
30+
/(ThemeProvider|CssVarsProvider|Container|ColorInversion|GlobalStyles|InitColorSchemeScript)/,
31+
) !== null
3132
);
3233
},
3334
translationPagesDirectory: 'docs/translations/api-docs-joy',

packages/api-docs-builder-core/materialUi/projectSettings.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export const projectSettings: ProjectSettings = {
2828
getComponentInfo: getMaterialUiComponentInfo,
2929
translationLanguages: LANGUAGES,
3030
skipComponent(filename: string) {
31-
return filename.match(/(ThemeProvider|CssVarsProvider|DefaultPropsProvider|Grid2)/) !== null;
31+
return (
32+
filename.match(
33+
/(ThemeProvider|CssVarsProvider|InitColorSchemeScript|DefaultPropsProvider|Grid2)/,
34+
) !== null
35+
);
3236
},
3337
translationPagesDirectory: 'docs/translations/api-docs',
3438
generateClassName: generateUtilityClass,

packages/api-docs-builder-core/muiSystem/projectSettings.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export const projectSettings: ProjectSettings = {
2424
translationLanguages: LANGUAGES,
2525
skipComponent(filename) {
2626
return (
27-
filename.match(/(ThemeProvider|CssVarsProvider|DefaultPropsProvider|GlobalStyles)/) !== null
27+
filename.match(
28+
/(ThemeProvider|CssVarsProvider|InitColorSchemeScript|DefaultPropsProvider|GlobalStyles)/,
29+
) !== null
2830
);
2931
},
3032
translationPagesDirectory: 'docs/translations/api-docs',

packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ export default async function generateComponentApi(
773773
throw new Error(
774774
'Unable to find demos. \n' +
775775
`Be sure to include \`components: ${reactApi.name}\` in the markdown pages where the \`${reactApi.name}\` component is relevant. ` +
776-
'Every public component should have a demo. ',
776+
'Every public component should have a demo.\nFor internal component, add the name of the component to the `skipComponent` method of the product.',
777777
);
778778
}
779779

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as React from 'react';
2+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
3+
4+
<InitColorSchemeScript nonce="foo-bar" />;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as React from 'react';
2+
import { expect } from 'chai';
3+
import { createRenderer } from '@mui-internal/test-utils';
4+
import InitColorSchemeScript from '@mui/joy/InitColorSchemeScript';
5+
6+
describe('InitColorSchemeScript', () => {
7+
const { render } = createRenderer();
8+
9+
it('should render as expected', () => {
10+
const { container } = render(<InitColorSchemeScript />);
11+
expect(container.firstChild).to.have.tagName('script');
12+
});
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from 'react';
2+
import SystemInitColorSchemeScript from '@mui/system/InitColorSchemeScript';
3+
4+
export const defaultConfig = {
5+
attribute: 'data-joy-color-scheme',
6+
colorSchemeStorageKey: 'joy-color-scheme',
7+
defaultLightColorScheme: 'light',
8+
defaultDarkColorScheme: 'dark',
9+
modeStorageKey: 'joy-mode',
10+
} as const;
11+
12+
export default (function InitColorSchemeScript(props) {
13+
return <SystemInitColorSchemeScript {...defaultConfig} {...props} />;
14+
} as typeof SystemInitColorSchemeScript);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './InitColorSchemeScript';

0 commit comments

Comments
 (0)