-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Next.js (React Server Components) support #469
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
base: staging
Are you sure you want to change the base?
Changes from all commits
3d87db2
930c583
ad4b907
8a26eb1
ef9a443
01a4aac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { Meta, Canvas } from '@storybook/addon-docs/blocks'; | ||
| import * as Stories from './accordion.stories'; | ||
| import * as NextjsStories from './accordion.nextjs.stories'; | ||
|
|
||
| <Meta of={Stories} name="Next.js Usage" /> | ||
|
|
||
| # Accordion — Next.js (App Router) | ||
|
|
||
| > **TL;DR** — `Accordion` works in the Next.js App Router out of the box: import it and render it inside a **Server Component**. Use its named subpart exports (e.g. `AccordionItem`) — not dot notation (`Accordion.X`). Put anything interactive (event handlers, state) in a file that starts with `'use client'`. | ||
|
|
||
| ## Usage in a Server Component | ||
|
|
||
| Use the **named subpart exports** — not dot access (`Accordion.X`). In a Server Component, React cannot read sub-components attached to a client reference, so dot access resolves to `undefined`. The named exports are individual client references: | ||
|
|
||
| ```tsx | ||
| // app/page.tsx — Server Component | ||
| import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@bsf/force-ui'; | ||
|
|
||
| export default function Page() { | ||
| return ( | ||
| <Accordion> | ||
| <AccordionItem value="a"> | ||
| <AccordionTrigger>What is force-ui?</AccordionTrigger> | ||
| <AccordionContent>A React + Tailwind component library.</AccordionContent> | ||
| </AccordionItem> | ||
| </Accordion> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Dot notation (client components only) | ||
|
|
||
| Inside your own client components the familiar compound dot syntax still works — it is only Server Components that require the named exports above: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { Accordion } from '@bsf/force-ui'; | ||
|
|
||
| <Accordion> | ||
| <Accordion.Item value="a"> | ||
| <Accordion.Trigger>Title</Accordion.Trigger> | ||
| <Accordion.Content>Body</Accordion.Content> | ||
| </Accordion.Item> | ||
| </Accordion> | ||
| ``` | ||
|
|
||
| ## Event handlers & state | ||
|
|
||
| Server Components can't hold state or receive event handlers (`onClick`, `onChange`, …). Move the interactive part into its own client component: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { Accordion } from '@bsf/force-ui'; | ||
|
|
||
| export default function Interactive() { | ||
| // useState, onClick, onChange … live in this client file | ||
| return <Accordion />; | ||
| } | ||
| ``` | ||
|
|
||
| Then import that into your Server Component — the page itself stays a Server Component. | ||
|
|
||
| ## Live preview | ||
|
|
||
| _Rendered with the named (App-Router) pattern._ | ||
|
|
||
| <Canvas of={NextjsStories.ServerComponentPattern} /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
|
jaieds marked this conversation as resolved.
|
||
| import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components'; | ||
|
|
||
| // Powers the "Next.js Usage" docs <Canvas>. Hidden from the sidebar | ||
| // (`!dev`) and excluded from autodocs — it only demonstrates the named | ||
| // (App-Router) import pattern in an isolated preview with copyable code. | ||
| const meta: Meta = { | ||
| title: 'Molecules/Accordion/Next.js Example', | ||
| component: Accordion, | ||
| tags: [ '!dev', '!autodocs' ], | ||
| parameters: { layout: 'centered' }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| export const ServerComponentPattern: StoryObj = { | ||
|
jaieds marked this conversation as resolved.
|
||
| name: 'Named exports (App Router)', | ||
| render: () => { | ||
| return ( | ||
| <div className="w-full max-w-lg"> | ||
| <Accordion> | ||
|
jaieds marked this conversation as resolved.
|
||
| <AccordionItem value="a"> | ||
| <AccordionTrigger>What is force-ui?</AccordionTrigger> | ||
| <AccordionContent>A React + Tailwind component library.</AccordionContent> | ||
| </AccordionItem> | ||
| </Accordion> | ||
| </div> | ||
| ); | ||
| }, | ||
| }; | ||
|
jaieds marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| 'use client'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What: The addition of the 'use client' directive is correct, but ensure consistent application across all components that directly interact with client-side functionalities. Why: The 'use client' directive is necessary for Next.js Server Components to correctly interpret component behavior. This helps to prevent potential rendering issues when components need to access client-specific behavior, such as DOM manipulation or state management. How: Double-check that this directive is present for all components intended to be rendered as client components. Review all component files to ensure consistency and functionality. |
||
| import React, { | ||
| type ReactNode, | ||
| type ElementType, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,6 @@ | ||
| export { default } from './accordion'; | ||
| export { | ||
|
jaieds marked this conversation as resolved.
|
||
| AccordionItem, | ||
| AccordionTrigger, | ||
| AccordionContent, | ||
| } from './accordion'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { Meta, Canvas } from '@storybook/addon-docs/blocks'; | ||
| import * as Stories from './alert.stories'; | ||
|
|
||
| <Meta of={Stories} name="Next.js Usage" /> | ||
|
|
||
| # Alert — Next.js (App Router) | ||
|
|
||
| > **TL;DR** — `Alert` works in the Next.js App Router out of the box: import it and render it inside a **Server Component**. Put anything interactive (event handlers, state) in a file that starts with `'use client'`. | ||
|
|
||
| ## Usage in a Server Component | ||
|
|
||
| ```tsx | ||
| // app/page.tsx — Server Component | ||
| import { Alert } from '@bsf/force-ui'; | ||
|
|
||
| export default function Page() { | ||
| return <Alert />; | ||
| } | ||
| ``` | ||
|
|
||
| ## Event handlers & state | ||
|
|
||
| Server Components can't hold state or receive event handlers (`onClick`, `onChange`, …). Move the interactive part into its own client component: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { Alert } from '@bsf/force-ui'; | ||
|
|
||
| export default function Interactive() { | ||
| // useState, onClick, onChange … live in this client file | ||
| return <Alert />; | ||
| } | ||
| ``` | ||
|
|
||
| Then import that into your Server Component — the page itself stays a Server Component. | ||
|
|
||
| ## Live preview | ||
|
|
||
| <Canvas of={Stories.Neutral} /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { Meta, Canvas } from '@storybook/addon-docs/blocks'; | ||
| import * as Stories from './area-chart.stories'; | ||
|
|
||
| <Meta of={Stories} name="Next.js Usage" /> | ||
|
|
||
| # AreaChart — Next.js (App Router) | ||
|
|
||
| > **TL;DR** — `AreaChart` works in the Next.js App Router out of the box: import it and render it inside a **Server Component**. Put anything interactive (event handlers, state) in a file that starts with `'use client'`. | ||
|
|
||
| ## Usage in a Server Component | ||
|
|
||
| ```tsx | ||
| // app/page.tsx — Server Component | ||
| import { AreaChart } from '@bsf/force-ui'; | ||
|
|
||
| export default function Page() { | ||
| return <AreaChart />; | ||
| } | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - Chart data is passed as props; renders client-side after hydration. | ||
|
|
||
| ## Event handlers & state | ||
|
|
||
| Server Components can't hold state or receive event handlers (`onClick`, `onChange`, …). Move the interactive part into its own client component: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { AreaChart } from '@bsf/force-ui'; | ||
|
|
||
| export default function Interactive() { | ||
| // useState, onClick, onChange … live in this client file | ||
| return <AreaChart />; | ||
| } | ||
| ``` | ||
|
|
||
| Then import that into your Server Component — the page itself stays a Server Component. | ||
|
|
||
| ## Live preview | ||
|
|
||
| <Canvas of={Stories.AreaChartSimple} /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { Meta, Canvas } from '@storybook/addon-docs/blocks'; | ||
| import * as Stories from './avatar.stories'; | ||
|
|
||
| <Meta of={Stories} name="Next.js Usage" /> | ||
|
|
||
| # Avatar — Next.js (App Router) | ||
|
|
||
| > **TL;DR** — `Avatar` works in the Next.js App Router out of the box: import it and render it inside a **Server Component**. Put anything interactive (event handlers, state) in a file that starts with `'use client'`. | ||
|
|
||
| ## Usage in a Server Component | ||
|
|
||
| ```tsx | ||
| // app/page.tsx — Server Component | ||
| import { Avatar } from '@bsf/force-ui'; | ||
|
|
||
| export default function Page() { | ||
| return <Avatar />; | ||
| } | ||
| ``` | ||
|
|
||
| ## Event handlers & state | ||
|
|
||
| Server Components can't hold state or receive event handlers (`onClick`, `onChange`, …). Move the interactive part into its own client component: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { Avatar } from '@bsf/force-ui'; | ||
|
|
||
| export default function Interactive() { | ||
| // useState, onClick, onChange … live in this client file | ||
| return <Avatar />; | ||
| } | ||
| ``` | ||
|
|
||
| Then import that into your Server Component — the page itself stays a Server Component. | ||
|
|
||
| ## Live preview | ||
|
|
||
| <Canvas of={Stories.Default} /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { Meta, Canvas } from '@storybook/addon-docs/blocks'; | ||
| import * as Stories from './badge.stories'; | ||
|
|
||
| <Meta of={Stories} name="Next.js Usage" /> | ||
|
|
||
| # Badge — Next.js (App Router) | ||
|
|
||
| > **TL;DR** — `Badge` works in the Next.js App Router out of the box: import it and render it inside a **Server Component**. Put anything interactive (event handlers, state) in a file that starts with `'use client'`. | ||
|
|
||
| ## Usage in a Server Component | ||
|
|
||
| ```tsx | ||
| // app/page.tsx — Server Component | ||
| import { Badge } from '@bsf/force-ui'; | ||
|
|
||
| export default function Page() { | ||
| return <Badge />; | ||
| } | ||
| ``` | ||
|
|
||
| ## Event handlers & state | ||
|
|
||
| Server Components can't hold state or receive event handlers (`onClick`, `onChange`, …). Move the interactive part into its own client component: | ||
|
|
||
| ```tsx | ||
| 'use client'; | ||
| import { Badge } from '@bsf/force-ui'; | ||
|
|
||
| export default function Interactive() { | ||
| // useState, onClick, onChange … live in this client file | ||
| return <Badge />; | ||
| } | ||
| ``` | ||
|
|
||
| Then import that into your Server Component — the page itself stays a Server Component. | ||
|
|
||
| ## Live preview | ||
|
|
||
| <Canvas of={Stories.Neutral} /> |
Uh oh!
There was an error while loading. Please reload this page.