@codepress/design-kit 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,389 @@
1
+ ---
2
+ name: storybook-setup
3
+ description: "Add Storybook to a repository, or extend the one it has, with a stories file for every existing component and a tokens story, as one reviewable pull request: survey the components and tokens, install a pinned Storybook that matches the repo's React, Next and TypeScript, fix the usual first-build failures, write stories for every variant and state, typecheck, build, and check the stories capture in a design kit. Use when asked to set up Storybook, add Storybook, add stories for our components, backfill missing stories, or build a component catalog."
4
+ ---
5
+
6
+ # Set up Storybook with stories for your components
7
+
8
+ Your job is **one reviewable pull request** that gives this repository a
9
+ working Storybook with a stories file for every component it already has,
10
+ plus a tokens story, so people can browse the library and a design kit
11
+ (`@codepress/design-kit`) can capture it. You never merge it.
12
+
13
+ Run the steps in order. The survey comes first because the most common
14
+ failure is a Storybook that ignores what the repository already has: the wrong
15
+ folders, no styles, a second Storybook next to the first.
16
+
17
+ Supported first: **React with Vite or Next.js.** On another renderer (Vue,
18
+ Svelte, Angular, web components) you may still install Storybook and write
19
+ stories under the same rules, but say plainly in the pull request that this
20
+ guide has not been tried on that stack.
21
+
22
+ ## 1. Survey the repository
23
+
24
+ A bounded read, so you extend what exists instead of competing with it:
25
+
26
+ - **Stack and versions.** Read `react`, `react-dom`, `next`, `typescript`,
27
+ `vite` and the Node version (`engines`, `.nvmrc`, `.node-version`; when
28
+ none is recorded, the version its CI workflows use) from `package.json` and
29
+ the lockfile. In a monorepo, find the package that holds
30
+ the components; Storybook goes there.
31
+ - **Package manager.** Use the one the repository uses: the `packageManager`
32
+ field, or the lockfile (`pnpm-lock.yaml` pnpm, `yarn.lock` yarn,
33
+ `bun.lock`/`bun.lockb` bun, `package-lock.json` npm). Never add a second
34
+ lockfile.
35
+ - **The component library: every folder, not just `ui/`.** Components live in
36
+ `components/`, `ui/`, `features/*/components/`, `app/**/_components/`,
37
+ `src/shared/`, workspace packages and more. List every file that exports a
38
+ component that renders something, and note the conventions: file naming,
39
+ default or named exports, how props are typed.
40
+ - **The token layer.** CSS custom properties (`:root { --… }`), a Tailwind
41
+ theme (`tailwind.config.*`, or `@theme` in CSS for Tailwind 4), a theme
42
+ object for a CSS-in-JS `ThemeProvider`, or a design-tokens file.
43
+ - **The global stylesheet and providers.** What the app's entry imports
44
+ (`main.tsx`, `app/layout.tsx`, `pages/_app.tsx`): global CSS, fonts, and
45
+ providers components need to render (theme, router, i18n, data clients).
46
+ - **An existing Storybook.** `.storybook/`, `*.stories.*` files, `storybook`
47
+ or `@storybook/*` dependencies, a `storybook` script. **If Storybook already
48
+ exists, extend it: never run `storybook init` again** and never upgrade it
49
+ in this pull request (one older than Storybook 7 cannot be captured into a
50
+ design kit; say so). Match its story format exactly (CSF version, typing,
51
+ decorators, titles), keep its stories, and add stories only where they are
52
+ missing. A component with a stories file that lacks states gets those
53
+ states added to that file, never a second file.
54
+ - **A design kit.** A `design-kit.config.json` means stories will be captured
55
+ into a kit; step 6 checks they are.
56
+
57
+ Write the survey as one short paragraph (it goes in the pull request) before
58
+ you change anything.
59
+
60
+ ## 2. Install Storybook (only when there is none)
61
+
62
+ **Pick the version.** Use **Storybook 9** (`storybook@9`): it is the newest
63
+ major that `design-kit capture stories` is checked against, and it supports
64
+ Next.js 14.1 through 16. Never use `storybook@latest`. Use **Storybook 8**
65
+ (`storybook@8`) instead when the app is below Storybook 9's floors: Node 20,
66
+ Vite 5, Next.js 14.1 or TypeScript 4.9. Check a framework's peer ranges with
67
+ `npm view @storybook/<framework>@9 peerDependencies`.
68
+
69
+ **Pick the builder deliberately, and record the choice for the pull request.**
70
+
71
+ - Vite app: `@storybook/react-vite`.
72
+ - Next.js app: `@storybook/nextjs` (webpack) is the default and handles
73
+ `next/font`, `next/image`, `next/navigation` and the app's PostCSS and
74
+ Tailwind setup. `@storybook/nextjs-vite` is faster and fits when the app
75
+ has no custom `webpack()` in `next.config.*` that its components depend on
76
+ (SVGR, raw loaders, aliases). When unsure, take `@storybook/nextjs`.
77
+
78
+ **Run init with the repository's package manager**, in the package that holds
79
+ the components:
80
+
81
+ ```bash
82
+ pnpm dlx storybook@9 init --yes --no-dev --disable-telemetry --features docs
83
+ # npm: npx --yes storybook@9 init ... yarn: yarn dlx storybook@9 init ...
84
+ # bun: bunx storybook@9 init ...
85
+ # add --type nextjs, or --builder vite, when init detects the wrong one
86
+ ```
87
+
88
+ `--features docs` keeps init from adding its test-runner setup; step 3
89
+ removes docs (and the onboarding addon init adds anyway) unless the person
90
+ asked for docs pages. Ignore init's banner telling you to cancel and use
91
+ `@latest`: the pin is deliberate.
92
+
93
+ **Then check what init changed: `git diff package.json`.**
94
+
95
+ - Only Storybook packages may be added (`storybook`, `@storybook/*`, and
96
+ `eslint-plugin-storybook` when the repository uses ESLint). All of
97
+ them must be on the **same exact version**; write it without a `^` or `~`
98
+ range.
99
+ - `react`, `react-dom`, `next`, `typescript`, `vite` and every other existing
100
+ dependency must be unchanged. **Never downgrade or upgrade the app** to fit
101
+ Storybook; choose the Storybook version that fits the app.
102
+ - A `storybook` and a `build-storybook` script are fine, and so are the
103
+ `storybook-static` and `*storybook.log` lines init adds to `.gitignore`.
104
+
105
+ ## 3. Configure it
106
+
107
+ Keep the configuration small and make it render the components as the app
108
+ does. When Storybook already existed, change its configuration only where the
109
+ stories need it to render and build (styles, providers, env placeholders,
110
+ loaders, `staticDirs`), and keep its addons as they are.
111
+
112
+ - **Addons.** Keep only what init added for the framework itself. Remove the
113
+ onboarding addon (`@storybook/addon-onboarding`) and its package, and
114
+ delete init's example stories folder (`stories/` or `src/stories/`, with
115
+ its Button, Header and Page examples). Remove `@storybook/addon-docs` (from
116
+ `addons` and `package.json`), any `tags: ["autodocs"]` and the `*.mdx`
117
+ glob unless the person asked for docs pages. Add no other addons. Set
118
+ `core: { disableTelemetry: true }` in `main.*` so builds stay quiet (for
119
+ builds you run against an existing Storybook, set
120
+ `STORYBOOK_DISABLE_TELEMETRY=1` instead).
121
+ - **Story globs.** Point `stories` in `.storybook/main.*` at the real
122
+ component folders from the survey, for example
123
+ `["../src/**/*.stories.@(ts|tsx)"]`, or one entry per folder or package.
124
+ - **Styles and tokens.** Import the global stylesheet or token layer in
125
+ `.storybook/preview.*` (`import "../src/styles/globals.css";`). With
126
+ Tailwind 3, make sure its `content` globs cover the stories files too. If
127
+ the app sets fonts or a theme class in its layout, reproduce that with a
128
+ decorator in `preview.*` (rename init's `preview.ts` to `preview.tsx` for
129
+ JSX). Wrap stories in the providers components need, the same way the app
130
+ does. **An unstyled Storybook is worse than none**: check one built story
131
+ actually looks like the app.
132
+ - **Landing page.** Land the sidebar on a real story, not a docs page. The
133
+ tokens story from step 4 works well: in `preview.*`, set
134
+ `parameters.options.storySort` to `{ order: ["Foundations", "*"] }`.
135
+
136
+ **Fix the first build's failures yourself.** These four hit almost every
137
+ fresh Storybook; fix them rather than report them:
138
+
139
+ 1. **Peer-dependency conflicts.** Put every Storybook package on one exact
140
+ version, and pick the Storybook major whose peer ranges include the app's
141
+ React, Next and TypeScript (step 2). If init's own install fails, re-run
142
+ it with `--skip-install` and install with the repository's package manager.
143
+ Drop an addon that conflicts rather than force it. Never use `--force`,
144
+ and never change the app's versions. If a conflict is only in a transitive
145
+ Storybook dependency, an `overrides` (npm), `resolutions` (yarn) or
146
+ `pnpm.overrides` entry for that one package is acceptable; say so in the
147
+ pull request.
148
+ 2. **Modules that throw at import on a missing environment variable**, for
149
+ example a config module that throws when `NEXT_PUBLIC_API_URL` or
150
+ `VITE_API_URL` is unset. Give Storybook placeholder values. **Never edit
151
+ the app's own validation.** Bundlers inline these variables at build time,
152
+ so pass them through Storybook's `env` option in `.storybook/main.*`, which
153
+ reaches `process.env` and `import.meta.env` in both the webpack and Vite
154
+ builders. Setting `process.env` at the top of `main.*` does not: the
155
+ webpack builder replaces `process.env` in the bundle.
156
+
157
+ ```ts
158
+ // Storybook only: placeholders so modules that validate env at import
159
+ // time load. Never real values.
160
+ env: (config = {}) => ({
161
+ ...config,
162
+ NEXT_PUBLIC_API_URL: config.NEXT_PUBLIC_API_URL ?? "https://api.example.invalid",
163
+ }),
164
+ ```
165
+
166
+ Never commit a real key, and never copy the app's `.env` files.
167
+
168
+ 3. **`next/font`, `next/image` and SVGR imports.** Keep a Next.js framework
169
+ (`@storybook/nextjs` or `@storybook/nextjs-vite`), which handles
170
+ `next/font` and `next/image`. Components that use `next/navigation` need
171
+ `parameters: { nextjs: { appDirectory: true } }` in `preview.*`. A font
172
+ the app's layout applies (`className={inter.variable}` on `<html>`) needs
173
+ the same class in a `preview.*` decorator. With `next/font/google` the
174
+ font files load from Google when a story is viewed, so a captured kit
175
+ falls back to the system font; say so in the pull request
176
+ (`next/font/local` fonts are served by Storybook and captured). Storybook
177
+ does not run the app's custom `webpack()` from `next.config.*`, so an SVG
178
+ imported as a component (`import Logo from "./logo.svg"`) needs the loader
179
+ added in `webpackFinal`, the app's own `@svgr/webpack`:
180
+
181
+ ```ts
182
+ webpackFinal: async (config) => {
183
+ const rules = config.module?.rules ?? [];
184
+ for (const rule of rules)
185
+ if (rule && typeof rule === "object" && rule.test instanceof RegExp && rule.test.test(".svg"))
186
+ rule.exclude = /\.svg$/i;
187
+ rules.push({ test: /\.svg$/i, use: ["@svgr/webpack"] });
188
+ return config;
189
+ },
190
+ ```
191
+
192
+ With a Vite builder, the app's `vite.config.*` plugins (such as
193
+ `vite-plugin-svgr`) are merged in; add the plugin or alias in `viteFinal`
194
+ only when the app configures it somewhere Storybook does not read.
195
+
196
+ 4. **`public/` assets.** Images and fonts the components load by absolute
197
+ path (`/logo.svg`) 404 until `.storybook/main.*` has
198
+ `staticDirs: ["../public"]` (the path to the app's public folder).
199
+
200
+ ## 4. Write the stories
201
+
202
+ **One stories file per component, next to the component**, named the way
203
+ the repository names files (`Button.tsx` and `Button.stories.tsx`). Use CSF 3
204
+ in the repository's typing style, for example:
205
+
206
+ ```tsx
207
+ import type { Meta, StoryObj } from "@storybook/react-vite"; // or @storybook/nextjs, @storybook/nextjs-vite; @storybook/react on Storybook 8
208
+ import { Button } from "./Button";
209
+
210
+ const meta = {
211
+ component: Button,
212
+ args: { children: "Save" },
213
+ } satisfies Meta<typeof Button>;
214
+ export default meta;
215
+ type Story = StoryObj<typeof meta>;
216
+
217
+ export const Primary: Story = {};
218
+ export const Secondary: Story = { args: { variant: "secondary" } };
219
+ ```
220
+
221
+ Each file covers, where it applies:
222
+
223
+ - **the default**, and **every variant and every size** the props allow;
224
+ - **hover, focus and disabled** for anything interactive. Disabled is a prop.
225
+ Focus is a `play` function that focuses the element
226
+ (`play: async ({ canvasElement }) => canvasElement.querySelector("button")?.focus()`).
227
+ Hover that the component drives itself (a tooltip, a menu) is a `play`
228
+ function too. A pure CSS `:hover` cannot be shown without a pseudo-state
229
+ addon, so say so in the coverage note instead of adding one;
230
+ - **empty, loading and error** states for anything that shows data or
231
+ results;
232
+ - **long text or overflow** for anything that holds text: a long label, a
233
+ long unbroken word or address, many items.
234
+
235
+ Name state stories so they do not shadow globals: `ErrorState` with
236
+ `name: "Error"`, never `export const Error`. A captured kit is static HTML, so
237
+ focus rings and other live states show in Storybook but not in the kit.
238
+
239
+ Use made-up fixture data (`example.com` addresses, invented names), never
240
+ real customer data. Write stories through the component's props; do not
241
+ refactor the app to make a state reachable. A state no prop can reach is
242
+ skipped for cause and listed.
243
+
244
+ **Add a tokens (foundations) story**, for example
245
+ `src/foundations/Tokens.stories.tsx` with the title `Foundations/Tokens`.
246
+ It renders the design language from the token layer the survey found: the
247
+ palette as labelled swatches (`background: var(--color-primary)`, or Tailwind
248
+ classes), the type scale (each size with its name), and the spacing ramp
249
+ (bars of each width). Radii and shadows can go in too. List the token names
250
+ from the token source, so a new token is one line.
251
+
252
+ **"Every component" means the whole catalog**: the shared primitives and
253
+ the composites and feature components (lists, panels, cards, headers,
254
+ forms). Skip a component **only for cause**: it cannot render apart from
255
+ live data (it fetches on its own and has no way to pass data in), it is
256
+ deprecated, or it is an internal detail of another component that is
257
+ already covered. Route files (`page.tsx`, `layout.tsx`) and the app root are
258
+ screens, not catalog components: give one a story when it renders from props
259
+ alone, and otherwise list it as skipped. List every skip, with its reason, in
260
+ the pull request. A large backfill may be split into several pull requests by
261
+ component family; say which family each covers and what is left. Silently
262
+ leaving components out is not allowed.
263
+
264
+ While you read the screens, note **inline UI that should become shared
265
+ components**: the same markup repeated across pages. Name each one under
266
+ "Recommended next" in the pull request. Do not extract it in this pull
267
+ request.
268
+
269
+ ## 5. Typecheck, then build
270
+
271
+ 1. **Typecheck first.** It is cheaper than a build, and it catches story type
272
+ errors that `storybook build` lets through. Run the repository's own
273
+ typecheck (`pnpm typecheck`, `pnpm exec tsc --noEmit`, `tsc -b`). Check
274
+ that its `include` covers the stories files and `.storybook/`. TypeScript
275
+ skips dot-folders in wildcards, so name the files: add
276
+ `".storybook/*.ts", ".storybook/*.tsx"` to the `include` the typecheck
277
+ uses. Fix every error.
278
+ 2. **Build.** Run `storybook build` with the repository's package manager
279
+ (`pnpm exec storybook build --quiet`, `npx storybook build --quiet`). Fix
280
+ every error and warning that points at a story or the config. Then check
281
+ `storybook-static/index.json`: its story entries should match what you
282
+ wrote, and none of init's examples should be left.
283
+ 3. Run the repository's lint and its other pull request checks that the
284
+ change touches (ESLint on the stories, a format check), and fix what this
285
+ change broke.
286
+
287
+ ## 6. Check the design kit captures it
288
+
289
+ `design-kit capture stories` renders every story in a headless browser, so it
290
+ also proves each story renders without throwing. Write the kit to a scratch
291
+ folder **outside the repository**, never into it.
292
+
293
+ - **With a `design-kit.config.json`:** give it a `stories` section if it has
294
+ none:
295
+
296
+ ```json
297
+ "stories": {
298
+ "root": ".",
299
+ "buildCommand": "pnpm exec storybook build --quiet -o storybook-static",
300
+ "staticDir": "storybook-static",
301
+ "gaps": { "componentGlob": "src/**/[A-Z]*.{tsx,jsx}" }
302
+ }
303
+ ```
304
+
305
+ (`root` is the folder holding `.storybook/`, relative to the config; use
306
+ the repository's package manager in `buildCommand`.) If the repository's
307
+ `.github/workflows/design-kit.yml` and `.claude/skills/design-kit/SKILL.md`
308
+ were written before Storybook existed, they skip stories too. To update
309
+ them, copy the repository (without `node_modules`) to a scratch folder,
310
+ run `npx --yes @codepress/design-kit init --force` there, and port only
311
+ the Storybook parts of those two files back: the "Build Storybook and
312
+ capture stories" step and the stories capture line. Then run, from the
313
+ folder that holds `design-kit.config.json`:
314
+
315
+ ```bash
316
+ npx --yes @codepress/design-kit capture stories --out "$TMPDIR/design-kit-check"
317
+ ```
318
+
319
+ - **Without one:** check anyway with a scratch config outside the repository:
320
+
321
+ ```bash
322
+ cat > "$TMPDIR/design-kit-check.json" <<EOF
323
+ {"version": 1, "stories": {"root": "$PWD", "storybookDir": "$PWD/storybook-static",
324
+ "gaps": {"componentGlob": "src/**/[A-Z]*.{tsx,jsx}"}}}
325
+ EOF
326
+ npx --yes @codepress/design-kit capture stories --config "$TMPDIR/design-kit-check.json" --out "$TMPDIR/design-kit-check"
327
+ ```
328
+
329
+ Capture needs Playwright and Chromium. When the repository has neither,
330
+ run `npx --yes -p @playwright/test playwright install chromium`, then
331
+ `npx --yes -p @codepress/design-kit -p @playwright/test design-kit capture stories ...`;
332
+ install nothing into the repository for it.
333
+
334
+ Capture what you just built: the `buildCommand` rebuilds Storybook, and with
335
+ a `storybookDir` rebuild first, because a stale build hides missing stories.
336
+ Read what it printed (`stories N, ... failed M`) and
337
+ `$TMPDIR/design-kit-check/gaps.json`:
338
+
339
+ - a **failed story** (it threw, or never rendered) is a bug in the story or
340
+ the config: fix it and capture again;
341
+ - a **`no story`** row names a component file with no stories file: write
342
+ one, or make sure it is in your skip list with its reason. Its path is
343
+ relative to the config file's folder.
344
+
345
+ Aim for zero failures and every `no story` row explained. The files in
346
+ `html/` are bare markup without CSS; to look at one story styled, find its id
347
+ and compose it into a page, then open that page in a browser:
348
+
349
+ ```bash
350
+ KIT="$TMPDIR/design-kit-check"
351
+ node "$KIT/kit.mjs" --kit "$KIT" find <component name> # ids like story:<storybook-id>@1280
352
+ node "$KIT/kit.mjs" --kit "$KIT" compose --frame <id> --out "$TMPDIR/story.html"
353
+ ```
354
+
355
+ Check it looks like the app, and take a screenshot for the pull request.
356
+
357
+ ## 7. Open the pull request
358
+
359
+ One pull request; never merge it. When the survey and the checks found
360
+ nothing missing (Storybook builds, every component has its stories, the
361
+ capture has no failures and no unexplained `no story` rows), open no pull
362
+ request: report that proof instead. Otherwise the description says:
363
+
364
+ - **The survey**: where the components and tokens live, and whether
365
+ Storybook existed (extended, not re-initialised).
366
+ - **Versions and builder**: the Storybook version and why (9, or 8 because of
367
+ which floor), the framework package and why, and that React, Next and
368
+ TypeScript are unchanged.
369
+ - **First-build fixes**: each one you made (env placeholders, loaders,
370
+ `staticDirs`, overrides) and why.
371
+ - **Coverage**: the components covered, counted per folder ("14 of 15 in
372
+ `src/components`"), with the states each covers, and the tokens story.
373
+ - **Skipped**: every component or state left out, each with its reason.
374
+ - **Proof**: the typecheck and `storybook build` results, the capture's
375
+ story count, failures and `gaps.json` rows, and the screenshot.
376
+ - **Recommended next**: inline UI that should become shared components, and,
377
+ when the repository has no design kit yet, that
378
+ `npx --yes @codepress/design-kit setup-guide` sets one up.
379
+ - **Not tried**: anything this guide does not cover on this stack, said
380
+ plainly.
381
+
382
+ Never commit `storybook-static/`, a captured kit, or a real secret.
383
+
384
+ ## Keeping stories current
385
+
386
+ After this pull request, when you add a component or change what one
387
+ renders, add or update its story in the same pull request, covering the
388
+ states in step 4, so the next design kit captures it. Say in the pull request
389
+ which stories you added or changed.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `design-kit capture stories`: every story of a Storybook 7/8 static build
2
+ * `design-kit capture stories`: every story of a Storybook 7, 8 or 9 static build
3
3
  * rendered to static HTML, the CSS the preview loaded, the fonts, an index
4
4
  * and kit.mjs.
5
5
  *