diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index af6d5950afa7..c31af8862c7e 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -31,6 +31,7 @@ - [`useSelectedLayoutSegment` and `useSelectedLayoutSegments` hook](#useselectedlayoutsegment-and-useselectedlayoutsegments-hook) - [Default Navigation Context](#default-navigation-context) - [Actions Integration Caveats](#actions-integration-caveats-1) + - [Next.js Head](#nextjs-head) - [Sass/Scss](#sassscss) - [Css/Sass/Scss Modules](#csssassscss-modules) - [Styled JSX](#styled-jsx) @@ -54,6 +55,8 @@ 👉 [Next.js Routing (next/router)](#nextjs-routing) +👉 [Next.js Head (next/head)](#nextjs-head) + 👉 [Next.js Navigation (next/navigation)](#nextjs-navigation) 👉 [Sass/Scss](#sassscss) @@ -599,6 +602,10 @@ export const parameters = { }; ``` +### Next.js Head + +[next/head](https://nextjs.org/docs/api-reference/next/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head children are placed into the head element of the iframe that Storybook uses to render your stories. + ### Sass/Scss [Global sass/scss stylesheets](https://nextjs.org/docs/basic-features/built-in-css-support#sass-support) are supported without any additional configuration as well. Just import them into [preview.js](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) diff --git a/code/frameworks/nextjs/src/head-manager/decorator.tsx b/code/frameworks/nextjs/src/head-manager/decorator.tsx new file mode 100644 index 000000000000..794ad9c77cb9 --- /dev/null +++ b/code/frameworks/nextjs/src/head-manager/decorator.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import HeadManagerProvider from './head-manager-provider'; + +export const HeadManagerDecorator = (Story: React.FC): React.ReactNode => { + return ( + + + + ); +}; diff --git a/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx b/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx new file mode 100644 index 000000000000..3762833fc137 --- /dev/null +++ b/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context'; +import initHeadManager from 'next/dist/client/head-manager'; + +type HeadManagerValue = { + updateHead?: ((state: JSX.Element[]) => void) | undefined; + mountedInstances?: Set; + updateScripts?: ((state: any) => void) | undefined; + scripts?: any; + getIsSsr?: () => boolean; + appDir?: boolean | undefined; + nonce?: string | undefined; +}; + +const HeadManagerProvider: React.FC = ({ children }) => { + const headManager: HeadManagerValue = initHeadManager(); + headManager.getIsSsr = () => false; + + return {children}; +}; + +export default HeadManagerProvider; diff --git a/code/frameworks/nextjs/src/preview.tsx b/code/frameworks/nextjs/src/preview.tsx index ba2be7833421..dc5179bb829f 100644 --- a/code/frameworks/nextjs/src/preview.tsx +++ b/code/frameworks/nextjs/src/preview.tsx @@ -2,5 +2,15 @@ import './config/preview'; import { RouterDecorator } from './routing/decorator'; import { StyledJsxDecorator } from './styledJsx/decorator'; import './images/next-image-stub'; +import { HeadManagerDecorator } from './head-manager/decorator'; -export const decorators = [StyledJsxDecorator, RouterDecorator]; +function addNextHeadCount() { + const meta = document.createElement('meta'); + meta.name = 'next-head-count'; + meta.content = '0'; + document.head.appendChild(meta); +} + +addNextHeadCount(); + +export const decorators = [StyledJsxDecorator, RouterDecorator, HeadManagerDecorator]; diff --git a/code/frameworks/nextjs/template/stories_default-js/Head.stories.jsx b/code/frameworks/nextjs/template/stories_default-js/Head.stories.jsx new file mode 100644 index 000000000000..76c9d5983031 --- /dev/null +++ b/code/frameworks/nextjs/template/stories_default-js/Head.stories.jsx @@ -0,0 +1,34 @@ +/* eslint-disable no-undef */ +import { expect } from '@storybook/jest'; +import Head from 'next/head'; +import React from 'react'; +import { within, userEvent, waitFor } from '@storybook/testing-library'; + +function Component() { + return ( +
+ + Next.js Head Title + + + + + +

Hello world!

+
+ ); +} + +export default { + component: Component, +}; + +export const Default = { + play: async ({ canvasElement }) => { + await waitFor(() => expect(document.title).toEqual('Next.js Head Title')); + await expect(document.querySelectorAll('meta[property="og:title"]')).toHaveLength(1); + await expect(document.querySelector('meta[property="og:title"]').content).toEqual( + 'My new title' + ); + }, +};