@awarebydefault/display-case 1.0.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.
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/display-case.prompt.md +64 -0
- package/docs/ai-agents.md +126 -0
- package/docs/cli.md +99 -0
- package/docs/configuration.md +410 -0
- package/docs/documentation-panel.md +50 -0
- package/docs/examples/README.md +14 -0
- package/docs/examples/multi-variant.case.tsx +30 -0
- package/docs/examples/plain.case.tsx +22 -0
- package/docs/examples/tweak-control.placard.md +80 -0
- package/docs/examples/tweaks.case.tsx +39 -0
- package/docs/hierarchy.md +59 -0
- package/docs/quick-start.md +78 -0
- package/docs/style-engines.md +180 -0
- package/docs/testing.md +245 -0
- package/docs/theming.md +97 -0
- package/docs/tweaks.md +75 -0
- package/docs/writing-cases.md +144 -0
- package/docs/writing-placard-docs.md +194 -0
- package/package.json +113 -0
- package/skills/display-case-author-case/README.md +20 -0
- package/skills/display-case-author-case/SKILL.md +40 -0
- package/skills/display-case-author-placard-doc/README.md +24 -0
- package/skills/display-case-author-placard-doc/SKILL.md +65 -0
- package/skills/display-case-review/README.md +19 -0
- package/skills/display-case-review/SKILL.md +30 -0
- package/skills/display-case-snapshot/README.md +20 -0
- package/skills/display-case-snapshot/SKILL.md +29 -0
- package/src/checks/a11y-scanner.test.ts +240 -0
- package/src/checks/a11y-scanner.ts +410 -0
- package/src/checks/check-text.test.ts +53 -0
- package/src/checks/check-text.ts +78 -0
- package/src/checks/check.test.ts +194 -0
- package/src/checks/check.ts +473 -0
- package/src/checks/providers/pixelmatch-diff.test.ts +79 -0
- package/src/checks/providers/pixelmatch-diff.ts +30 -0
- package/src/checks/providers/playwright-driver.ts +104 -0
- package/src/checks/ssr-check.test.ts +73 -0
- package/src/checks/ssr-check.ts +96 -0
- package/src/checks/structure-check.cross-package.test.ts +165 -0
- package/src/checks/structure-check.test.ts +651 -0
- package/src/checks/structure-check.ts +988 -0
- package/src/checks/tokens-check.test.ts +159 -0
- package/src/checks/tokens-check.ts +162 -0
- package/src/cli.ts +218 -0
- package/src/commands/agents.test.ts +24 -0
- package/src/commands/agents.ts +28 -0
- package/src/commands/init-run.test.ts +123 -0
- package/src/commands/init.test.ts +63 -0
- package/src/commands/init.ts +412 -0
- package/src/commands/publish.test.ts +210 -0
- package/src/commands/publish.ts +292 -0
- package/src/core/affected.test.ts +99 -0
- package/src/core/affected.ts +144 -0
- package/src/core/catalog.test.ts +152 -0
- package/src/core/catalog.ts +92 -0
- package/src/core/discovery.test.ts +184 -0
- package/src/core/discovery.ts +250 -0
- package/src/core/manifest.ts +41 -0
- package/src/core/mdx-lite/__fixtures__/box-stub.tsx +7 -0
- package/src/core/mdx-lite/index.ts +393 -0
- package/src/core/mdx-lite/mdx-lite.test.ts +345 -0
- package/src/core/mdx-plugin.test.ts +60 -0
- package/src/core/mdx-plugin.ts +30 -0
- package/src/flow-step.test-d.ts +39 -0
- package/src/index.test.ts +100 -0
- package/src/index.ts +564 -0
- package/src/render/collect-styles.emotion.test.tsx +114 -0
- package/src/render/collect-styles.test.tsx +72 -0
- package/src/render/collect-styles.ts +33 -0
- package/src/render/documents.test.ts +184 -0
- package/src/render/documents.ts +88 -0
- package/src/render/render-node.test.tsx +160 -0
- package/src/render/render-node.tsx +133 -0
- package/src/render/ssr-primer.test.tsx +25 -0
- package/src/render/ssr-primer.tsx +54 -0
- package/src/render/ssr-render.test.tsx +142 -0
- package/src/render/ssr-render.tsx +63 -0
- package/src/render/ssr-shell.test.tsx +57 -0
- package/src/render/ssr-shell.tsx +54 -0
- package/src/server/prod-server.ts +237 -0
- package/src/server/server.test.ts +117 -0
- package/src/server/server.ts +1039 -0
- package/src/style-engine.test-d.ts +37 -0
- package/src/testing/test-helpers.ts +27 -0
- package/src/types/pixelmatch.d.ts +12 -0
- package/src/ui/browser-entry.tsx +51 -0
- package/src/ui/chrome.css +485 -0
- package/src/ui/design-system/README.md +88 -0
- package/src/ui/design-system/components/controls/Button.case.tsx +52 -0
- package/src/ui/design-system/components/controls/Button.css +89 -0
- package/src/ui/design-system/components/controls/Button.placard.md +14 -0
- package/src/ui/design-system/components/controls/Button.test.tsx +45 -0
- package/src/ui/design-system/components/controls/Button.tsx +41 -0
- package/src/ui/design-system/components/controls/IconButton.case.tsx +52 -0
- package/src/ui/design-system/components/controls/IconButton.css +67 -0
- package/src/ui/design-system/components/controls/IconButton.placard.md +13 -0
- package/src/ui/design-system/components/controls/IconButton.test.tsx +39 -0
- package/src/ui/design-system/components/controls/IconButton.tsx +47 -0
- package/src/ui/design-system/components/controls/Input.case.tsx +50 -0
- package/src/ui/design-system/components/controls/Input.css +52 -0
- package/src/ui/design-system/components/controls/Input.placard.md +12 -0
- package/src/ui/design-system/components/controls/Input.test.tsx +43 -0
- package/src/ui/design-system/components/controls/Input.tsx +45 -0
- package/src/ui/design-system/components/controls/Select.case.tsx +48 -0
- package/src/ui/design-system/components/controls/Select.css +44 -0
- package/src/ui/design-system/components/controls/Select.placard.md +15 -0
- package/src/ui/design-system/components/controls/Select.test.tsx +57 -0
- package/src/ui/design-system/components/controls/Select.tsx +58 -0
- package/src/ui/design-system/components/controls/SelectMenu.case.tsx +100 -0
- package/src/ui/design-system/components/controls/SelectMenu.css +72 -0
- package/src/ui/design-system/components/controls/SelectMenu.placard.md +18 -0
- package/src/ui/design-system/components/controls/SelectMenu.test.tsx +66 -0
- package/src/ui/design-system/components/controls/SelectMenu.tsx +377 -0
- package/src/ui/design-system/components/index.ts +66 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.case.tsx +44 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.tsx +51 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.case.tsx +38 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.case.tsx +24 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.case.tsx +27 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.placard.md +13 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.case.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.placard.md +7 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.case.tsx +20 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.tsx +33 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.case.tsx +56 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.placard.md +17 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.tsx +45 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.case.tsx +17 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.placard.md +16 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.tsx +39 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.case.tsx +26 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.tsx +42 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.case.tsx +23 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.placard.md +14 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.case.tsx +28 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.tsx +46 -0
- package/src/ui/design-system/components/primer-specimen/index.ts +31 -0
- package/src/ui/design-system/components/primer-specimen/styles.css +476 -0
- package/src/ui/design-system/components/shell/A11yPage.case.tsx +237 -0
- package/src/ui/design-system/components/shell/A11yPage.placard.md +15 -0
- package/src/ui/design-system/components/shell/CaseTemplate.case.tsx +32 -0
- package/src/ui/design-system/components/shell/CaseTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/CasesPage.case.tsx +141 -0
- package/src/ui/design-system/components/shell/CasesPage.placard.md +12 -0
- package/src/ui/design-system/components/shell/PrimerPage.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerPage.placard.md +3 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.case.tsx +57 -0
- package/src/ui/design-system/components/shell/ShellView.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.tsx +678 -0
- package/src/ui/design-system/components/shell/shell-fixtures.tsx +727 -0
- package/src/ui/design-system/components/showcase/A11yBadge.case.tsx +46 -0
- package/src/ui/design-system/components/showcase/A11yBadge.css +27 -0
- package/src/ui/design-system/components/showcase/A11yBadge.placard.md +11 -0
- package/src/ui/design-system/components/showcase/A11yBadge.test.tsx +31 -0
- package/src/ui/design-system/components/showcase/A11yBadge.tsx +41 -0
- package/src/ui/design-system/components/showcase/A11yPanel.case.tsx +121 -0
- package/src/ui/design-system/components/showcase/A11yPanel.css +198 -0
- package/src/ui/design-system/components/showcase/A11yPanel.placard.md +19 -0
- package/src/ui/design-system/components/showcase/A11yPanel.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/A11yPanel.tsx +144 -0
- package/src/ui/design-system/components/showcase/Chip.case.tsx +48 -0
- package/src/ui/design-system/components/showcase/Chip.css +51 -0
- package/src/ui/design-system/components/showcase/Chip.placard.md +13 -0
- package/src/ui/design-system/components/showcase/Chip.test.tsx +46 -0
- package/src/ui/design-system/components/showcase/Chip.tsx +54 -0
- package/src/ui/design-system/components/showcase/Eyebrow.case.tsx +30 -0
- package/src/ui/design-system/components/showcase/Eyebrow.css +16 -0
- package/src/ui/design-system/components/showcase/Eyebrow.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Eyebrow.test.tsx +38 -0
- package/src/ui/design-system/components/showcase/Eyebrow.tsx +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.case.tsx +35 -0
- package/src/ui/design-system/components/showcase/FlowNav.css +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.placard.md +13 -0
- package/src/ui/design-system/components/showcase/FlowNav.test.tsx +48 -0
- package/src/ui/design-system/components/showcase/FlowNav.tsx +58 -0
- package/src/ui/design-system/components/showcase/ImpactTag.case.tsx +19 -0
- package/src/ui/design-system/components/showcase/ImpactTag.css +36 -0
- package/src/ui/design-system/components/showcase/ImpactTag.placard.md +14 -0
- package/src/ui/design-system/components/showcase/ImpactTag.test.tsx +40 -0
- package/src/ui/design-system/components/showcase/ImpactTag.tsx +35 -0
- package/src/ui/design-system/components/showcase/NavItem.case.tsx +86 -0
- package/src/ui/design-system/components/showcase/NavItem.css +111 -0
- package/src/ui/design-system/components/showcase/NavItem.placard.md +13 -0
- package/src/ui/design-system/components/showcase/NavItem.test.tsx +65 -0
- package/src/ui/design-system/components/showcase/NavItem.tsx +95 -0
- package/src/ui/design-system/components/showcase/RenderAddress.case.tsx +21 -0
- package/src/ui/design-system/components/showcase/RenderAddress.css +35 -0
- package/src/ui/design-system/components/showcase/RenderAddress.placard.md +7 -0
- package/src/ui/design-system/components/showcase/RenderAddress.test.tsx +26 -0
- package/src/ui/design-system/components/showcase/RenderAddress.tsx +43 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.case.tsx +84 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.css +61 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.placard.md +21 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.tsx +75 -0
- package/src/ui/design-system/components/showcase/Sidebar.case.tsx +67 -0
- package/src/ui/design-system/components/showcase/Sidebar.css +6 -0
- package/src/ui/design-system/components/showcase/Sidebar.placard.md +14 -0
- package/src/ui/design-system/components/showcase/Sidebar.test.tsx +32 -0
- package/src/ui/design-system/components/showcase/Sidebar.tsx +30 -0
- package/src/ui/design-system/components/showcase/Stage.case.tsx +51 -0
- package/src/ui/design-system/components/showcase/Stage.css +91 -0
- package/src/ui/design-system/components/showcase/Stage.placard.md +15 -0
- package/src/ui/design-system/components/showcase/Stage.test.tsx +84 -0
- package/src/ui/design-system/components/showcase/Stage.tsx +97 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.case.tsx +81 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.css +169 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.placard.md +20 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.tsx +230 -0
- package/src/ui/design-system/components/showcase/Wordmark.case.tsx +42 -0
- package/src/ui/design-system/components/showcase/Wordmark.css +31 -0
- package/src/ui/design-system/components/showcase/Wordmark.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Wordmark.test.tsx +22 -0
- package/src/ui/design-system/components/showcase/Wordmark.tsx +22 -0
- package/src/ui/design-system/primer-specimens/brand.tsx +26 -0
- package/src/ui/design-system/primer-specimens/colors.tsx +83 -0
- package/src/ui/design-system/primer-specimens/components.tsx +308 -0
- package/src/ui/design-system/primer-specimens/foundations.tsx +71 -0
- package/src/ui/design-system/primer-specimens/index.ts +25 -0
- package/src/ui/design-system/primer-specimens/showcase.tsx +68 -0
- package/src/ui/design-system/primer-specimens/spacing.tsx +101 -0
- package/src/ui/design-system/primer-specimens/type.tsx +75 -0
- package/src/ui/design-system/primer.mdx +236 -0
- package/src/ui/design-system/styles.css +14 -0
- package/src/ui/design-system/tokens/colors.css +172 -0
- package/src/ui/design-system/tokens/fonts.css +18 -0
- package/src/ui/design-system/tokens/spacing.css +48 -0
- package/src/ui/design-system/tokens/typography.css +49 -0
- package/src/ui/markdown.test.tsx +54 -0
- package/src/ui/markdown.tsx +19 -0
- package/src/ui/primer-mount.tsx +76 -0
- package/src/ui/primer.css +175 -0
- package/src/ui/primer.tsx +277 -0
- package/src/ui/render-mount.tsx +284 -0
- package/src/ui/shell-core.test.ts +340 -0
- package/src/ui/shell-core.ts +295 -0
- package/src/ui/shell.tsx +60 -0
- package/src/ui/test-ids.ts +53 -0
- package/src/ui/use-shell.ts +1230 -0
package/docs/tweaks.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Tweaks
|
|
2
|
+
|
|
3
|
+
> Nav: [Quick start](quick-start.md) · [Writing cases](writing-cases.md) · [Hierarchy](hierarchy.md) · **Tweaks** · [Theming](theming.md) · [Documentation panel](documentation-panel.md) · [Writing placard docs](writing-placard-docs.md) · [Testing](testing.md) · [CLI](cli.md) · [AI agents](ai-agents.md) · [Configuration](configuration.md)
|
|
4
|
+
|
|
5
|
+
Tweaks are typed, interactive controls attached to a case. Instead of writing one variant per prop combination, declare the props as tweaks and let the viewer adjust them live.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { defineCases, tweak } from '@awarebydefault/display-case'
|
|
9
|
+
import { TweakControl } from './tweak-control'
|
|
10
|
+
|
|
11
|
+
export default defineCases('TweakControl', {
|
|
12
|
+
Playground: {
|
|
13
|
+
tweaks: {
|
|
14
|
+
label: tweak.text('Save changes'),
|
|
15
|
+
kind: tweak.choice(['text', 'boolean', 'choice'], 'text'),
|
|
16
|
+
disabled: tweak.boolean(false),
|
|
17
|
+
},
|
|
18
|
+
render: (t) => (
|
|
19
|
+
<TweakControl
|
|
20
|
+
kind={t.kind as 'text' | 'boolean' | 'choice'}
|
|
21
|
+
label={t.label}
|
|
22
|
+
disabled={t.disabled}
|
|
23
|
+
/>
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
}, { level: 'atom' })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
A tweaked case is an object with two keys: a `tweaks` schema and a `render` function that receives the resolved values.
|
|
30
|
+
|
|
31
|
+
## Control kinds
|
|
32
|
+
|
|
33
|
+
There are four serializable tweak builders, all imported from the `tweak` namespace:
|
|
34
|
+
|
|
35
|
+
| Builder | Resolved value type | Default argument |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `tweak.text(default?)` | `string` | optional, defaults to `''` |
|
|
38
|
+
| `tweak.boolean(default?)` | `boolean` | optional, defaults to `false` |
|
|
39
|
+
| `tweak.number(default?)` | `number` | optional, defaults to `0` |
|
|
40
|
+
| `tweak.choice(options, default)` | `string` | both required |
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
tweak.text('Hello') // text field
|
|
44
|
+
tweak.boolean(true) // toggle
|
|
45
|
+
tweak.number(8) // number field
|
|
46
|
+
tweak.choice(['sm', 'md', 'lg'], 'md') // select from fixed options
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The keys of the `tweaks` object become the control labels and the property names on the `render` argument.
|
|
50
|
+
|
|
51
|
+
## Values are URL-encoded
|
|
52
|
+
|
|
53
|
+
A tweak's current value is serialized into the render URL as `t.<name>`, so any tweaked state is a shareable, snapshottable link:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
/render/tweak-control/playground?theme=dark&t.label=Delete&t.kind=choice&t.disabled=1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Encoding rules:
|
|
60
|
+
|
|
61
|
+
- `boolean` — `1` / `true` is true; anything else is false.
|
|
62
|
+
- `number` — parsed with `Number(...)`.
|
|
63
|
+
- `text` and `choice` — used verbatim.
|
|
64
|
+
- A missing `t.<name>` falls back to the declared `default`.
|
|
65
|
+
|
|
66
|
+
This is what lets an AI agent or screenshot tool reproduce an exact tweaked state deterministically — see [AI agents](ai-agents.md).
|
|
67
|
+
|
|
68
|
+
## Typing
|
|
69
|
+
|
|
70
|
+
`choice` returns a plain `string` at runtime, so when feeding it back into a union-typed prop you may need a cast (as in the example above). The other kinds resolve to their natural types (`string`, `boolean`, `number`).
|
|
71
|
+
|
|
72
|
+
## When to reach for tweaks
|
|
73
|
+
|
|
74
|
+
- **Use a tweaked case** for an exploratory "playground" where many prop combinations matter.
|
|
75
|
+
- **Use plain cases** for the canonical, named variants you want to keep visible and snapshot in tests — these render with fixed inputs and are the stable surface for visual regression. See [Testing](testing.md).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Writing cases
|
|
2
|
+
|
|
3
|
+
> Nav: [Quick start](quick-start.md) · **Writing cases** · [Hierarchy](hierarchy.md) · [Tweaks](tweaks.md) · [Theming](theming.md) · [Documentation panel](documentation-panel.md) · [Writing placard docs](writing-placard-docs.md) · [Testing](testing.md) · [CLI](cli.md) · [AI agents](ai-agents.md) · [Configuration](configuration.md)
|
|
4
|
+
|
|
5
|
+
A case file is the unit of the showcase: a `*.case.tsx` file colocated with the component it demonstrates, default-exporting one call to `defineCases` or `defineFlow`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// src/components/tweak-control.case.tsx
|
|
9
|
+
import { defineCases } from '@awarebydefault/display-case'
|
|
10
|
+
import { TweakControl } from './tweak-control'
|
|
11
|
+
|
|
12
|
+
export default defineCases('TweakControl', {
|
|
13
|
+
Kinds: () => (
|
|
14
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
|
15
|
+
<TweakControl kind="text" label="Label" value="Save" />
|
|
16
|
+
<TweakControl kind="boolean" label="Disabled" value={false} />
|
|
17
|
+
<TweakControl kind="choice" label="Variant" options={['sm', 'md', 'lg']} value="md" />
|
|
18
|
+
</div>
|
|
19
|
+
),
|
|
20
|
+
Boolean: () => <TweakControl kind="boolean" label="Disabled" value={true} />,
|
|
21
|
+
Disabled: () => <TweakControl kind="text" label="Label" value="Save" disabled />,
|
|
22
|
+
}, { level: 'atom' })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## `defineCases(component, cases, meta?)`
|
|
26
|
+
|
|
27
|
+
| Argument | Type | Notes |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `component` | `string` | The display name shown in the sidebar. Its slug (kebab-case) forms the URL, e.g. `TweakControl` → `/c/tweak-control`. |
|
|
30
|
+
| `cases` | `Record<string, Case>` | Keyed by display name; **insertion order is preserved**. Each value is either a simple render thunk or a tweaked case (see below). |
|
|
31
|
+
| `meta.level` | `HierarchyLevel?` | One of `atom`, `molecule`, `organism`, `template`, `page` (`flow` is set automatically by `defineFlow`). Drives sidebar grouping. Omit to leave it "unclassified" (sorted last). See [Hierarchy](hierarchy.md). |
|
|
32
|
+
| `meta.area` | `string?` | Free-form layout tag passed to the [`decorator`](configuration.md#decorator) so it can wrap this case in app chrome (nav/header/footer). Display Case mandates no vocabulary — the decorator interprets the value. Takes precedence over folder-based detection via `sourcePath`; omit to fall back to that (or to render bare). |
|
|
33
|
+
|
|
34
|
+
### Two shapes of case
|
|
35
|
+
|
|
36
|
+
A **simple case** is a thunk returning a React node:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
Disabled: () => <TweakControl kind="text" label="Label" value="Save" disabled />,
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A **tweaked case** declares typed controls and receives their resolved values:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
Playground: {
|
|
46
|
+
tweaks: { label: tweak.text('Save') },
|
|
47
|
+
render: (t) => <TweakControl kind="text" label="Label" value={t.label} />,
|
|
48
|
+
},
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
See [Tweaks](tweaks.md) for the full control set.
|
|
52
|
+
|
|
53
|
+
### Order the default-landing variant first
|
|
54
|
+
|
|
55
|
+
Clicking a component in the sidebar navigates to its **first** case, so lead with the most exploratory variant — a tweaked `Playground` case, or a "do-anything" interactive demo (e.g. a stateful, clickable example). Keep isolated single-state variants (one `Disabled`, one `With error`) *after* it: those exist mainly for snapshots and visual-regression, not as the thing a reader should land on. Flow steps are the exception — order them in flow sequence (`defineFlow` below).
|
|
56
|
+
|
|
57
|
+
## `defineFlow(name, { steps })`
|
|
58
|
+
|
|
59
|
+
For behavioural multi-step flows — a wizard, a sign-in sequence — use `defineFlow`. Each step is an ordered, individually addressable, snapshottable state. A step may declare preset `tweaks`, `transitions` to other steps, and wire its injected `goto` into a presentational view's callbacks so an in-step button advances the flow in place.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// src/components/sign-in-flow.case.tsx
|
|
63
|
+
import { defineFlow } from '@awarebydefault/display-case'
|
|
64
|
+
import { RequestLink, CheckEmail, SignedIn } from './sign-in-screens'
|
|
65
|
+
|
|
66
|
+
export default defineFlow('Sign-in flow', {
|
|
67
|
+
steps: {
|
|
68
|
+
'Request link': {
|
|
69
|
+
transitions: ['Check email'],
|
|
70
|
+
render: ({ goto }) => <RequestLink onSubmit={() => goto('Check email')} />,
|
|
71
|
+
},
|
|
72
|
+
'Check email': {
|
|
73
|
+
transitions: ['Signed in'],
|
|
74
|
+
render: ({ goto }) => <CheckEmail onOpen={() => goto('Signed in')} />,
|
|
75
|
+
},
|
|
76
|
+
'Signed in': { render: () => <SignedIn /> },
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Keep the views pure: a step wires `goto` to the view's callbacks; the view never imports navigation, so the same view is reused in the real route. `goto(step, overrides?)` re-enters the target step with optional preset tweak values. A flow whose steps declare no transitions is a static, walkable sequence. A flow is always at the `flow` level. See [Hierarchy](hierarchy.md#flows) for how flows differ from regular cases.
|
|
82
|
+
|
|
83
|
+
`defineFlow` also accepts an optional `area` alongside `steps` — the same free-form layout tag as [`meta.area`](#definecasescomponent-cases-meta) — so a flow can be wrapped in app chrome by the decorator:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
export default defineFlow('Checkout', { area: 'app', steps: { /* … */ } })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Typed step values.** A bare step object has loosely-typed `values`. To read typed preset values (`values.error` as `boolean`), wrap the step in the `flowStep` helper, which infers the step's own tweak schema:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { defineFlow, flowStep, tweak } from '@awarebydefault/display-case'
|
|
93
|
+
|
|
94
|
+
export default defineFlow('Sign-in', {
|
|
95
|
+
steps: {
|
|
96
|
+
'Check email': flowStep({
|
|
97
|
+
tweaks: { error: tweak.boolean(false) },
|
|
98
|
+
render: ({ values, goto }) => (
|
|
99
|
+
<CheckEmail error={values.error} onVerify={() => goto('Signed in')} />
|
|
100
|
+
),
|
|
101
|
+
}),
|
|
102
|
+
'Signed in': { render: () => <SignedIn /> },
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`goto`/`transitions` targets are not key-checked at compile time — an unknown target renders the not-found step at runtime.
|
|
108
|
+
|
|
109
|
+
## Authoring rules
|
|
110
|
+
|
|
111
|
+
- **Default-export the definition.** A file with no valid default export (or one whose `component` is not a string) is skipped and reported as a load error; the rest still load.
|
|
112
|
+
- **No top-level side effects.** Render functions are lazy. The server imports the module to build the manifest without rendering — so don't call hooks, fetch, or touch the DOM at module top level.
|
|
113
|
+
- **Give an interactive specimen a distinct per-case `key`.** This is a foot-gun worth understanding. A controlled component needs a little wrapper that owns its state (`function Demo({ initial }) { const [v, setV] = useState(initial); … }`), and you'll reuse that one wrapper across several cases. But the browse chrome **swaps cases in place** — it re-renders one persistent root with `root.render()` and never unmounts (so theme/tweak changes don't flicker). React then sees the *same* `<Demo>` at the *same* position across cases and **keeps its `useState` value** instead of re-seeding from the new case's `initial`. Between cases whose props differ — a different selected id, or a disjoint set of options — the leaked value shows the wrong selection, or (if it isn't in the new options) *no* selection at all. Fix it by giving each case's wrapper a distinct `key` so React remounts it:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
function Demo({ options, initial }: { options: Opt[]; initial: string }) {
|
|
117
|
+
const [value, setValue] = useState(initial)
|
|
118
|
+
return <Toggle options={options} value={value} onChange={setValue} />
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default defineCases('Toggle', {
|
|
122
|
+
// A bare <Demo …/> here would carry the previous case's value across the swap.
|
|
123
|
+
Two: () => <Demo key="two" options={two} initial="b" />,
|
|
124
|
+
Five: () => <Demo key="five" options={five} initial="lg" />,
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A tweaked `Playground` that re-seeds from a tweak follows the same rule — key it by the seeding tweak (`key={`pg-${t.count}`}`) so changing the tweak remounts with the new `initial`. A specimen rendered in only *one* case is safe (switching to any sibling case mounts a different element, which remounts it anyway). The `interactive-cases-keyed` structure check flags a stateful wrapper reused across cases with a missing `key`; waive a deliberate exception with a `// display-case: allow-interactive-cases-keyed <reason>` comment.
|
|
129
|
+
- **Compose freely inside a render.** Layout wrappers, multiple instances, sample data — anything that returns a React node is fine.
|
|
130
|
+
- **One component (or flow) per file.** Coverage tooling expects a `<name>.case.tsx` sibling for each component module.
|
|
131
|
+
- **Edits are picked up on save; reload to see them.** The dev server watches case files and rebuilds on every change — including manifest-shape edits (case *order*, case/component *names*, hierarchy `level`, tweak schema). There is no in-page HMR, so reload the browser to pick up a rebuild. (The rebuild reads the manifest in a fresh subprocess because Bun caches ES modules by path for a process's lifetime; without that, an in-process re-import would return the stale module.)
|
|
132
|
+
|
|
133
|
+
## Coverage
|
|
134
|
+
|
|
135
|
+
The coverage check fails if a showcased component module has no colocated `*.case.tsx`. Wire it into your lint or CI step to keep every component browsable. To exempt a non-showcasable module, add a comment anywhere in the component file:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// display-case: no-case this is an internal helper, not a visual component
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## See also
|
|
142
|
+
|
|
143
|
+
- Runnable examples: [examples/](examples/) — a plain case, a tweaks case, and a multi-variant case.
|
|
144
|
+
- [Configuration](configuration.md) for `roots` globs that decide which files are discovered.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Writing placard docs
|
|
2
|
+
|
|
3
|
+
> Nav: [Quick start](quick-start.md) · [Writing cases](writing-cases.md) · [Hierarchy](hierarchy.md) · [Tweaks](tweaks.md) · [Theming](theming.md) · [Documentation panel](documentation-panel.md) · **Writing placard docs** · [Testing](testing.md) · [CLI](cli.md) · [AI agents](ai-agents.md) · [Configuration](configuration.md)
|
|
4
|
+
|
|
5
|
+
A `<component>.placard.md` is the component's **prose contract**: the one place that
|
|
6
|
+
tells a reader what the types can't. [Documentation panel](documentation-panel.md)
|
|
7
|
+
covers how the file is discovered and rendered; this guide covers what to *put in
|
|
8
|
+
it*. For a complete, annotated specimen see
|
|
9
|
+
[`examples/tweak-control.placard.md`](examples/tweak-control.placard.md).
|
|
10
|
+
|
|
11
|
+
## Who reads it, and why that decides everything
|
|
12
|
+
|
|
13
|
+
The primary reader is an **AI agent** assembling a UI; the secondary reader is a
|
|
14
|
+
human skimming the doc panel. Both arrive already holding two things you should
|
|
15
|
+
never restate:
|
|
16
|
+
|
|
17
|
+
- the **source** — every prop name, type, and default is in the `.tsx`;
|
|
18
|
+
- the **manifest** — every case, `renderUrl`, and `tweaks` schema is already
|
|
19
|
+
enumerated for machine readers (see [AI agents](ai-agents.md)).
|
|
20
|
+
|
|
21
|
+
So the placard doc earns its tokens only by carrying what *neither* of those can
|
|
22
|
+
express: **intent, judgement, and contract.** A good doc lets a reader choose the
|
|
23
|
+
component and use it correctly on the **first try, without opening the source.**
|
|
24
|
+
That is the whole bar.
|
|
25
|
+
|
|
26
|
+
> **The one principle:** document what the types can't. A signature says a prop is
|
|
27
|
+
> `(next: string[]) => void`; only prose says whether `next` is *the toggled item*
|
|
28
|
+
> or *the full next array*. Spend your words there.
|
|
29
|
+
|
|
30
|
+
## What to include
|
|
31
|
+
|
|
32
|
+
Ordered by value. Lead with the highest; stop when a reader could use the
|
|
33
|
+
component correctly without the source. Most atoms need only the first three.
|
|
34
|
+
|
|
35
|
+
1. **Identity line.** A bold name, an em-dash, and one sentence: *what it is* and
|
|
36
|
+
*the single most common reason to reach for it*. This is what shows when the
|
|
37
|
+
library is scanned, so it must stand alone. Lead with the conclusion.
|
|
38
|
+
|
|
39
|
+
2. **Canonical example.** One minimal, **correct, copy-pasteable** `tsx` snippet
|
|
40
|
+
of the idiomatic call — the common case, not every prop. Agents paste it
|
|
41
|
+
verbatim, so a wrong example is worse than none. Add a second snippet only when
|
|
42
|
+
a distinct mode (a different `kind`, a controlled vs. composed form) genuinely
|
|
43
|
+
needs one.
|
|
44
|
+
|
|
45
|
+
3. **Variants and when to pick each.** Map each variant/mode to its *meaning and
|
|
46
|
+
use*, not its type union. Name the default. Use a GFM table once there are more
|
|
47
|
+
than three; a sentence suffices below that. Restate semantics, never the
|
|
48
|
+
signature — semantics drift more slowly than types.
|
|
49
|
+
|
|
50
|
+
4. **Decision boundary — when *not* to use it.** The highest-value, most-skipped
|
|
51
|
+
content. Point to the sibling that fits the case you're excluding, by name, so
|
|
52
|
+
the reader navigates the library instead of misusing this part: *"for long or
|
|
53
|
+
searchable lists, reach for `Combobox`"*; *"inline notice, not a transient
|
|
54
|
+
toast — use `Toast` for those."* This is the single biggest defence against an
|
|
55
|
+
agent picking the wrong primitive.
|
|
56
|
+
|
|
57
|
+
5. **State & callback contract.** Controlled or uncontrolled? What does each
|
|
58
|
+
callback *emit* — the changed item or the whole next value? What fires on
|
|
59
|
+
mount? None of this is visible in a type and all of it is guessed wrong.
|
|
60
|
+
|
|
61
|
+
6. **Composition & required wrappers.** Relationships the type system permits but
|
|
62
|
+
the design requires: *"wrap in `FormField` for the label and error"*; *"at most
|
|
63
|
+
one Other choice per question."* Constraints that aren't compile errors.
|
|
64
|
+
|
|
65
|
+
7. **Accessibility behaviour.** What the component handles for you (so the reader
|
|
66
|
+
doesn't double up a `role`) and what the caller **must** supply (a label, alt
|
|
67
|
+
text). State it in one line; omit if there's nothing non-obvious.
|
|
68
|
+
|
|
69
|
+
8. **Gotchas & anti-patterns.** The non-obvious rule and the tempting wrong use.
|
|
70
|
+
One bullet each; skip the section if there are none.
|
|
71
|
+
|
|
72
|
+
## What to leave out
|
|
73
|
+
|
|
74
|
+
Every line here is either drift waiting to happen or a duplicate of a better
|
|
75
|
+
source:
|
|
76
|
+
|
|
77
|
+
- **Prop tables that retype the TypeScript.** Restate a prop only to add meaning
|
|
78
|
+
the type lacks. The source is the signature of record.
|
|
79
|
+
- **The case list, render URLs, or tweak schema.** The manifest owns these and
|
|
80
|
+
stays in sync automatically; a copy here only rots.
|
|
81
|
+
- **Styling internals** — CSS variables, class names, DOM structure, token math.
|
|
82
|
+
- **Implementation detail** — how it works inside. Document the contract, not the
|
|
83
|
+
mechanism.
|
|
84
|
+
- **Changelog or version history.** That is what git is for.
|
|
85
|
+
- **Anything the name already says.** `<Spinner>` spins.
|
|
86
|
+
|
|
87
|
+
## Form: write for the medium
|
|
88
|
+
|
|
89
|
+
The doc renders as **CommonMark + GFM** (tables, task lists, strikethrough,
|
|
90
|
+
autolinks) — but with [two limits](documentation-panel.md#two-intentional-limits):
|
|
91
|
+
|
|
92
|
+
- **No raw HTML.** Embedded `<div>`/`<span>`/`<style>` is stripped, not rendered.
|
|
93
|
+
Stay in Markdown.
|
|
94
|
+
- **No syntax highlighting.** Fenced blocks are plain `<pre>`. Use fences for
|
|
95
|
+
*structure*, never to imply colour.
|
|
96
|
+
|
|
97
|
+
And because the file is ingested into a context window as often as it is read on a
|
|
98
|
+
screen:
|
|
99
|
+
|
|
100
|
+
- **Be dense.** Every sentence earns its tokens. The model of a great doc is
|
|
101
|
+
short — see [`Button.placard.md`](../src/ui/design-system/components/controls/Button.placard.md):
|
|
102
|
+
identity line, example, one variant sentence. Scale up only for real complexity.
|
|
103
|
+
- **Be scannable.** Bold lead line always; `##` headings only once the doc has
|
|
104
|
+
enough distinct sections to need them. A five-line atom needs no headings.
|
|
105
|
+
- **Be present-tense and declarative**, in the calm house voice — no marketing,
|
|
106
|
+
no hedging.
|
|
107
|
+
- **Keep examples runnable and current.** They are the most-copied lines in the
|
|
108
|
+
file; treat a stale example as a bug.
|
|
109
|
+
|
|
110
|
+
## Length is a function of complexity, not a target
|
|
111
|
+
|
|
112
|
+
| Component shape | Doc shape |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| Atom, 1–2 props, one behaviour | Identity line + one example + a variant sentence. ~5 lines. |
|
|
115
|
+
| Several variants or modes | Add a variant table and a decision-boundary line. |
|
|
116
|
+
| Non-obvious callback or composition | Add the state/callback contract and required-wrapper notes. |
|
|
117
|
+
| Subtle a11y or footguns | Add an accessibility line and a gotchas bullet. |
|
|
118
|
+
|
|
119
|
+
Stop at the point where an agent could use the component correctly without the
|
|
120
|
+
source. Past that, more words are liability, not value.
|
|
121
|
+
|
|
122
|
+
## Pages, templates, and flows
|
|
123
|
+
|
|
124
|
+
Everything above assumes a **reusable component** (atom / molecule / organism) —
|
|
125
|
+
something you instantiate, so the doc revolves around the *call*: identity,
|
|
126
|
+
canonical example, variants, decision boundary, prop/callback contract.
|
|
127
|
+
Template-, page-, and flow-level exhibits aren't parts you instantiate, so that
|
|
128
|
+
shape mostly doesn't apply. There is **no idiomatic call to paste, no prop API,
|
|
129
|
+
no variant union** — drop the canonical-example snippet, the variant table, and
|
|
130
|
+
the state/callback contract. The one principle holds, but what you document turns
|
|
131
|
+
**behavioural / structural**, and the [manifest](ai-agents.md) already enumerates
|
|
132
|
+
the cases / steps / `renderUrl`s / `transitions` — restate their *meaning*, never
|
|
133
|
+
re-list them.
|
|
134
|
+
|
|
135
|
+
- **Template** (page-level layout, no real data) — document **expected usage**:
|
|
136
|
+
the regions/slots the layout defines and what each is meant to hold, and when to
|
|
137
|
+
reach for this layout over a sibling. Structure, not data, not behaviour.
|
|
138
|
+
- **Page** (a template filled with representative content) — document **expected
|
|
139
|
+
behaviour**: what screen it represents, what a viewer can do on it, the states
|
|
140
|
+
it exercises, and which template + content it composes. What it *does*.
|
|
141
|
+
- **Flow** (a multi-step journey) — document **expected behaviour end to end**:
|
|
142
|
+
the ordered steps and what each represents, and — critically — *what advances
|
|
143
|
+
the flow between them* (the trigger on each transition), plus entry/exit and any
|
|
144
|
+
preset step state ([Hierarchy](hierarchy.md#flows) explains the flow model).
|
|
145
|
+
|
|
146
|
+
Skeletons for the three:
|
|
147
|
+
|
|
148
|
+
````md
|
|
149
|
+
<!-- template -->
|
|
150
|
+
**Name** — the layout it is; the one reason to reach for it.
|
|
151
|
+
|
|
152
|
+
Regions: `header` (…) · `main` (…) · `aside` (…) — what each holds.
|
|
153
|
+
|
|
154
|
+
Use this layout for X; for Y reach for `OtherTemplate`.
|
|
155
|
+
````
|
|
156
|
+
|
|
157
|
+
````md
|
|
158
|
+
<!-- page -->
|
|
159
|
+
**Name** — the screen it represents; what it demonstrates.
|
|
160
|
+
|
|
161
|
+
Behaviour: what the viewer can do; the states shown.
|
|
162
|
+
|
|
163
|
+
Composes `SomeTemplate` with `…` content.
|
|
164
|
+
````
|
|
165
|
+
|
|
166
|
+
````md
|
|
167
|
+
<!-- flow -->
|
|
168
|
+
**Name** — the journey and its outcome.
|
|
169
|
+
|
|
170
|
+
Steps, in order: `step-one` (…) → `step-two` (…) → `done` (…).
|
|
171
|
+
|
|
172
|
+
Advances when: `step-one`'s submit → `step-two`; … . Entry: … . Preset: … .
|
|
173
|
+
````
|
|
174
|
+
|
|
175
|
+
## A skeleton to copy
|
|
176
|
+
|
|
177
|
+
````md
|
|
178
|
+
**ComponentName** — what it is; the one reason to reach for it.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<ComponentName prop="idiomatic" onChange={handle} />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Variants: `default` (…, the default) · `alt` (…). Restate what each *means*.
|
|
185
|
+
|
|
186
|
+
Use this for X. For Y, reach for `SiblingComponent` instead.
|
|
187
|
+
|
|
188
|
+
Controlled — pass `value`/`onChange`; `onChange` emits the exact contract.
|
|
189
|
+
Wrap in `FormField` for a label and error. Handles `role=…` itself.
|
|
190
|
+
````
|
|
191
|
+
|
|
192
|
+
Drop any line that would only restate the type or the name. The
|
|
193
|
+
[canonical example](examples/tweak-control.placard.md) fills this skeleton in for a
|
|
194
|
+
real component, with margin notes on each choice.
|
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@awarebydefault/display-case",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Bun-native, AI-friendly component showcase — a lightweight alternative to Storybook.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jake Uskoski <jake@awarebydefault.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"homepage": "https://github.com/AwareByDefault/display-case#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/AwareByDefault/display-case.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/AwareByDefault/display-case/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"bun",
|
|
19
|
+
"component",
|
|
20
|
+
"showcase",
|
|
21
|
+
"storybook",
|
|
22
|
+
"storybook-alternative",
|
|
23
|
+
"react",
|
|
24
|
+
"ssr",
|
|
25
|
+
"design-system",
|
|
26
|
+
"ai-agents",
|
|
27
|
+
"preview",
|
|
28
|
+
"playground",
|
|
29
|
+
"visual-regression",
|
|
30
|
+
"accessibility"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"bun": ">=1.2.0"
|
|
34
|
+
},
|
|
35
|
+
"packageManager": "bun@1.3.14",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"main": "./src/index.ts",
|
|
41
|
+
"types": "./src/index.ts",
|
|
42
|
+
"bin": {
|
|
43
|
+
"display-case": "src/cli.ts"
|
|
44
|
+
},
|
|
45
|
+
"exports": {
|
|
46
|
+
".": "./src/index.ts",
|
|
47
|
+
"./tokens-check": "./src/checks/tokens-check.ts",
|
|
48
|
+
"./prod-server": "./src/server/prod-server.ts"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"src",
|
|
52
|
+
"skills",
|
|
53
|
+
"docs",
|
|
54
|
+
"display-case.prompt.md",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"setup": "bun scripts/setup.ts",
|
|
60
|
+
"clean": "rm -rf .display-case dist dist-showcase",
|
|
61
|
+
"dev": "bun src/cli.ts . --dev",
|
|
62
|
+
"display-case": "bun src/cli.ts .",
|
|
63
|
+
"display-case:check": "bun src/cli.ts check .",
|
|
64
|
+
"lint": "biome check . && bun tools/lint/index.ts",
|
|
65
|
+
"lint:fix": "biome check --write . && bun tools/lint/index.ts --fix",
|
|
66
|
+
"lint:checks": "bun tools/lint/index.ts",
|
|
67
|
+
"typecheck": "tsc --noEmit",
|
|
68
|
+
"openspec": "openspec",
|
|
69
|
+
"check": "bun src/cli.ts check . --structure --tokens --ssr",
|
|
70
|
+
"baselines:record": "bun scripts/record-baselines.ts",
|
|
71
|
+
"test": "bun test",
|
|
72
|
+
"test:container": "bun test ./test/publish-container.test.ts",
|
|
73
|
+
"e2e": "playwright test",
|
|
74
|
+
"e2e:headed": "playwright test --headed",
|
|
75
|
+
"e2e:install": "playwright install chromium",
|
|
76
|
+
"release": "semantic-release",
|
|
77
|
+
"release:dry": "semantic-release --dry-run --no-ci",
|
|
78
|
+
"prepare": "husky"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"react": "^19",
|
|
82
|
+
"react-dom": "^19"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"@parcel/watcher": "^2.5.6",
|
|
86
|
+
"markdown-to-jsx": "^9.8.2"
|
|
87
|
+
},
|
|
88
|
+
"optionalDependencies": {
|
|
89
|
+
"@axe-core/playwright": "^4.10.1",
|
|
90
|
+
"pixelmatch": "^6.0.0",
|
|
91
|
+
"playwright": "^1.50.1",
|
|
92
|
+
"pngjs": "^7.0.0"
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@biomejs/biome": "^2.5.0",
|
|
96
|
+
"@commitlint/cli": "^19.6.0",
|
|
97
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
98
|
+
"@emotion/cache": "^11.14.0",
|
|
99
|
+
"@emotion/react": "^11.14.0",
|
|
100
|
+
"@emotion/server": "^11.11.0",
|
|
101
|
+
"@fission-ai/openspec": "1.4.1",
|
|
102
|
+
"@playwright/test": "^1.60.0",
|
|
103
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
104
|
+
"@semantic-release/git": "^10.0.1",
|
|
105
|
+
"@types/bun": "^1.2.0",
|
|
106
|
+
"@types/pngjs": "^6.0.5",
|
|
107
|
+
"@types/react": "^19",
|
|
108
|
+
"@types/react-dom": "^19",
|
|
109
|
+
"husky": "^9.1.7",
|
|
110
|
+
"semantic-release": "^25.0.5",
|
|
111
|
+
"typescript": "^5.7.0"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# display-case-author-case
|
|
2
|
+
|
|
3
|
+
Scaffold a `*.case.tsx` for a component that doesn't have one yet.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Reads a component's source for its real props, then writes a colocated case file that default-exports `defineCases(...)` — a `Default`, the meaningful variants, and a `Playground` with typed tweaks — tagged with the right Atomic Design `level`. The result shows up in the showcase and satisfies the `display-case-coverage` lint.
|
|
8
|
+
|
|
9
|
+
## When it triggers
|
|
10
|
+
|
|
11
|
+
The coverage check fails for a component, a new shared component is added, or someone asks to "add a case" / "showcase this component".
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. Read `<name>.tsx` (and `<name>.placard.md`) for exports and prop types.
|
|
16
|
+
2. Write `<name>.case.tsx` with variants + a tweaks playground, choosing the hierarchy `level` by composition.
|
|
17
|
+
3. Keep cases side-effect-free (lazy thunks; stateful demos as inner components).
|
|
18
|
+
4. Verify with `bun run display-case`; the `display-case-coverage` lint passes.
|
|
19
|
+
|
|
20
|
+
Authoring spec: [`../../display-case.prompt.md`](../../display-case.prompt.md).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: display-case-author-case
|
|
3
|
+
description: >
|
|
4
|
+
Write a Display Case *.case.tsx file for a component that lacks one, so it
|
|
5
|
+
appears in the showcase and passes the display-case-coverage lint. Use when
|
|
6
|
+
the coverage check fails, when adding a new shared component, or when asked to
|
|
7
|
+
"add a case", "showcase this component", or "cover <component> in Display Case".
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Author a colocated `*.case.tsx` for a component so it shows up in Display Case.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Read the component source** (`<name>.tsx`) to get the exact exported name and prop types. Read the sibling `<name>.placard.md` if present for realistic usage.
|
|
15
|
+
2. **Create `<name>.case.tsx`** next to it, default-exporting `defineCases`:
|
|
16
|
+
```tsx
|
|
17
|
+
import { defineCases, tweak } from '@awarebydefault/display-case'
|
|
18
|
+
import { TweakControl } from './tweak-control'
|
|
19
|
+
|
|
20
|
+
export default defineCases(
|
|
21
|
+
'TweakControl',
|
|
22
|
+
{
|
|
23
|
+
Default: () => <TweakControl kind="text" label="Label" />,
|
|
24
|
+
Variants: () => (/* one instance per meaningful variant */),
|
|
25
|
+
Playground: {
|
|
26
|
+
tweaks: { label: tweak.text('Variant'), disabled: tweak.boolean(false) },
|
|
27
|
+
render: (t) => <TweakControl kind="text" label={t.label} disabled={t.disabled} />,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{ level: 'atom' }, // atom|molecule|organism|template|page
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
3. **Pick the hierarchy `level`** by composition: primitives→`atom`, small composites→`molecule`, sections→`organism`, layouts→`template`, full screens→`page`. A behavioural multi-step flow uses `defineFlow(name, { steps, area? })` instead (level `flow`). For a `page`/`flow` case that should render inside app chrome (nav/header), add a free-form `area` tag — `defineCases(name, cases, { level: 'page', area: 'app' })` or `defineFlow(name, { area: 'app', steps })` — which the package's decorator maps to a layout (it overrides folder-based detection via the case's path).
|
|
34
|
+
4. **Add tweaks** for the interesting props (`tweak.text/boolean/number/choice`); cast a `choice` value into a union-typed prop.
|
|
35
|
+
5. **Keep it side-effect-free**: cases are lazy thunks. For controlled components, define a tiny stateful demo component above the export and reference it.
|
|
36
|
+
6. **Verify**: `bun run display-case` shows it; the `display-case-coverage` lint passes.
|
|
37
|
+
|
|
38
|
+
## Reference
|
|
39
|
+
|
|
40
|
+
`../../display-case.prompt.md` is the authoring spec; `../../docs/writing-cases.md`, `../../docs/hierarchy.md`, and `../../docs/tweaks.md` go deeper.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# display-case-author-placard-doc
|
|
2
|
+
|
|
3
|
+
Write a `<component>.placard.md` — the prose doc panel — that lets a reader use the component correctly without opening its source.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Reads a component's source for its real props, defaults, and callback contracts (and its case file for variants), then writes a colocated `.placard.md` following the [Writing placard docs](../../docs/writing-placard-docs.md) best practices: an identity line, a copy-pasteable canonical example, variant semantics, the decision boundary to sibling components, the state/callback contract, and composition/a11y notes — and deliberately *omits* anything the source, the manifest, or the component name already says.
|
|
8
|
+
|
|
9
|
+
## When it triggers
|
|
10
|
+
|
|
11
|
+
A new shared component is added, a component has a `.case.tsx` but no `.placard.md`, or someone asks to "write a placard doc", "document this component", "add a doc panel", or "write usage docs for `<component>`".
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. Read `<name>.tsx` (props, defaults, what callbacks emit) and `<name>.case.tsx` (variants); improve an existing `.placard.md` rather than replace it.
|
|
16
|
+
2. Find the decision boundary — the sibling components this one is easily confused with — by scanning the package's component inventory.
|
|
17
|
+
3. Draft top-down, highest value first (identity → example → variants → decision boundary → state/callback contract → composition/a11y → gotchas), stopping once the source is unnecessary.
|
|
18
|
+
4. Restate meaning, never type signatures; cut prop tables, case/render-URL lists, styling internals, and changelogs.
|
|
19
|
+
5. Write for the medium: GFM, no raw HTML, no syntax highlighting, dense.
|
|
20
|
+
6. Verify it renders in the doc panel and that every example is correct and copy-pasteable.
|
|
21
|
+
|
|
22
|
+
Unlike a `.case.tsx`, a `.placard.md` is not enforced by any lint — its only value is quality, so the bar is applied by judgement.
|
|
23
|
+
|
|
24
|
+
Authoring guide: [`../../docs/writing-placard-docs.md`](../../docs/writing-placard-docs.md). Annotated specimen: [`../../docs/examples/tweak-control.placard.md`](../../docs/examples/tweak-control.placard.md).
|