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

[material-ui][joy-ui] Add InitColorSchemeScript for Next.js App Router #42829

Merged
merged 7 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
38 changes: 31 additions & 7 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,47 @@ 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:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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:
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:

Maybe rephrase, as it is no longer a script.


### Next.js App Router

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 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}>
<body>
<InitColorSchemeScript />
<CssVarsProvider>
<CssBaseline />
{children}
</CssVarsProvider>
</body>
</html>
);
}
```

### 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 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 @@ -289,12 +289,10 @@ function App() {
}
```

For a server-side application, provide the same value to [`getInitColorSchemeScript()`](/material-ui/experimental-api/css-theme-variables/usage/#server-side-rendering):
For a server-side application, provide the same value to [`InitColorSchemeScript`](/material-ui/experimental-api/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 @@ -180,23 +180,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 @@ -212,10 +212,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
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,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 @@ -159,7 +178,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 @@ -172,10 +191,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
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 @@ -173,8 +173,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 @@ -773,7 +773,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
Loading