diff --git a/.plop/templates/component/component.mdx.hbs b/.plop/templates/component/component.mdx.hbs new file mode 100644 index 000000000..4d79153a2 --- /dev/null +++ b/.plop/templates/component/component.mdx.hbs @@ -0,0 +1,74 @@ +import { Canvas, Controls, Meta, Subtitle, Title } from '@storybook/addon-docs/blocks' + +import * as {{pascalCase name}}Stories from './{{dashCase name}}.stories' + + + + + +<Subtitle>One-line summary of what {{pascalCase name}} is for.</Subtitle> + +## Overview + +What {{pascalCase name}} is and the problem it solves, in a sentence or two. For design +guidance (anatomy, when to use, do and don't), see the +[{{pascalCase name}} handbook page](https://github.com/Doist/component-libraries/blob/main/handbook/components/{{dashCase name}}.md). + +{/* Verify this handbook link: the slug isn't always the component's dash-name (e.g. + buttons.md, menus.md, switches.md) and some pages don't exist yet. */} + +## When to use + +{/* Two or more concrete scenarios. Trim to what the handbook doesn't already cover. */} + +- Use {{pascalCase name}} when ... +- Prefer another component when ... + +## Basic usage + +The simplest real example. Pass `variant` and `children`. + +<Canvas of={ {{pascalCase name}}Stories.Playground } /> + +## Variants + +{{pascalCase name}} supports a `primary` and a `secondary` variant. + +<Canvas of={ {{pascalCase name}}Stories.Variants } /> + +## States + +{/* Visual states (idle, hover, disabled, loading) live here. Add a `States` story and + reference it with <Canvas of={ {{pascalCase name}}Stories.States } />, or delete this + section if the component has no distinct states. */} + +## API + +<Controls of={ {{pascalCase name}}Stories.Playground } /> + +{/* Composable components: add `ArgTypes` to the import above and document each exported + part with its own block, e.g. + +### `<{{pascalCase name}}.Item>` + +<ArgTypes of={ {{pascalCase name}}Item } /> + +Repeat per exported sub-component. */} + +## Custom properties + +The following CSS custom properties customize the {{pascalCase name}} appearance. The values +shown are the defaults. + +```css +--reactist-{{dashCase name}}-tint +--reactist-{{dashCase name}}-fill +``` + +## Accessibility + +- **Keyboard**: describe focus order and keys the component handles. +- **Screen reader**: the name, role, and state it exposes (and via which prop). +- **Focus**: focus management / trapping behavior, if any. + +<Canvas of={ {{pascalCase name}}Stories.Accessibility } /> diff --git a/.plop/templates/component/component.stories.tsx.hbs b/.plop/templates/component/component.stories.tsx.hbs index 9c093b586..8ea0a9efc 100644 --- a/.plop/templates/component/component.stories.tsx.hbs +++ b/.plop/templates/component/component.stories.tsx.hbs @@ -1,8 +1,9 @@ import * as React from 'react' -import type { Meta, StoryObj } from '@storybook/react' import { {{pascalCase name}} } from './{{dashCase name}}' +import type { Meta, StoryObj } from '@storybook/react' + const meta = { title: 'Design system/{{pascalCase name}}', component: {{pascalCase name}}, @@ -32,7 +33,7 @@ export const Playground: Story = { control: { type: 'text' }, }, }, - render(args) { + render(args: React.ComponentProps<typeof {{pascalCase name}}>) { return ( <section> <{{pascalCase name}} {...args} /> @@ -40,3 +41,24 @@ export const Playground: Story = { ) }, } + +export const Variants: Story = { + render() { + return ( + <section> + <{{pascalCase name}} variant="primary">Primary</{{pascalCase name}}>{' '} + <{{pascalCase name}} variant="secondary">Secondary</{{pascalCase name}}> + </section> + ) + }, +} + +export const Accessibility: Story = { + render() { + return ( + <section> + <{{pascalCase name}} variant="primary">Accessible content</{{pascalCase name}}> + </section> + ) + }, +} diff --git a/.plop/templates/component/component.test.tsx.hbs b/.plop/templates/component/component.test.tsx.hbs index dbe0405ad..e91960a16 100644 --- a/.plop/templates/component/component.test.tsx.hbs +++ b/.plop/templates/component/component.test.tsx.hbs @@ -1,5 +1,7 @@ import * as React from 'react' + import { render, screen } from '@testing-library/react' + import { {{pascalCase name}} } from './{{dashCase name}}' describe('{{pascalCase name}}', () => { diff --git a/.plop/templates/component/component.tsx.hbs b/.plop/templates/component/component.tsx.hbs index f1da8df32..a42def267 100644 --- a/.plop/templates/component/component.tsx.hbs +++ b/.plop/templates/component/component.tsx.hbs @@ -1,5 +1,7 @@ import * as React from 'react' + import { Box } from '../box' + import styles from './{{dashCase name}}.module.css' type {{pascalCase name}}Props = { @@ -14,11 +16,7 @@ type {{pascalCase name}}Props = { } function {{pascalCase name}}({ children, variant }: {{pascalCase name}}Props) { - return ( - <Box className={[styles.container, styles[variant]]}> - {children} - </Box> - ) + return <Box className={[styles.container, styles[variant]]}>{children}</Box> } export { {{pascalCase name}} } diff --git a/.plop/templates/component/index.ts.hbs b/.plop/templates/component/index.ts.hbs index 45e186b92..cd311cbf9 100644 --- a/.plop/templates/component/index.ts.hbs +++ b/.plop/templates/component/index.ts.hbs @@ -1,2 +1,2 @@ -export { {{pascalCase name}} } from './{{dashCase name}}' export type { {{pascalCase name}}Props } from './{{dashCase name}}' +export { {{pascalCase name}} } from './{{dashCase name}}' diff --git a/CLAUDE.md b/CLAUDE.md index 663e47b86..d74c887e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,8 @@ src/ component-name.tsx # Implementation component-name.module.css # CSS Modules styles component-name.test.tsx # Tests - component-name.stories.mdx # Storybook docs (or .tsx) + component-name.stories.tsx # Storybook stories (CSF) + component-name.mdx # Storybook MDX docs page styles/ design-tokens.css # Global CSS custom properties (--reactist-*) utils/ @@ -43,7 +44,7 @@ src/ ### File structure -Every component has `index.ts`, `component.tsx`, `component.module.css`, `component.test.tsx`, and a story file. Use `npm run plop component` to scaffold new components. +Every component has `index.ts`, `component.tsx`, `component.module.css`, `component.test.tsx`, a `component.stories.tsx` story file, and a `component.mdx` docs page. Use `npm run plop component` to scaffold new components. ### Implementation patterns diff --git a/README.md b/README.md index d5dcdba8d..536ef58e5 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ The generated source files include the component implementation with sample prop You also need to export your new component by adding a reference to it in the [top-level index file](src/index.ts). +The generator also creates a `<component-name>.mdx` documentation page next to the stories. Edit it to document the component: fill in the Overview and link the matching [design handbook page](https://github.com/Doist/component-libraries/tree/main/handbook/components), keep the sections that apply, and delete the ones that don't. For composable components (multiple exported parts), repeat the API block per part. Its live examples render from the generated `Playground`, `Variants`, and `Accessibility` stories. + ## Storybook For the first development mode run: diff --git a/plopfile.mjs b/plopfile.mjs index 14bfb6212..626a2ed94 100644 --- a/plopfile.mjs +++ b/plopfile.mjs @@ -56,6 +56,12 @@ function componentGenerator(plop) { templateFile: templateFile('component/component.stories.tsx'), }) + actions.push({ + type: 'add', + path: 'src/{{dashCase name}}/{{dashCase name}}.mdx', + templateFile: templateFile('component/component.mdx'), + }) + return actions }, })