Stories were throwing without AuthModal/CreateFlow providers, autodocs was a no-op, and several DS/create-flow screens had no stories.
121 lines
4.3 KiB
Plaintext
121 lines
4.3 KiB
Plaintext
---
|
|
description: Storybook story conventions — location, naming, titles, decorators
|
|
globs: stories/**/*.{js,jsx,ts,tsx,mdx},.storybook/**/*.{js,ts}
|
|
alwaysApply: false
|
|
---
|
|
|
|
# Where stories live
|
|
|
|
All stories live in the top-level `stories/` folder. Two layout rules:
|
|
|
|
- **Design-system components** mirror `app/components/`. A component at
|
|
`app/components/<bucket>/<Name>` gets `stories/<bucket>/<Name>.stories.js`.
|
|
- **Create-flow material** has two carve-outs:
|
|
- `stories/create-flow/` — shared create-flow pieces that aren't in
|
|
`app/components/` (e.g. composed wizard fragments).
|
|
- `stories/pages/` — integration stories that exercise an entire
|
|
`app/(app)/create/screens/<...>` screen as it appears in the wizard.
|
|
|
|
| Source | Story location |
|
|
| --------------------------------- | --------------------------------------- |
|
|
| `app/components/controls/Chip` | `stories/controls/Chip.stories.js` |
|
|
| `app/components/buttons/Button` | `stories/buttons/Button.stories.js` |
|
|
| `app/(app)/create/screens/.../FooScreen`| `stories/pages/FooPage.stories.js` |
|
|
| Shared create-flow fragment | `stories/create-flow/<Name>.stories.js` |
|
|
|
|
Do **not** colocate `*.stories.*` next to components. The Storybook config
|
|
(`.storybook/main.js`) only globs `stories/**`.
|
|
|
|
# File naming
|
|
|
|
- `<ComponentName>.stories.js` — default; keep `.js` unless the story
|
|
genuinely needs types.
|
|
- Use `.tsx` only when the story uses `Meta` / `StoryObj` (rare).
|
|
- Variants get a suffix: `Button.visual.stories.js`,
|
|
`Footer.responsive.stories.js`.
|
|
|
|
# Default export shape (CSF3)
|
|
|
|
```javascript
|
|
import MyComponent from "../../app/components/<area>/MyComponent";
|
|
import { CHIP_PALETTE_OPTIONS } from "../../lib/propNormalization";
|
|
|
|
export default {
|
|
title: "Components/<SubFolder>/MyComponent",
|
|
component: MyComponent,
|
|
parameters: {
|
|
layout: "centered",
|
|
docs: {
|
|
description: {
|
|
component: "Short description of what the component is for.",
|
|
},
|
|
},
|
|
},
|
|
argTypes: {
|
|
palette: {
|
|
control: { type: "select" },
|
|
options: [...CHIP_PALETTE_OPTIONS],
|
|
description: "The palette (Figma prop)",
|
|
},
|
|
onClick: { action: "clicked" },
|
|
},
|
|
};
|
|
|
|
export const Default = { args: { palette: "default" } };
|
|
```
|
|
|
|
## Title hierarchy
|
|
|
|
- Design-system components → `Components/<SubFolder>/<Name>` (e.g.
|
|
`Components/Controls/Checkbox`).
|
|
- Create-flow screens → `Pages/Create Flow/<Step>` (folder: `stories/pages/`).
|
|
- Create flow shared pieces → `Create Flow/<Name>`.
|
|
|
|
## `argTypes`
|
|
|
|
For every Figma enum prop (`variant`, `size`, `state`, `mode`, `palette`,
|
|
…) expose a `select` control listing the option set, sourced from the matching
|
|
`*_OPTIONS` const in `lib/propNormalization.ts`. See
|
|
`.cursor/rules/component-props.mdc`. Spread with `[...FOO_OPTIONS]` so the
|
|
control receives a mutable array.
|
|
|
|
# Rely on the global preview — don't re-wrap
|
|
|
|
`.storybook/preview.js` already provides:
|
|
|
|
- `MessagesProvider` with `messages/en` → access copy via `useMessages()`
|
|
inside stories exactly like app code. Never hard-code user-facing strings.
|
|
- `AuthModalProvider` and `CreateFlowProvider` (same stack as
|
|
`tests/utils/test-utils.tsx`) so `Top` and create-flow screens can mount.
|
|
- `app/globals.css` + Inter / Bricolage Grotesque / Space Grotesk CSS
|
|
variables + `.font-inter` wrapper.
|
|
- Dark canvas default and Figma breakpoints (`sm` 430, `md` 640, `lg` 1024,
|
|
`xl` 1440).
|
|
|
|
Do **not** add your own `MessagesProvider`, font wrapper, or token setup in a
|
|
story. If you need a new global, update `preview.js`.
|
|
|
|
# Interaction tests (`play`)
|
|
|
|
Use `storybook/test` for interaction assertions — not `@testing-library/*`
|
|
directly. This matches `Checkbox.stories.js`. Storybook is documentation;
|
|
Vitest component tests remain the source of truth.
|
|
|
|
```javascript
|
|
import { within, userEvent, expect } from "storybook/test";
|
|
|
|
export const Interactive = {
|
|
play: async ({ canvasElement }) => {
|
|
const canvas = within(canvasElement);
|
|
await userEvent.click(canvas.getByRole("checkbox"));
|
|
expect(canvas.getByRole("checkbox")).toHaveAttribute("aria-checked", "true");
|
|
},
|
|
};
|
|
```
|
|
|
|
# Coverage expectation
|
|
|
|
Every new component in `app/components/**` ships with a story. Screens in
|
|
`app/(app)/create/screens/**` ship with a `stories/pages/<Name>Page.stories.js`
|
|
entry. A new component without a story is considered incomplete.
|