@auto-engineer/component-implementor-react 1.98.0 → 1.100.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +92 -0
- package/dist/src/commands/implement-component.d.ts +19 -0
- package/dist/src/commands/implement-component.d.ts.map +1 -1
- package/dist/src/commands/implement-component.js +109 -30
- package/dist/src/commands/implement-component.js.map +1 -1
- package/dist/src/commands/implement-component.test.js +259 -69
- package/dist/src/commands/implement-component.test.js.map +1 -1
- package/dist/src/extract-exports.d.ts +6 -0
- package/dist/src/extract-exports.d.ts.map +1 -0
- package/dist/src/extract-exports.js +46 -0
- package/dist/src/extract-exports.js.map +1 -0
- package/dist/src/generate-story-deterministic.d.ts +30 -0
- package/dist/src/generate-story-deterministic.d.ts.map +1 -0
- package/dist/src/generate-story-deterministic.js +229 -0
- package/dist/src/generate-story-deterministic.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/pipeline/run-pipeline.d.ts +69 -0
- package/dist/src/pipeline/run-pipeline.d.ts.map +1 -0
- package/dist/src/pipeline/run-pipeline.js +78 -0
- package/dist/src/pipeline/run-pipeline.js.map +1 -0
- package/dist/src/pipeline/run-pipeline.test.d.ts +2 -0
- package/dist/src/pipeline/run-pipeline.test.d.ts.map +1 -0
- package/dist/src/pipeline/run-pipeline.test.js +247 -0
- package/dist/src/pipeline/run-pipeline.test.js.map +1 -0
- package/dist/src/pipeline/steps/generate-component.d.ts +4 -0
- package/dist/src/pipeline/steps/generate-component.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-component.js +50 -0
- package/dist/src/pipeline/steps/generate-component.js.map +1 -0
- package/dist/src/pipeline/steps/generate-component.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-component.test.js +106 -0
- package/dist/src/pipeline/steps/generate-component.test.js.map +1 -0
- package/dist/src/pipeline/steps/generate-story.d.ts +3 -0
- package/dist/src/pipeline/steps/generate-story.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-story.js +14 -0
- package/dist/src/pipeline/steps/generate-story.js.map +1 -0
- package/dist/src/pipeline/steps/generate-story.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-story.test.js +41 -0
- package/dist/src/pipeline/steps/generate-story.test.js.map +1 -0
- package/dist/src/pipeline/steps/generate-test.d.ts +4 -0
- package/dist/src/pipeline/steps/generate-test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-test.js +19 -0
- package/dist/src/pipeline/steps/generate-test.js.map +1 -0
- package/dist/src/pipeline/steps/generate-test.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/generate-test.test.js +60 -0
- package/dist/src/pipeline/steps/generate-test.test.js.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/lint-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.js +45 -0
- package/dist/src/pipeline/steps/lint-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.js +119 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/story-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.js +34 -0
- package/dist/src/pipeline/steps/story-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.js +94 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.d.ts +3 -0
- package/dist/src/pipeline/steps/storybook-test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.js +22 -0
- package/dist/src/pipeline/steps/storybook-test.js.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.test.d.ts +2 -0
- package/dist/src/pipeline/steps/storybook-test.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.test.js +66 -0
- package/dist/src/pipeline/steps/storybook-test.test.js.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/test-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.js +44 -0
- package/dist/src/pipeline/steps/test-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.js +168 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/type-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.js +43 -0
- package/dist/src/pipeline/steps/type-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.js +112 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/visual-test.d.ts +3 -0
- package/dist/src/pipeline/steps/visual-test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/visual-test.js +4 -0
- package/dist/src/pipeline/steps/visual-test.js.map +1 -0
- package/dist/src/pipeline/steps/visual-test.test.d.ts +2 -0
- package/dist/src/pipeline/steps/visual-test.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/visual-test.test.js +9 -0
- package/dist/src/pipeline/steps/visual-test.test.js.map +1 -0
- package/dist/src/project-context.d.ts +10 -0
- package/dist/src/project-context.d.ts.map +1 -0
- package/dist/src/project-context.js +178 -0
- package/dist/src/project-context.js.map +1 -0
- package/dist/src/prompt.d.ts +39 -7
- package/dist/src/prompt.d.ts.map +1 -1
- package/dist/src/prompt.js +233 -23
- package/dist/src/prompt.js.map +1 -1
- package/dist/src/prompt.test.js +154 -9
- package/dist/src/prompt.test.js.map +1 -1
- package/dist/src/scaffold.d.ts +49 -0
- package/dist/src/scaffold.d.ts.map +1 -0
- package/dist/src/scaffold.js +208 -0
- package/dist/src/scaffold.js.map +1 -0
- package/dist/src/tools/lint-runner.d.ts +7 -0
- package/dist/src/tools/lint-runner.d.ts.map +1 -0
- package/dist/src/tools/lint-runner.js +48 -0
- package/dist/src/tools/lint-runner.js.map +1 -0
- package/dist/src/tools/lint-runner.test.d.ts +2 -0
- package/dist/src/tools/lint-runner.test.d.ts.map +1 -0
- package/dist/src/tools/lint-runner.test.js +90 -0
- package/dist/src/tools/lint-runner.test.js.map +1 -0
- package/dist/src/tools/storybook-runner.d.ts +6 -0
- package/dist/src/tools/storybook-runner.d.ts.map +1 -0
- package/dist/src/tools/storybook-runner.js +25 -0
- package/dist/src/tools/storybook-runner.js.map +1 -0
- package/dist/src/tools/storybook-runner.test.d.ts +2 -0
- package/dist/src/tools/storybook-runner.test.d.ts.map +1 -0
- package/dist/src/tools/storybook-runner.test.js +43 -0
- package/dist/src/tools/storybook-runner.test.js.map +1 -0
- package/dist/src/tools/test-runner.d.ts +9 -0
- package/dist/src/tools/test-runner.d.ts.map +1 -0
- package/dist/src/tools/test-runner.js +74 -0
- package/dist/src/tools/test-runner.js.map +1 -0
- package/dist/src/tools/test-runner.test.d.ts +2 -0
- package/dist/src/tools/test-runner.test.d.ts.map +1 -0
- package/dist/src/tools/test-runner.test.js +177 -0
- package/dist/src/tools/test-runner.test.js.map +1 -0
- package/dist/src/tools/type-checker.d.ts +6 -0
- package/dist/src/tools/type-checker.d.ts.map +1 -0
- package/dist/src/tools/type-checker.js +36 -0
- package/dist/src/tools/type-checker.js.map +1 -0
- package/dist/src/tools/type-checker.test.d.ts +2 -0
- package/dist/src/tools/type-checker.test.d.ts.map +1 -0
- package/dist/src/tools/type-checker.test.js +96 -0
- package/dist/src/tools/type-checker.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/inputs/model-a/spec-deltas.json +1460 -0
- package/inputs/model-b/spec-deltas.json +1424 -0
- package/inputs/model-c/spec-deltas.json +1432 -0
- package/inputs/model-d/spec-deltas.json +967 -0
- package/inputs/model-e/spec-deltas.json +2292 -0
- package/ketchup-plan.md +43 -8
- package/package.json +3 -3
- package/scoring-heuristic.md +138 -0
- package/scripts/improve.ts +23 -18
- package/src/commands/implement-component.test.ts +309 -76
- package/src/commands/implement-component.ts +155 -31
- package/src/extract-exports.ts +53 -0
- package/src/generate-story-deterministic.ts +267 -0
- package/src/index.ts +12 -0
- package/src/pipeline/run-pipeline.test.ts +292 -0
- package/src/pipeline/run-pipeline.ts +160 -0
- package/src/pipeline/steps/generate-component.test.ts +130 -0
- package/src/pipeline/steps/generate-component.ts +60 -0
- package/src/pipeline/steps/generate-story.test.ts +54 -0
- package/src/pipeline/steps/generate-story.ts +17 -0
- package/src/pipeline/steps/generate-test.test.ts +75 -0
- package/src/pipeline/steps/generate-test.ts +25 -0
- package/src/pipeline/steps/lint-fix-loop.test.ts +155 -0
- package/src/pipeline/steps/lint-fix-loop.ts +59 -0
- package/src/pipeline/steps/story-fix-loop.test.ts +123 -0
- package/src/pipeline/steps/story-fix-loop.ts +47 -0
- package/src/pipeline/steps/storybook-test.test.ts +82 -0
- package/src/pipeline/steps/storybook-test.ts +27 -0
- package/src/pipeline/steps/test-fix-loop.test.ts +201 -0
- package/src/pipeline/steps/test-fix-loop.ts +56 -0
- package/src/pipeline/steps/type-fix-loop.test.ts +145 -0
- package/src/pipeline/steps/type-fix-loop.ts +55 -0
- package/src/pipeline/steps/visual-test.test.ts +10 -0
- package/src/pipeline/steps/visual-test.ts +5 -0
- package/src/project-context.ts +205 -0
- package/src/prompt.test.ts +174 -8
- package/src/prompt.ts +301 -23
- package/src/scaffold.ts +281 -0
- package/src/tools/lint-runner.test.ts +112 -0
- package/src/tools/lint-runner.ts +52 -0
- package/src/tools/storybook-runner.test.ts +53 -0
- package/src/tools/storybook-runner.ts +29 -0
- package/src/tools/test-runner.test.ts +213 -0
- package/src/tools/test-runner.ts +84 -0
- package/src/tools/type-checker.test.ts +120 -0
- package/src/tools/type-checker.ts +42 -0
- package/vitest.config.ts +9 -1
- package/dist/src/generate-component.d.ts +0 -4
- package/dist/src/generate-component.d.ts.map +0 -1
- package/dist/src/generate-component.js +0 -14
- package/dist/src/generate-component.js.map +0 -1
- package/dist/src/generate-component.test.d.ts.map +0 -1
- package/dist/src/generate-component.test.js +0 -73
- package/dist/src/generate-component.test.js.map +0 -1
- package/dist/src/generate-story.d.ts +0 -4
- package/dist/src/generate-story.d.ts.map +0 -1
- package/dist/src/generate-story.js +0 -14
- package/dist/src/generate-story.js.map +0 -1
- package/dist/src/generate-story.test.d.ts.map +0 -1
- package/dist/src/generate-story.test.js +0 -58
- package/dist/src/generate-story.test.js.map +0 -1
- package/dist/src/generate-test.d.ts +0 -4
- package/dist/src/generate-test.d.ts.map +0 -1
- package/dist/src/generate-test.js +0 -14
- package/dist/src/generate-test.js.map +0 -1
- package/dist/src/generate-test.test.d.ts.map +0 -1
- package/dist/src/generate-test.test.js +0 -77
- package/dist/src/generate-test.test.js.map +0 -1
- package/dist/src/reconcile.d.ts +0 -8
- package/dist/src/reconcile.d.ts.map +0 -1
- package/dist/src/reconcile.js +0 -18
- package/dist/src/reconcile.js.map +0 -1
- package/dist/src/reconcile.test.d.ts +0 -2
- package/dist/src/reconcile.test.d.ts.map +0 -1
- package/dist/src/reconcile.test.js +0 -108
- package/dist/src/reconcile.test.js.map +0 -1
- package/src/generate-component.test.ts +0 -89
- package/src/generate-component.ts +0 -16
- package/src/generate-story.test.ts +0 -71
- package/src/generate-story.ts +0 -16
- package/src/generate-test.test.ts +0 -93
- package/src/generate-test.ts +0 -16
- package/src/reconcile.test.ts +0 -127
- package/src/reconcile.ts +0 -27
- /package/dist/src/{generate-component.test.d.ts → pipeline/steps/generate-component.test.d.ts} +0 -0
- /package/dist/src/{generate-story.test.d.ts → pipeline/steps/generate-story.test.d.ts} +0 -0
- /package/dist/src/{generate-test.test.d.ts → pipeline/steps/generate-test.test.d.ts} +0 -0
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"componentId": "board-card",
|
|
4
|
+
"componentName": "BoardCard",
|
|
5
|
+
"isNew": true,
|
|
6
|
+
"atomicType": "molecule",
|
|
7
|
+
"composes": ["ui-components-card"],
|
|
8
|
+
"specDeltas": {
|
|
9
|
+
"structure": [
|
|
10
|
+
"Composes Card with CardHeader showing title, CardContent showing description, and CardFooter with edit button.",
|
|
11
|
+
"Uses semantic <article> for the root element."
|
|
12
|
+
],
|
|
13
|
+
"rendering": ["Renders card data from props: title and description.", "In ready state, shows full card content."],
|
|
14
|
+
"interaction": [
|
|
15
|
+
"On drag start, calls onDragStart: (cardId: string) => void.",
|
|
16
|
+
"On drag end, calls onDragEnd: (cardId: string, newListId: string) => void.",
|
|
17
|
+
"Clicking edit button calls onEdit: (cardId: string) => void, which navigates to edit-card scene."
|
|
18
|
+
],
|
|
19
|
+
"styling": [
|
|
20
|
+
"Root uses `w-full rounded-md border border-border bg-background p-4 shadow-sm hover:shadow-md transition-shadow` consistent with Card.",
|
|
21
|
+
"Header uses `text-lg font-semibold tracking-tight text-foreground` for title.",
|
|
22
|
+
"Content uses `text-sm text-muted-foreground mt-2` for description.",
|
|
23
|
+
"Footer uses `mt-4 flex justify-end` with edit button `text-sm text-primary hover:underline`.",
|
|
24
|
+
"Draggable state adds `cursor-grab active:cursor-grabbing` with hover `ring-1 ring-accent`.",
|
|
25
|
+
"Hover effect wrapped in `@media (hover: hover) and (pointer: fine) { hover:bg-accent/10 }` with CSS `transition: background-color 150ms ease`.",
|
|
26
|
+
"Touch target for edit button uses `min-h-11 min-w-11 touch-action: manipulation`."
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"props": [
|
|
30
|
+
{
|
|
31
|
+
"name": "card",
|
|
32
|
+
"type": "{ id: string; title: string; description: string; listId: string }",
|
|
33
|
+
"required": true,
|
|
34
|
+
"description": "Data for the card.",
|
|
35
|
+
"category": "data"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "onEdit",
|
|
39
|
+
"type": "(cardId: string) => void",
|
|
40
|
+
"required": false,
|
|
41
|
+
"description": "Callback to edit the card.",
|
|
42
|
+
"category": "callback"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "onDragStart",
|
|
46
|
+
"type": "(cardId: string) => void",
|
|
47
|
+
"required": false,
|
|
48
|
+
"description": "Callback on drag start.",
|
|
49
|
+
"category": "callback"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "onDragEnd",
|
|
53
|
+
"type": "(cardId: string, newListId: string) => void",
|
|
54
|
+
"required": false,
|
|
55
|
+
"description": "Callback on drag end.",
|
|
56
|
+
"category": "callback"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "draggable",
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"required": false,
|
|
62
|
+
"default": "true",
|
|
63
|
+
"description": "Enables drag-and-drop for the card.",
|
|
64
|
+
"category": "visual"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"storyVariants": [
|
|
68
|
+
{
|
|
69
|
+
"name": "Default",
|
|
70
|
+
"description": "Ready state with sample data.",
|
|
71
|
+
"args": {
|
|
72
|
+
"card": {
|
|
73
|
+
"id": "1",
|
|
74
|
+
"title": "Task",
|
|
75
|
+
"description": "Do something",
|
|
76
|
+
"listId": "list1"
|
|
77
|
+
},
|
|
78
|
+
"onEdit": "fn()",
|
|
79
|
+
"onDragStart": "fn()",
|
|
80
|
+
"onDragEnd": "fn()",
|
|
81
|
+
"draggable": true
|
|
82
|
+
},
|
|
83
|
+
"needsPlayFunction": true,
|
|
84
|
+
"playDescription": "1. Click edit button using userEvent.click(getByRole('button', { name: 'Edit' })). 2. Verify onEdit called with expect(fn).toHaveBeenCalledWith('1')."
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"dataContract": {
|
|
88
|
+
"source": "props",
|
|
89
|
+
"propsFieldName": "card",
|
|
90
|
+
"fields": ["id", "title", "description", "listId"]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"componentId": "board-list",
|
|
95
|
+
"componentName": "BoardList",
|
|
96
|
+
"isNew": true,
|
|
97
|
+
"atomicType": "organism",
|
|
98
|
+
"composes": ["ui-components-card", "board-card"],
|
|
99
|
+
"specDeltas": {
|
|
100
|
+
"structure": [
|
|
101
|
+
"Composes Card with CardHeader showing list title, CardContent rendering array of BoardCard components vertically stacked, CardFooter with add card button.",
|
|
102
|
+
"Uses semantic <section> with aria-label='List {title}' for the root."
|
|
103
|
+
],
|
|
104
|
+
"rendering": [
|
|
105
|
+
"Renders list of cards from props.cards array as vertical stack; empty state shows 'No cards' message when cards.length === 0.",
|
|
106
|
+
"In ready state, shows header, content with cards, and footer."
|
|
107
|
+
],
|
|
108
|
+
"interaction": [
|
|
109
|
+
"Accepts dropped cards and calls onCardDrop: (listId: string, cardId: string) => void.",
|
|
110
|
+
"Clicking add card button calls onAddCard: (listId: string) => void, which navigates to edit-card for new card."
|
|
111
|
+
],
|
|
112
|
+
"styling": [
|
|
113
|
+
"Root uses `w-72 rounded-lg border border-border bg-background p-4 shadow-sm flex flex-col space-y-4` consistent with Card.",
|
|
114
|
+
"Header uses `text-xl font-semibold tracking-tight text-foreground` for title.",
|
|
115
|
+
"Content uses `flex flex-col space-y-3 min-h-[200px]` for cards stack; empty state `text-sm text-muted-foreground text-center py-8`.",
|
|
116
|
+
"Footer uses `mt-auto` with add button `w-full bg-primary text-primary-foreground hover:bg-primary/90 rounded-md h-10`.",
|
|
117
|
+
"Drop target highlights with `ring-2 ring-accent` on drag over.",
|
|
118
|
+
"Hover on add button uses CSS `transition: background-color 150ms ease` wrapped in `@media (hover: hover) and (pointer: fine)`.",
|
|
119
|
+
"Touch target for add button uses `min-h-11 touch-action: manipulation`."
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"props": [
|
|
123
|
+
{
|
|
124
|
+
"name": "list",
|
|
125
|
+
"type": "{ id: string; title: string; cards: Array<{ id: string; title: string; description: string; listId: string }> }",
|
|
126
|
+
"required": true,
|
|
127
|
+
"description": "Data for the list and its cards.",
|
|
128
|
+
"category": "data"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "onAddCard",
|
|
132
|
+
"type": "(listId: string) => void",
|
|
133
|
+
"required": false,
|
|
134
|
+
"description": "Callback to add a new card to the list.",
|
|
135
|
+
"category": "callback"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "onCardDrop",
|
|
139
|
+
"type": "(listId: string, cardId: string) => void",
|
|
140
|
+
"required": false,
|
|
141
|
+
"description": "Callback when a card is dropped into the list.",
|
|
142
|
+
"category": "callback"
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"storyVariants": [
|
|
146
|
+
{
|
|
147
|
+
"name": "Default",
|
|
148
|
+
"description": "Ready state with cards.",
|
|
149
|
+
"args": {
|
|
150
|
+
"list": {
|
|
151
|
+
"id": "1",
|
|
152
|
+
"title": "To Do",
|
|
153
|
+
"cards": [
|
|
154
|
+
{
|
|
155
|
+
"id": "c1",
|
|
156
|
+
"title": "Task 1",
|
|
157
|
+
"description": "",
|
|
158
|
+
"listId": "1"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"onAddCard": "fn()",
|
|
163
|
+
"onCardDrop": "fn()"
|
|
164
|
+
},
|
|
165
|
+
"needsPlayFunction": true,
|
|
166
|
+
"playDescription": "1. Click add card button using userEvent.click(getByRole('button', { name: 'Add Card' })). 2. Verify onAddCard called with expect(fn).toHaveBeenCalledWith('1')."
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "Empty",
|
|
170
|
+
"description": "Empty state without cards.",
|
|
171
|
+
"args": {
|
|
172
|
+
"list": {
|
|
173
|
+
"id": "1",
|
|
174
|
+
"title": "To Do",
|
|
175
|
+
"cards": []
|
|
176
|
+
},
|
|
177
|
+
"onAddCard": "fn()",
|
|
178
|
+
"onCardDrop": "fn()"
|
|
179
|
+
},
|
|
180
|
+
"needsPlayFunction": false
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"dataContract": {
|
|
184
|
+
"source": "props",
|
|
185
|
+
"propsFieldName": "list",
|
|
186
|
+
"fields": ["id", "title", "cards.id", "cards.title", "cards.description", "cards.listId"]
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"componentId": "board-lists-container",
|
|
191
|
+
"componentName": "BoardListsContainer",
|
|
192
|
+
"isNew": true,
|
|
193
|
+
"atomicType": "organism",
|
|
194
|
+
"composes": ["board-list"],
|
|
195
|
+
"specDeltas": {
|
|
196
|
+
"structure": [
|
|
197
|
+
"Horizontal scrollable container rendering array of BoardList components side by side.",
|
|
198
|
+
"Uses semantic <main> with role='region' aria-label='Board lists'."
|
|
199
|
+
],
|
|
200
|
+
"rendering": [
|
|
201
|
+
"In loading state, shows skeletons for each list (h-64 w-72 for each list placeholder).",
|
|
202
|
+
"In ready state, renders lists from props.lists array as horizontal row; empty state shows 'No lists' message when lists.length === 0.",
|
|
203
|
+
"In error state, hides lists and shows error banner with retry button."
|
|
204
|
+
],
|
|
205
|
+
"interaction": [
|
|
206
|
+
"Supports drag-and-drop of cards between lists, using react-dnd or similar; calls onCardMoved: (cardId: string, fromListId: string, toListId: string) => void on successful drop.",
|
|
207
|
+
"Clicking retry in error state calls onRetry: () => void, transitioning to loading state."
|
|
208
|
+
],
|
|
209
|
+
"styling": [
|
|
210
|
+
"Root uses `flex overflow-x-auto space-x-4 p-4` for horizontal scrollable layout.",
|
|
211
|
+
"Loading state skeletons use `h-64 w-72 rounded-lg bg-muted animate-pulse` for each list placeholder.",
|
|
212
|
+
"Error state banner uses `p-4 border border-destructive rounded-md bg-destructive/10 text-destructive text-center` with retry button `mt-2 bg-destructive text-destructive-foreground hover:bg-destructive/90`.",
|
|
213
|
+
"Empty state uses `text-lg text-muted-foreground text-center py-8` with `w-full`.",
|
|
214
|
+
"Drag over list adds `bg-accent/20` with CSS `transition: background-color 150ms ease`.",
|
|
215
|
+
"Respects prefers-reduced-motion by setting `transition: none` for background changes."
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"props": [
|
|
219
|
+
{
|
|
220
|
+
"name": "lists",
|
|
221
|
+
"type": "Array<{ id: string; title: string; cards: Array<{ id: string; title: string; description: string; listId: string }> }>",
|
|
222
|
+
"required": true,
|
|
223
|
+
"description": "Array of lists with their cards.",
|
|
224
|
+
"category": "data"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "isLoading",
|
|
228
|
+
"type": "boolean",
|
|
229
|
+
"required": false,
|
|
230
|
+
"default": "false",
|
|
231
|
+
"description": "Triggers loading state with skeletons.",
|
|
232
|
+
"category": "state"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "error",
|
|
236
|
+
"type": "string | null",
|
|
237
|
+
"required": false,
|
|
238
|
+
"default": "null",
|
|
239
|
+
"description": "Error message for error state.",
|
|
240
|
+
"category": "state"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "onCardMoved",
|
|
244
|
+
"type": "(cardId: string, fromListId: string, toListId: string) => void",
|
|
245
|
+
"required": false,
|
|
246
|
+
"description": "Callback when a card is moved between lists.",
|
|
247
|
+
"category": "callback"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "onRetry",
|
|
251
|
+
"type": "() => void",
|
|
252
|
+
"required": false,
|
|
253
|
+
"description": "Callback for retry in error state.",
|
|
254
|
+
"category": "callback"
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
"storyVariants": [
|
|
258
|
+
{
|
|
259
|
+
"name": "Default",
|
|
260
|
+
"description": "Ready state with lists and cards.",
|
|
261
|
+
"args": {
|
|
262
|
+
"lists": [
|
|
263
|
+
{
|
|
264
|
+
"id": "1",
|
|
265
|
+
"title": "To Do",
|
|
266
|
+
"cards": [
|
|
267
|
+
{
|
|
268
|
+
"id": "c1",
|
|
269
|
+
"title": "Task",
|
|
270
|
+
"description": "",
|
|
271
|
+
"listId": "1"
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
],
|
|
276
|
+
"onCardMoved": "fn()",
|
|
277
|
+
"onRetry": "fn()"
|
|
278
|
+
},
|
|
279
|
+
"needsPlayFunction": false
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"name": "Loading",
|
|
283
|
+
"description": "Loading state with skeletons.",
|
|
284
|
+
"args": {
|
|
285
|
+
"isLoading": true,
|
|
286
|
+
"onCardMoved": "fn()",
|
|
287
|
+
"onRetry": "fn()"
|
|
288
|
+
},
|
|
289
|
+
"needsPlayFunction": false
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"name": "Error",
|
|
293
|
+
"description": "Error state with banner.",
|
|
294
|
+
"args": {
|
|
295
|
+
"error": "Failed to load board",
|
|
296
|
+
"onCardMoved": "fn()",
|
|
297
|
+
"onRetry": "fn()"
|
|
298
|
+
},
|
|
299
|
+
"needsPlayFunction": true,
|
|
300
|
+
"playDescription": "1. Click retry button using userEvent.click(getByRole('button', { name: 'Retry' })). 2. Verify onRetry called with expect(fn).toHaveBeenCalled()."
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"name": "Empty",
|
|
304
|
+
"description": "Empty state without lists.",
|
|
305
|
+
"args": {
|
|
306
|
+
"lists": [],
|
|
307
|
+
"onCardMoved": "fn()",
|
|
308
|
+
"onRetry": "fn()"
|
|
309
|
+
},
|
|
310
|
+
"needsPlayFunction": false
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
"dataContract": {
|
|
314
|
+
"source": "props",
|
|
315
|
+
"propsFieldName": "lists",
|
|
316
|
+
"fields": ["id", "title", "cards.id", "cards.title", "cards.description", "cards.listId"]
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"componentId": "ui-components-input",
|
|
321
|
+
"componentName": "Input",
|
|
322
|
+
"isNew": false,
|
|
323
|
+
"specDeltas": {
|
|
324
|
+
"structure": ["Renders as a single input element with associated label via htmlFor for accessibility."],
|
|
325
|
+
"rendering": [
|
|
326
|
+
"In editing-invalid state, shows inline error message below the input if validation fails, with aria-invalid=true and aria-describedby pointing to the error message element."
|
|
327
|
+
],
|
|
328
|
+
"interaction": [
|
|
329
|
+
"On change, calls onChange: (event: React.ChangeEvent<HTMLInputElement>) => void and validates input; if invalid, sets internal error state and shows inline feedback.",
|
|
330
|
+
"On blur, triggers validation and shows inline error if invalid."
|
|
331
|
+
],
|
|
332
|
+
"styling": [
|
|
333
|
+
"Input uses `h-10 px-3 py-2 rounded-md border border-input bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50` for base styling.",
|
|
334
|
+
"Error state applies `border-destructive focus-visible:ring-destructive` to the input when aria-invalid=true.",
|
|
335
|
+
"Inline error message uses `text-sm text-destructive mt-1` positioned below the input.",
|
|
336
|
+
"Respects prefers-reduced-motion by adding `motion-reduce:transition-none` to focus-visible transitions.",
|
|
337
|
+
"Uses `text-base` for font-size to prevent iOS auto-zoom on focus.",
|
|
338
|
+
"Hover effect wrapped in `@media (hover: hover) and (pointer: fine) { hover:border-input/80 }`.",
|
|
339
|
+
"Touch target expanded with `min-h-11` including padding."
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
"props": [
|
|
343
|
+
{
|
|
344
|
+
"name": "value",
|
|
345
|
+
"type": "string",
|
|
346
|
+
"required": false,
|
|
347
|
+
"description": "The current value of the input.",
|
|
348
|
+
"category": "state"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"name": "onChange",
|
|
352
|
+
"type": "(event: React.ChangeEvent<HTMLInputElement>) => void",
|
|
353
|
+
"required": false,
|
|
354
|
+
"description": "Callback fired on input change.",
|
|
355
|
+
"category": "callback"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "placeholder",
|
|
359
|
+
"type": "string",
|
|
360
|
+
"required": false,
|
|
361
|
+
"description": "Placeholder text shown when input is empty.",
|
|
362
|
+
"category": "config"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"name": "disabled",
|
|
366
|
+
"type": "boolean",
|
|
367
|
+
"required": false,
|
|
368
|
+
"default": "false",
|
|
369
|
+
"description": "Disables the input if true.",
|
|
370
|
+
"category": "state"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"name": "error",
|
|
374
|
+
"type": "string | null",
|
|
375
|
+
"required": false,
|
|
376
|
+
"default": "null",
|
|
377
|
+
"description": "Error message to display if validation fails.",
|
|
378
|
+
"category": "state"
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
"storyVariants": [
|
|
382
|
+
{
|
|
383
|
+
"name": "Default",
|
|
384
|
+
"description": "Ready state with placeholder.",
|
|
385
|
+
"args": {
|
|
386
|
+
"placeholder": "Enter title",
|
|
387
|
+
"onChange": "fn()"
|
|
388
|
+
},
|
|
389
|
+
"needsPlayFunction": false
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"name": "Error",
|
|
393
|
+
"description": "Invalid state with error message.",
|
|
394
|
+
"args": {
|
|
395
|
+
"placeholder": "Enter title",
|
|
396
|
+
"error": "Title is required",
|
|
397
|
+
"onChange": "fn()"
|
|
398
|
+
},
|
|
399
|
+
"needsPlayFunction": true,
|
|
400
|
+
"playDescription": "1. Type 'a' in the input using userEvent.type(getByRole('textbox'), 'a'). 2. Blur the input using userEvent.tab(). 3. Verify error message appears with expect(getByText('Title is required')).toBeVisible()."
|
|
401
|
+
}
|
|
402
|
+
],
|
|
403
|
+
"dataContract": {
|
|
404
|
+
"source": "props",
|
|
405
|
+
"propsFieldName": "value",
|
|
406
|
+
"fields": ["value"]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"componentId": "ui-components-textarea",
|
|
411
|
+
"componentName": "Textarea",
|
|
412
|
+
"isNew": false,
|
|
413
|
+
"specDeltas": {
|
|
414
|
+
"structure": ["Renders as a single textarea element with associated label via htmlFor for accessibility."],
|
|
415
|
+
"rendering": [
|
|
416
|
+
"In editing-invalid state, shows inline error message below the textarea if validation fails, with aria-invalid=true and aria-describedby pointing to the error message element."
|
|
417
|
+
],
|
|
418
|
+
"interaction": [
|
|
419
|
+
"On change, calls onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void and validates input; if invalid, sets internal error state and shows inline feedback.",
|
|
420
|
+
"On blur, triggers validation and shows inline error if invalid.",
|
|
421
|
+
"Supports Cmd+Enter (Mac) or Ctrl+Enter (Windows) to submit the form."
|
|
422
|
+
],
|
|
423
|
+
"styling": [
|
|
424
|
+
"Textarea uses `min-h-[80px] px-3 py-2 rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-y` for base styling.",
|
|
425
|
+
"Error state applies `border-destructive focus-visible:ring-destructive` to the textarea when aria-invalid=true.",
|
|
426
|
+
"Inline error message uses `text-sm text-destructive mt-1` positioned below the textarea.",
|
|
427
|
+
"Respects prefers-reduced-motion by adding `motion-reduce:transition-none` to focus-visible transitions.",
|
|
428
|
+
"Uses `text-base` for font-size to prevent iOS auto-zoom on focus.",
|
|
429
|
+
"Hover effect wrapped in `@media (hover: hover) and (pointer: fine) { hover:border-input/80 }`.",
|
|
430
|
+
"Touch target expanded with `min-h-11` for each line including padding."
|
|
431
|
+
]
|
|
432
|
+
},
|
|
433
|
+
"props": [
|
|
434
|
+
{
|
|
435
|
+
"name": "value",
|
|
436
|
+
"type": "string",
|
|
437
|
+
"required": false,
|
|
438
|
+
"description": "The current value of the textarea.",
|
|
439
|
+
"category": "state"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"name": "onChange",
|
|
443
|
+
"type": "(event: React.ChangeEvent<HTMLTextAreaElement>) => void",
|
|
444
|
+
"required": false,
|
|
445
|
+
"description": "Callback fired on textarea change.",
|
|
446
|
+
"category": "callback"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "placeholder",
|
|
450
|
+
"type": "string",
|
|
451
|
+
"required": false,
|
|
452
|
+
"description": "Placeholder text shown when textarea is empty.",
|
|
453
|
+
"category": "config"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"name": "disabled",
|
|
457
|
+
"type": "boolean",
|
|
458
|
+
"required": false,
|
|
459
|
+
"default": "false",
|
|
460
|
+
"description": "Disables the textarea if true.",
|
|
461
|
+
"category": "state"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"name": "error",
|
|
465
|
+
"type": "string | null",
|
|
466
|
+
"required": false,
|
|
467
|
+
"default": "null",
|
|
468
|
+
"description": "Error message to display if validation fails.",
|
|
469
|
+
"category": "state"
|
|
470
|
+
}
|
|
471
|
+
],
|
|
472
|
+
"storyVariants": [
|
|
473
|
+
{
|
|
474
|
+
"name": "Default",
|
|
475
|
+
"description": "Ready state with placeholder.",
|
|
476
|
+
"args": {
|
|
477
|
+
"placeholder": "Enter description",
|
|
478
|
+
"onChange": "fn()"
|
|
479
|
+
},
|
|
480
|
+
"needsPlayFunction": false
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"name": "Error",
|
|
484
|
+
"description": "Invalid state with error message.",
|
|
485
|
+
"args": {
|
|
486
|
+
"placeholder": "Enter description",
|
|
487
|
+
"error": "Description is required",
|
|
488
|
+
"onChange": "fn()"
|
|
489
|
+
},
|
|
490
|
+
"needsPlayFunction": true,
|
|
491
|
+
"playDescription": "1. Type 'a' in the textarea using userEvent.type(getByRole('textbox'), 'a'). 2. Blur the textarea using userEvent.tab(). 3. Verify error message appears with expect(getByText('Description is required')).toBeVisible()."
|
|
492
|
+
}
|
|
493
|
+
],
|
|
494
|
+
"dataContract": {
|
|
495
|
+
"source": "props",
|
|
496
|
+
"propsFieldName": "value",
|
|
497
|
+
"fields": ["value"]
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"componentId": "ui-components-button",
|
|
502
|
+
"componentName": "Button",
|
|
503
|
+
"isNew": false,
|
|
504
|
+
"specDeltas": {
|
|
505
|
+
"structure": ["Renders as a button element with semantic role='button'."],
|
|
506
|
+
"rendering": ["In submitting state, disables the button and shows loading spinner inside."],
|
|
507
|
+
"interaction": ["On click, calls onClick: () => void if not disabled."],
|
|
508
|
+
"styling": [
|
|
509
|
+
"Button uses `inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50` for base styling.",
|
|
510
|
+
"Default variant adds `bg-primary text-primary-foreground hover:bg-primary/90`.",
|
|
511
|
+
"Loading state shows spinner with `animate-spin h-5 w-5 mr-2 text-primary-foreground` inside the button.",
|
|
512
|
+
"Hover effect uses CSS `transition: background-color 150ms ease` wrapped in `@media (hover: hover) and (pointer: fine)`.",
|
|
513
|
+
"Active state uses CSS `active:scale-[0.97]` with `transition: transform 100ms ease-out`.",
|
|
514
|
+
"Respects prefers-reduced-motion by setting `transition: none` for scale and color changes.",
|
|
515
|
+
"Touch target uses `min-h-11 min-w-11 touch-action: manipulation`."
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
"props": [
|
|
519
|
+
{
|
|
520
|
+
"name": "onClick",
|
|
521
|
+
"type": "() => void",
|
|
522
|
+
"required": false,
|
|
523
|
+
"description": "Callback fired on button click.",
|
|
524
|
+
"category": "callback"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"name": "disabled",
|
|
528
|
+
"type": "boolean",
|
|
529
|
+
"required": false,
|
|
530
|
+
"default": "false",
|
|
531
|
+
"description": "Disables the button if true.",
|
|
532
|
+
"category": "state"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"name": "isLoading",
|
|
536
|
+
"type": "boolean",
|
|
537
|
+
"required": false,
|
|
538
|
+
"default": "false",
|
|
539
|
+
"description": "Shows loading spinner and disables button if true.",
|
|
540
|
+
"category": "state"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
"name": "children",
|
|
544
|
+
"type": "ReactNode",
|
|
545
|
+
"required": false,
|
|
546
|
+
"description": "Content inside the button.",
|
|
547
|
+
"category": "slot"
|
|
548
|
+
}
|
|
549
|
+
],
|
|
550
|
+
"storyVariants": [
|
|
551
|
+
{
|
|
552
|
+
"name": "Default",
|
|
553
|
+
"description": "Ready state.",
|
|
554
|
+
"args": {
|
|
555
|
+
"children": "Button",
|
|
556
|
+
"onClick": "fn()"
|
|
557
|
+
},
|
|
558
|
+
"needsPlayFunction": true,
|
|
559
|
+
"playDescription": "1. Click the button using userEvent.click(getByRole('button', { name: 'Button' })). 2. Verify the action is called with expect(fn).toHaveBeenCalled()."
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
"name": "Loading",
|
|
563
|
+
"description": "Submitting state with spinner.",
|
|
564
|
+
"args": {
|
|
565
|
+
"children": "Button",
|
|
566
|
+
"isLoading": true,
|
|
567
|
+
"onClick": "fn()"
|
|
568
|
+
},
|
|
569
|
+
"needsPlayFunction": false
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
"dataContract": {
|
|
573
|
+
"source": "props",
|
|
574
|
+
"propsFieldName": "children",
|
|
575
|
+
"fields": ["children"]
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"componentId": "ui-components-dialog",
|
|
580
|
+
"componentName": "Dialog",
|
|
581
|
+
"isNew": false,
|
|
582
|
+
"specDeltas": {
|
|
583
|
+
"structure": [
|
|
584
|
+
"Root Dialog wraps DialogTrigger and DialogContent; DialogContent contains DialogHeader with DialogTitle, form content as children, DialogFooter with action buttons, and DialogClose.",
|
|
585
|
+
"Uses semantic <header>, <footer>, and <main> inside DialogContent for landmarks."
|
|
586
|
+
],
|
|
587
|
+
"rendering": [
|
|
588
|
+
"In loading state, shows skeleton in form region and hides actions region.",
|
|
589
|
+
"In ready state, shows header, form, actions, and feedback regions.",
|
|
590
|
+
"In editing-invalid state, shows header, form, actions, and feedback regions with inline error messages.",
|
|
591
|
+
"In submitting state, disables actions and shows progress indicator in feedback region; hides form region.",
|
|
592
|
+
"In error state, hides form and actions regions; shows error banner in feedback region with retry button."
|
|
593
|
+
],
|
|
594
|
+
"interaction": [
|
|
595
|
+
"On open, moves focus to the first focusable element inside DialogContent (first input field); sets inert on background content.",
|
|
596
|
+
"On close (via DialogClose or Escape), returns focus to the trigger element and removes inert from background.",
|
|
597
|
+
"Calls onOpenChange: (open: boolean) => void on open/close.",
|
|
598
|
+
"Escape key closes the dialog.",
|
|
599
|
+
"Clicking outside closes the dialog."
|
|
600
|
+
],
|
|
601
|
+
"styling": [
|
|
602
|
+
"DialogContent uses `sm:max-w-[425px] bg-background p-6 space-y-4 rounded-lg shadow-lg` for base layout.",
|
|
603
|
+
"Overlay uses `fixed inset-0 z-[300] bg-black/80` for backdrop.",
|
|
604
|
+
"Content entrance uses useTransition(isOpen, { from: { opacity: 0, scale: 0.95 }, enter: { opacity: 1, scale: 1 }, leave: { opacity: 0, scale: 0.95 }, config: { tension: 300, friction: 22 } }); both overlay and content use the same config; respects useReducedMotion() by setting immediate: true when reduced.",
|
|
605
|
+
"DialogHeader uses `space-y-1.5 text-center sm:text-left` with DialogTitle `text-lg font-semibold leading-none tracking-tight` and DialogDescription `text-sm text-muted-foreground`.",
|
|
606
|
+
"DialogFooter uses `flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2`.",
|
|
607
|
+
"DialogClose uses `absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground`.",
|
|
608
|
+
"Loading state skeletons use `h-10 w-full bg-muted animate-pulse` for inputs.",
|
|
609
|
+
"Error state Alert uses `p-4 border border-destructive rounded-md bg-destructive/10 text-destructive`."
|
|
610
|
+
]
|
|
611
|
+
},
|
|
612
|
+
"props": [
|
|
613
|
+
{
|
|
614
|
+
"name": "open",
|
|
615
|
+
"type": "boolean",
|
|
616
|
+
"required": false,
|
|
617
|
+
"default": "false",
|
|
618
|
+
"description": "Controls the open state of the dialog.",
|
|
619
|
+
"category": "state"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
"name": "onOpenChange",
|
|
623
|
+
"type": "(open: boolean) => void",
|
|
624
|
+
"required": false,
|
|
625
|
+
"description": "Callback fired when open state changes.",
|
|
626
|
+
"category": "callback"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
"name": "children",
|
|
630
|
+
"type": "ReactNode",
|
|
631
|
+
"required": true,
|
|
632
|
+
"description": "Content inside the dialog.",
|
|
633
|
+
"category": "slot"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"name": "isLoading",
|
|
637
|
+
"type": "boolean",
|
|
638
|
+
"required": false,
|
|
639
|
+
"default": "false",
|
|
640
|
+
"description": "Triggers loading state with skeletons.",
|
|
641
|
+
"category": "state"
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"name": "error",
|
|
645
|
+
"type": "string | null",
|
|
646
|
+
"required": false,
|
|
647
|
+
"default": "null",
|
|
648
|
+
"description": "Error message for error state.",
|
|
649
|
+
"category": "state"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
"name": "onRetry",
|
|
653
|
+
"type": "() => void",
|
|
654
|
+
"required": false,
|
|
655
|
+
"description": "Callback for retry button in error state.",
|
|
656
|
+
"category": "callback"
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
"storyVariants": [
|
|
660
|
+
{
|
|
661
|
+
"name": "Default",
|
|
662
|
+
"description": "Ready state with form content.",
|
|
663
|
+
"args": {
|
|
664
|
+
"open": true,
|
|
665
|
+
"onOpenChange": "fn()",
|
|
666
|
+
"children": "{\"header\": \"Title\", \"form\": \"Form fields\"}"
|
|
667
|
+
},
|
|
668
|
+
"needsPlayFunction": true,
|
|
669
|
+
"playDescription": "1. Verify dialog is open with expect(getByRole('dialog')).toBeVisible(). 2. Click close button using userEvent.click(getByRole('button', { name: 'Close' })). 3. Await waitFor(() => expect(getByRole('dialog')).not.toBeVisible())."
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
"name": "Loading",
|
|
673
|
+
"description": "Loading state with skeletons.",
|
|
674
|
+
"args": {
|
|
675
|
+
"open": true,
|
|
676
|
+
"isLoading": true,
|
|
677
|
+
"onOpenChange": "fn()",
|
|
678
|
+
"children": "{\"header\": \"Title\", \"form\": \"Form fields\"}"
|
|
679
|
+
},
|
|
680
|
+
"needsPlayFunction": false
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
"name": "Error",
|
|
684
|
+
"description": "Error state with banner and retry.",
|
|
685
|
+
"args": {
|
|
686
|
+
"open": true,
|
|
687
|
+
"error": "Failed to load",
|
|
688
|
+
"onOpenChange": "fn()",
|
|
689
|
+
"onRetry": "fn()",
|
|
690
|
+
"children": "{\"header\": \"Title\", \"form\": \"Form fields\"}"
|
|
691
|
+
},
|
|
692
|
+
"needsPlayFunction": true,
|
|
693
|
+
"playDescription": "1. Verify error banner with expect(getByRole('alert')).toHaveTextContent('Failed to load'). 2. Click retry button using userEvent.click(getByRole('button', { name: 'Retry' })). 3. Verify onRetry called with expect(fn).toHaveBeenCalled()."
|
|
694
|
+
}
|
|
695
|
+
],
|
|
696
|
+
"dataContract": {
|
|
697
|
+
"source": "props",
|
|
698
|
+
"propsFieldName": "children",
|
|
699
|
+
"fields": ["children"]
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"componentId": "ui-components-alert",
|
|
704
|
+
"componentName": "Alert",
|
|
705
|
+
"isNew": false,
|
|
706
|
+
"specDeltas": {
|
|
707
|
+
"structure": [
|
|
708
|
+
"Renders Alert with AlertTitle and AlertDescription; includes a retry button in destructive variant for error states."
|
|
709
|
+
],
|
|
710
|
+
"rendering": [
|
|
711
|
+
"In error state, renders destructive variant with error message as description and 'Retry' button that calls onRetry."
|
|
712
|
+
],
|
|
713
|
+
"interaction": ["Clicking retry button calls onRetry: () => void."],
|
|
714
|
+
"styling": [
|
|
715
|
+
"Alert uses `relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground` for base layout.",
|
|
716
|
+
"Destructive variant adds `border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive`.",
|
|
717
|
+
"AlertTitle uses `mb-1 font-medium leading-none tracking-tight`.",
|
|
718
|
+
"AlertDescription uses `text-sm [&_p]:leading-relaxed`.",
|
|
719
|
+
"Retry button in destructive variant uses `mt-2 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90`."
|
|
720
|
+
]
|
|
721
|
+
},
|
|
722
|
+
"props": [
|
|
723
|
+
{
|
|
724
|
+
"name": "variant",
|
|
725
|
+
"type": "'default' | 'destructive'",
|
|
726
|
+
"required": false,
|
|
727
|
+
"default": "'default'",
|
|
728
|
+
"description": "Visual variant of the alert.",
|
|
729
|
+
"category": "config"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"name": "title",
|
|
733
|
+
"type": "string",
|
|
734
|
+
"required": false,
|
|
735
|
+
"description": "Title of the alert.",
|
|
736
|
+
"category": "data"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
"name": "description",
|
|
740
|
+
"type": "string",
|
|
741
|
+
"required": false,
|
|
742
|
+
"description": "Description or message of the alert.",
|
|
743
|
+
"category": "data"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"name": "onRetry",
|
|
747
|
+
"type": "() => void",
|
|
748
|
+
"required": false,
|
|
749
|
+
"description": "Callback for retry action in error alerts.",
|
|
750
|
+
"category": "callback"
|
|
751
|
+
}
|
|
752
|
+
],
|
|
753
|
+
"storyVariants": [
|
|
754
|
+
{
|
|
755
|
+
"name": "Default",
|
|
756
|
+
"description": "Default variant with title and description.",
|
|
757
|
+
"args": {
|
|
758
|
+
"title": "Info",
|
|
759
|
+
"description": "This is an info alert."
|
|
760
|
+
},
|
|
761
|
+
"needsPlayFunction": false
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
"name": "Error",
|
|
765
|
+
"description": "Destructive variant with retry button.",
|
|
766
|
+
"args": {
|
|
767
|
+
"variant": "destructive",
|
|
768
|
+
"title": "Error",
|
|
769
|
+
"description": "Failed to load",
|
|
770
|
+
"onRetry": "fn()"
|
|
771
|
+
},
|
|
772
|
+
"needsPlayFunction": true,
|
|
773
|
+
"playDescription": "1. Click retry button using userEvent.click(getByRole('button', { name: 'Retry' })). 2. Verify onRetry called with expect(fn).toHaveBeenCalled()."
|
|
774
|
+
}
|
|
775
|
+
],
|
|
776
|
+
"dataContract": {
|
|
777
|
+
"source": "props",
|
|
778
|
+
"propsFieldName": "description",
|
|
779
|
+
"fields": ["title", "description"]
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
"componentId": "ui-components-card",
|
|
784
|
+
"componentName": "Card",
|
|
785
|
+
"isNew": false,
|
|
786
|
+
"specDeltas": {
|
|
787
|
+
"structure": [
|
|
788
|
+
"Root Card wraps CardHeader with CardTitle and CardDescription, CardContent for body, CardFooter for actions."
|
|
789
|
+
],
|
|
790
|
+
"rendering": ["Always renders provided slots; no conditional logic."],
|
|
791
|
+
"interaction": [],
|
|
792
|
+
"styling": [
|
|
793
|
+
"Card uses `rounded-lg border bg-card text-card-foreground shadow-sm` for base styling.",
|
|
794
|
+
"CardHeader uses `flex flex-col space-y-1.5 p-6` with CardTitle `text-2xl font-semibold leading-none tracking-tight` and CardDescription `text-sm text-muted-foreground`.",
|
|
795
|
+
"CardContent uses `p-6 pt-0` for body content.",
|
|
796
|
+
"CardFooter uses `flex items-center p-6 pt-0` for actions."
|
|
797
|
+
]
|
|
798
|
+
},
|
|
799
|
+
"props": [
|
|
800
|
+
{
|
|
801
|
+
"name": "children",
|
|
802
|
+
"type": "ReactNode",
|
|
803
|
+
"required": false,
|
|
804
|
+
"description": "Content inside the card.",
|
|
805
|
+
"category": "slot"
|
|
806
|
+
}
|
|
807
|
+
],
|
|
808
|
+
"storyVariants": [
|
|
809
|
+
{
|
|
810
|
+
"name": "Default",
|
|
811
|
+
"description": "Basic card with title and content.",
|
|
812
|
+
"args": {
|
|
813
|
+
"children": "{\"title\": \"Card Title\", \"content\": \"Card content\"}"
|
|
814
|
+
},
|
|
815
|
+
"needsPlayFunction": false
|
|
816
|
+
}
|
|
817
|
+
],
|
|
818
|
+
"dataContract": {
|
|
819
|
+
"source": "props",
|
|
820
|
+
"propsFieldName": "children",
|
|
821
|
+
"fields": ["children"]
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
"componentId": "board-page",
|
|
826
|
+
"componentName": "BoardPage",
|
|
827
|
+
"isNew": true,
|
|
828
|
+
"atomicType": "page",
|
|
829
|
+
"composes": ["board-lists-container", "ui-components-button", "ui-components-skeleton"],
|
|
830
|
+
"specDeltas": {
|
|
831
|
+
"structure": [
|
|
832
|
+
"Page-level component with <main> containing board-header region (board title and actions), lists-container region (BoardListsContainer), add-list region (Button to navigate to add-list), and feedback region (Alert for errors).",
|
|
833
|
+
"Registers route '/boards/:boardId' for dedicated routing."
|
|
834
|
+
],
|
|
835
|
+
"rendering": [
|
|
836
|
+
"In loading state, shows skeleton in lists-container region (h-8 w-48 for header, multiple w-72 h-full for lists); hides add-list region.",
|
|
837
|
+
"In ready state, shows all regions: board-header, lists-container with lists and cards, add-list button, and empty feedback if no lists.",
|
|
838
|
+
"In error state, hides lists-container and add-list regions; shows error Alert in feedback region with 'Retry' button.",
|
|
839
|
+
"Renders data from dataContract: board title in header, lists and cards in lists-container."
|
|
840
|
+
],
|
|
841
|
+
"interaction": [
|
|
842
|
+
"On mount, fetches board data via GraphQL query.",
|
|
843
|
+
"Clicking add-list button navigates to add-list scene with boardId parameter.",
|
|
844
|
+
"Clicking retry in error state refetches data, transitioning to loading state.",
|
|
845
|
+
"Handles unsaved changes guard from crossCuttingRules: on navigation away from dirty nested forms, shows confirmation dialog."
|
|
846
|
+
],
|
|
847
|
+
"styling": [
|
|
848
|
+
"Main uses `flex flex-col h-full` with header `sticky top-0 z-[200] bg-background p-4 border-b shadow-sm flex items-center justify-between` including title `text-2xl font-bold tracking-tight text-foreground` and actions.",
|
|
849
|
+
"Lists container uses `flex-1 overflow-auto p-4` with horizontal flex `space-x-4`.",
|
|
850
|
+
"Add list button uses `fixed bottom-4 right-4 bg-primary text-primary-foreground rounded-full h-12 w-12 flex items-center justify-center shadow-lg hover:bg-primary/90` for floating action.",
|
|
851
|
+
"Feedback area uses `absolute top-4 right-4 z-[700]` for toasts or banners.",
|
|
852
|
+
"Loading skeletons in header use `h-8 w-48 bg-muted animate-pulse`, in lists use multiple `h-full w-72 bg-muted animate-pulse`.",
|
|
853
|
+
"Error banner uses `m-4 p-4 border border-destructive rounded-md bg-destructive/10 text-destructive` with retry `bg-destructive text-destructive-foreground`.",
|
|
854
|
+
"Hover on add button wrapped in `@media (hover: hover) and (pointer: fine) { hover:scale-105 }` with CSS `transition: transform 150ms ease`.",
|
|
855
|
+
"Touch target for add button uses `min-h-11 min-w-11 touch-action: manipulation`."
|
|
856
|
+
]
|
|
857
|
+
},
|
|
858
|
+
"props": [
|
|
859
|
+
{
|
|
860
|
+
"name": "boardId",
|
|
861
|
+
"type": "string",
|
|
862
|
+
"required": true,
|
|
863
|
+
"description": "ID of the board to display.",
|
|
864
|
+
"category": "config"
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"name": "isLoading",
|
|
868
|
+
"type": "boolean",
|
|
869
|
+
"required": false,
|
|
870
|
+
"default": "false",
|
|
871
|
+
"description": "Triggers loading state.",
|
|
872
|
+
"category": "state"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"name": "error",
|
|
876
|
+
"type": "string | null",
|
|
877
|
+
"required": false,
|
|
878
|
+
"default": "null",
|
|
879
|
+
"description": "Error message for error state.",
|
|
880
|
+
"category": "state"
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"name": "onRetry",
|
|
884
|
+
"type": "() => void",
|
|
885
|
+
"required": false,
|
|
886
|
+
"description": "Callback to retry loading.",
|
|
887
|
+
"category": "callback"
|
|
888
|
+
}
|
|
889
|
+
],
|
|
890
|
+
"storyVariants": [
|
|
891
|
+
{
|
|
892
|
+
"name": "Default",
|
|
893
|
+
"description": "Ready state with sample board data.",
|
|
894
|
+
"args": {
|
|
895
|
+
"boardId": "1",
|
|
896
|
+
"onRetry": "fn()"
|
|
897
|
+
},
|
|
898
|
+
"needsPlayFunction": true,
|
|
899
|
+
"playDescription": "1. Click add list button using userEvent.click(getByRole('button', { name: 'Add List' })). 2. Verify navigation or callback."
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
"name": "Loading",
|
|
903
|
+
"description": "Loading state with skeletons.",
|
|
904
|
+
"args": {
|
|
905
|
+
"boardId": "1",
|
|
906
|
+
"isLoading": true,
|
|
907
|
+
"onRetry": "fn()"
|
|
908
|
+
},
|
|
909
|
+
"needsPlayFunction": false
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"name": "Error",
|
|
913
|
+
"description": "Error state with retry.",
|
|
914
|
+
"args": {
|
|
915
|
+
"boardId": "1",
|
|
916
|
+
"error": "Failed to load board",
|
|
917
|
+
"onRetry": "fn()"
|
|
918
|
+
},
|
|
919
|
+
"needsPlayFunction": true,
|
|
920
|
+
"playDescription": "1. Click retry button using userEvent.click(getByRole('button', { name: 'Retry' })). 2. Verify onRetry called with expect(fn).toHaveBeenCalled()."
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"name": "Empty",
|
|
924
|
+
"description": "Ready state with no lists.",
|
|
925
|
+
"args": {
|
|
926
|
+
"boardId": "1",
|
|
927
|
+
"onRetry": "fn()"
|
|
928
|
+
},
|
|
929
|
+
"needsPlayFunction": false
|
|
930
|
+
}
|
|
931
|
+
],
|
|
932
|
+
"dataContract": {
|
|
933
|
+
"source": "graphql-query",
|
|
934
|
+
"operationName": "GetBoard",
|
|
935
|
+
"fields": [
|
|
936
|
+
"id",
|
|
937
|
+
"title",
|
|
938
|
+
"lists.id",
|
|
939
|
+
"lists.title",
|
|
940
|
+
"lists.cards.id",
|
|
941
|
+
"lists.cards.title",
|
|
942
|
+
"lists.cards.description"
|
|
943
|
+
]
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
"componentId": "create-board-modal",
|
|
948
|
+
"componentName": "CreateBoardModal",
|
|
949
|
+
"isNew": true,
|
|
950
|
+
"atomicType": "page",
|
|
951
|
+
"composes": [
|
|
952
|
+
"ui-components-dialog",
|
|
953
|
+
"ui-components-input",
|
|
954
|
+
"ui-components-textarea",
|
|
955
|
+
"ui-components-button",
|
|
956
|
+
"ui-components-alert"
|
|
957
|
+
],
|
|
958
|
+
"specDeltas": {
|
|
959
|
+
"structure": [
|
|
960
|
+
"Composes Dialog with header region (title 'Create New Board'), form region (Input for title, Textarea for description), actions region (Cancel and Create Board buttons), feedback region (Alert for errors).",
|
|
961
|
+
"Nested route under board-view."
|
|
962
|
+
],
|
|
963
|
+
"rendering": [
|
|
964
|
+
"In loading state, shows skeleton in form region (h-10 for title input, h-20 for description textarea); hides actions region.",
|
|
965
|
+
"In ready state, shows all regions with empty form fields.",
|
|
966
|
+
"In editing-invalid state, shows inline errors for required fields (e.g., 'Title is required' below input).",
|
|
967
|
+
"In submitting state, disables buttons and shows progress spinner in feedback region.",
|
|
968
|
+
"In error state, shows Alert with 'Failed to create board' and retry button."
|
|
969
|
+
],
|
|
970
|
+
"interaction": [
|
|
971
|
+
"Clicking Cancel calls onCancel: () => void, transitioning to loading and navigating back.",
|
|
972
|
+
"Clicking Create Board validates form; if valid, calls onSubmit: (data: { title: string; description: string }) => void, transitioning to submitting.",
|
|
973
|
+
"On unsaved changes (dirty form), shows confirmation dialog before closing.",
|
|
974
|
+
"Retry button in error state calls onRetry: () => void, transitioning to ready."
|
|
975
|
+
],
|
|
976
|
+
"styling": [
|
|
977
|
+
"DialogContent uses `sm:max-w-md bg-background p-6 space-y-6 rounded-lg shadow-lg` for layout.",
|
|
978
|
+
"Header uses `text-2xl font-semibold tracking-tight text-foreground text-center`.",
|
|
979
|
+
"Form uses `space-y-4` with Input `h-10 w-full` and Textarea `min-h-[100px] w-full`.",
|
|
980
|
+
"Actions uses `flex justify-end space-x-2` with Cancel `variant=\"outline\"` and Create `variant=\"default\"`.",
|
|
981
|
+
"Feedback uses `text-sm text-destructive` for inline errors; submitting spinner `animate-spin h-5 w-5 text-primary`.",
|
|
982
|
+
"Error Alert uses `p-4 border border-destructive rounded-md bg-destructive/10 text-destructive` with retry button.",
|
|
983
|
+
"Entrance animation uses useTransition(open, { from: { opacity: 0, scale: 0.95 }, enter: { opacity: 1, scale: 1 }, leave: { opacity: 0, scale: 0.95 }, config: { tension: 300, friction: 22 } }); respects useReducedMotion() with immediate: true.",
|
|
984
|
+
"Loading skeletons use `h-10 w-full bg-muted animate-pulse` for input, `h-20 w-full bg-muted animate-pulse` for textarea."
|
|
985
|
+
]
|
|
986
|
+
},
|
|
987
|
+
"props": [
|
|
988
|
+
{
|
|
989
|
+
"name": "open",
|
|
990
|
+
"type": "boolean",
|
|
991
|
+
"required": true,
|
|
992
|
+
"description": "Controls modal open state.",
|
|
993
|
+
"category": "state"
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
"name": "onCancel",
|
|
997
|
+
"type": "() => void",
|
|
998
|
+
"required": true,
|
|
999
|
+
"description": "Callback to cancel and close.",
|
|
1000
|
+
"category": "callback"
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
"name": "onSubmit",
|
|
1004
|
+
"type": "(data: { title: string; description: string }) => void",
|
|
1005
|
+
"required": true,
|
|
1006
|
+
"description": "Callback to submit form data.",
|
|
1007
|
+
"category": "callback"
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
"name": "isLoading",
|
|
1011
|
+
"type": "boolean",
|
|
1012
|
+
"required": false,
|
|
1013
|
+
"default": "false",
|
|
1014
|
+
"description": "Triggers loading state.",
|
|
1015
|
+
"category": "state"
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
"name": "error",
|
|
1019
|
+
"type": "string | null",
|
|
1020
|
+
"required": false,
|
|
1021
|
+
"default": "null",
|
|
1022
|
+
"description": "Error message for error state.",
|
|
1023
|
+
"category": "state"
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
"name": "onRetry",
|
|
1027
|
+
"type": "() => void",
|
|
1028
|
+
"required": false,
|
|
1029
|
+
"description": "Callback for retry.",
|
|
1030
|
+
"category": "callback"
|
|
1031
|
+
}
|
|
1032
|
+
],
|
|
1033
|
+
"storyVariants": [
|
|
1034
|
+
{
|
|
1035
|
+
"name": "Default",
|
|
1036
|
+
"description": "Ready state.",
|
|
1037
|
+
"args": {
|
|
1038
|
+
"open": true,
|
|
1039
|
+
"onCancel": "fn()",
|
|
1040
|
+
"onSubmit": "fn()"
|
|
1041
|
+
},
|
|
1042
|
+
"needsPlayFunction": true,
|
|
1043
|
+
"playDescription": "1. Type in title input using userEvent.type(getByLabelText('Title'), 'New Board'). 2. Click Create Board using userEvent.click(getByRole('button', { name: 'Create Board' })). 3. Verify onSubmit called with expect(fn).toHaveBeenCalledWith({ title: 'New Board', description: '' })."
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
"name": "Loading",
|
|
1047
|
+
"description": "Loading state.",
|
|
1048
|
+
"args": {
|
|
1049
|
+
"open": true,
|
|
1050
|
+
"isLoading": true,
|
|
1051
|
+
"onCancel": "fn()",
|
|
1052
|
+
"onSubmit": "fn()"
|
|
1053
|
+
},
|
|
1054
|
+
"needsPlayFunction": false
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
"name": "Error",
|
|
1058
|
+
"description": "Error state.",
|
|
1059
|
+
"args": {
|
|
1060
|
+
"open": true,
|
|
1061
|
+
"error": "Failed to create board",
|
|
1062
|
+
"onCancel": "fn()",
|
|
1063
|
+
"onSubmit": "fn()",
|
|
1064
|
+
"onRetry": "fn()"
|
|
1065
|
+
},
|
|
1066
|
+
"needsPlayFunction": true,
|
|
1067
|
+
"playDescription": "1. Click retry using userEvent.click(getByRole('button', { name: 'Retry' })). 2. Verify onRetry called."
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"name": "EditingInvalid",
|
|
1071
|
+
"description": "Invalid form state.",
|
|
1072
|
+
"args": {
|
|
1073
|
+
"open": true,
|
|
1074
|
+
"onCancel": "fn()",
|
|
1075
|
+
"onSubmit": "fn()"
|
|
1076
|
+
},
|
|
1077
|
+
"needsPlayFunction": true,
|
|
1078
|
+
"playDescription": "1. Click Create Board without input using userEvent.click(getByRole('button', { name: 'Create Board' })). 2. Verify inline error with expect(getByText('Title is required')).toBeVisible()."
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
"name": "Submitting",
|
|
1082
|
+
"description": "Submitting state.",
|
|
1083
|
+
"args": {
|
|
1084
|
+
"open": true,
|
|
1085
|
+
"isLoading": true,
|
|
1086
|
+
"onCancel": "fn()",
|
|
1087
|
+
"onSubmit": "fn()"
|
|
1088
|
+
},
|
|
1089
|
+
"needsPlayFunction": false
|
|
1090
|
+
}
|
|
1091
|
+
],
|
|
1092
|
+
"dataContract": {
|
|
1093
|
+
"source": "local-state",
|
|
1094
|
+
"fields": ["title", "description"]
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
"componentId": "add-list-modal",
|
|
1099
|
+
"componentName": "AddListModal",
|
|
1100
|
+
"isNew": true,
|
|
1101
|
+
"atomicType": "page",
|
|
1102
|
+
"composes": ["ui-components-dialog", "ui-components-input", "ui-components-button", "ui-components-alert"],
|
|
1103
|
+
"specDeltas": {
|
|
1104
|
+
"structure": [
|
|
1105
|
+
"Composes Dialog with header region (title 'Add List'), form region (Input for title), actions region (Cancel and Add List buttons), feedback region (Alert for errors).",
|
|
1106
|
+
"Nested route under board-view with boardId parameter."
|
|
1107
|
+
],
|
|
1108
|
+
"rendering": [
|
|
1109
|
+
"In loading state, shows skeleton in form region (h-10 for title input); hides actions.",
|
|
1110
|
+
"In ready state, shows all regions with empty form.",
|
|
1111
|
+
"In editing-invalid state, shows inline error 'List title is required' below input.",
|
|
1112
|
+
"In submitting state, disables buttons and shows progress spinner in feedback.",
|
|
1113
|
+
"In error state, shows Alert with 'Failed to add list' and retry button."
|
|
1114
|
+
],
|
|
1115
|
+
"interaction": [
|
|
1116
|
+
"Clicking Cancel calls onCancel: () => void, transitioning to loading and navigating back.",
|
|
1117
|
+
"Clicking Add List validates form; if valid, calls onSubmit: (data: { title: string }) => void, transitioning to submitting.",
|
|
1118
|
+
"On unsaved changes, shows confirmation before closing.",
|
|
1119
|
+
"Retry in error calls onRetry: () => void."
|
|
1120
|
+
],
|
|
1121
|
+
"styling": [
|
|
1122
|
+
"DialogContent uses `sm:max-w-sm bg-background p-6 space-y-6 rounded-lg shadow-lg` for layout.",
|
|
1123
|
+
"Header uses `text-2xl font-semibold tracking-tight text-foreground text-center`.",
|
|
1124
|
+
"Form uses `space-y-4` with Input `h-10 w-full`.",
|
|
1125
|
+
"Actions uses `flex justify-end space-x-2` with Cancel `variant=\"outline\"` and Add `variant=\"default\"`.",
|
|
1126
|
+
"Feedback uses `text-sm text-destructive` for inline errors; submitting spinner `animate-spin h-5 w-5 text-primary`.",
|
|
1127
|
+
"Error Alert uses `p-4 border border-destructive rounded-md bg-destructive/10 text-destructive` with retry.",
|
|
1128
|
+
"Entrance animation uses useTransition(open, { from: { opacity: 0, scale: 0.95 }, enter: { opacity: 1, scale: 1 }, leave: { opacity: 0, scale: 0.95 }, config: { tension: 300, friction: 22 } }); respects useReducedMotion() with immediate: true.",
|
|
1129
|
+
"Loading skeleton uses `h-10 w-full bg-muted animate-pulse` for input."
|
|
1130
|
+
]
|
|
1131
|
+
},
|
|
1132
|
+
"props": [
|
|
1133
|
+
{
|
|
1134
|
+
"name": "open",
|
|
1135
|
+
"type": "boolean",
|
|
1136
|
+
"required": true,
|
|
1137
|
+
"description": "Controls modal open state.",
|
|
1138
|
+
"category": "state"
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
"name": "boardId",
|
|
1142
|
+
"type": "string",
|
|
1143
|
+
"required": true,
|
|
1144
|
+
"description": "ID of the board to add list to.",
|
|
1145
|
+
"category": "config"
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
"name": "onCancel",
|
|
1149
|
+
"type": "() => void",
|
|
1150
|
+
"required": true,
|
|
1151
|
+
"description": "Callback to cancel.",
|
|
1152
|
+
"category": "callback"
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
"name": "onSubmit",
|
|
1156
|
+
"type": "(data: { title: string; boardId: string }) => void",
|
|
1157
|
+
"required": true,
|
|
1158
|
+
"description": "Callback to submit list data.",
|
|
1159
|
+
"category": "callback"
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
"name": "isLoading",
|
|
1163
|
+
"type": "boolean",
|
|
1164
|
+
"required": false,
|
|
1165
|
+
"default": "false",
|
|
1166
|
+
"description": "Triggers loading state.",
|
|
1167
|
+
"category": "state"
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
"name": "error",
|
|
1171
|
+
"type": "string | null",
|
|
1172
|
+
"required": false,
|
|
1173
|
+
"default": "null",
|
|
1174
|
+
"description": "Error message.",
|
|
1175
|
+
"category": "state"
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
"name": "onRetry",
|
|
1179
|
+
"type": "() => void",
|
|
1180
|
+
"required": false,
|
|
1181
|
+
"description": "Callback for retry.",
|
|
1182
|
+
"category": "callback"
|
|
1183
|
+
}
|
|
1184
|
+
],
|
|
1185
|
+
"storyVariants": [
|
|
1186
|
+
{
|
|
1187
|
+
"name": "Default",
|
|
1188
|
+
"description": "Ready state.",
|
|
1189
|
+
"args": {
|
|
1190
|
+
"open": true,
|
|
1191
|
+
"boardId": "1",
|
|
1192
|
+
"onCancel": "fn()",
|
|
1193
|
+
"onSubmit": "fn()"
|
|
1194
|
+
},
|
|
1195
|
+
"needsPlayFunction": true,
|
|
1196
|
+
"playDescription": "1. Type in title using userEvent.type(getByLabelText('Title'), 'New List'). 2. Click Add List using userEvent.click(getByRole('button', { name: 'Add List' })). 3. Verify onSubmit called with { title: 'New List', boardId: '1' }."
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
"name": "Loading",
|
|
1200
|
+
"description": "Loading state.",
|
|
1201
|
+
"args": {
|
|
1202
|
+
"open": true,
|
|
1203
|
+
"boardId": "1",
|
|
1204
|
+
"isLoading": true,
|
|
1205
|
+
"onCancel": "fn()",
|
|
1206
|
+
"onSubmit": "fn()"
|
|
1207
|
+
},
|
|
1208
|
+
"needsPlayFunction": false
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
"name": "Error",
|
|
1212
|
+
"description": "Error state.",
|
|
1213
|
+
"args": {
|
|
1214
|
+
"open": true,
|
|
1215
|
+
"boardId": "1",
|
|
1216
|
+
"error": "Failed to add list",
|
|
1217
|
+
"onCancel": "fn()",
|
|
1218
|
+
"onSubmit": "fn()",
|
|
1219
|
+
"onRetry": "fn()"
|
|
1220
|
+
},
|
|
1221
|
+
"needsPlayFunction": true,
|
|
1222
|
+
"playDescription": "1. Click retry using userEvent.click(getByRole('button', { name: 'Retry' })). 2. Verify onRetry called."
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
"name": "EditingInvalid",
|
|
1226
|
+
"description": "Invalid state.",
|
|
1227
|
+
"args": {
|
|
1228
|
+
"open": true,
|
|
1229
|
+
"boardId": "1",
|
|
1230
|
+
"onCancel": "fn()",
|
|
1231
|
+
"onSubmit": "fn()"
|
|
1232
|
+
},
|
|
1233
|
+
"needsPlayFunction": true,
|
|
1234
|
+
"playDescription": "1. Click Add List without input. 2. Verify error 'List title is required'."
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
"name": "Submitting",
|
|
1238
|
+
"description": "Submitting state.",
|
|
1239
|
+
"args": {
|
|
1240
|
+
"open": true,
|
|
1241
|
+
"boardId": "1",
|
|
1242
|
+
"isLoading": true,
|
|
1243
|
+
"onCancel": "fn()",
|
|
1244
|
+
"onSubmit": "fn()"
|
|
1245
|
+
},
|
|
1246
|
+
"needsPlayFunction": false
|
|
1247
|
+
}
|
|
1248
|
+
],
|
|
1249
|
+
"dataContract": {
|
|
1250
|
+
"source": "local-state",
|
|
1251
|
+
"fields": ["title", "boardId"]
|
|
1252
|
+
}
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
"componentId": "edit-card-modal",
|
|
1256
|
+
"componentName": "EditCardModal",
|
|
1257
|
+
"isNew": true,
|
|
1258
|
+
"atomicType": "page",
|
|
1259
|
+
"composes": [
|
|
1260
|
+
"ui-components-dialog",
|
|
1261
|
+
"ui-components-input",
|
|
1262
|
+
"ui-components-textarea",
|
|
1263
|
+
"ui-components-button",
|
|
1264
|
+
"ui-components-alert"
|
|
1265
|
+
],
|
|
1266
|
+
"specDeltas": {
|
|
1267
|
+
"structure": [
|
|
1268
|
+
"Composes Dialog with header (title 'Edit Card' or 'Create Card'), form (Input for title, Textarea for description), actions (Cancel and Save Card buttons), feedback (Alert for errors).",
|
|
1269
|
+
"Nested route with boardId and optional cardId (new if absent)."
|
|
1270
|
+
],
|
|
1271
|
+
"rendering": [
|
|
1272
|
+
"In loading state, shows skeletons in form (h-10 title, h-20 description); hides actions.",
|
|
1273
|
+
"In ready state, shows all regions; pre-fills form if editing existing card.",
|
|
1274
|
+
"In editing-invalid state, shows inline error 'Card title is required'.",
|
|
1275
|
+
"In submitting state, disables buttons, shows progress in feedback.",
|
|
1276
|
+
"In error state, shows Alert with 'Failed to save card' and retry."
|
|
1277
|
+
],
|
|
1278
|
+
"interaction": [
|
|
1279
|
+
"Clicking Cancel calls onCancel: () => void, navigating back.",
|
|
1280
|
+
"Clicking Save Card validates; if valid, calls onSubmit: (data: { title: string; description: string; cardId?: string; boardId: string }) => void.",
|
|
1281
|
+
"On unsaved changes, shows confirmation before closing.",
|
|
1282
|
+
"Retry calls onRetry: () => void."
|
|
1283
|
+
],
|
|
1284
|
+
"styling": [
|
|
1285
|
+
"DialogContent uses `sm:max-w-md bg-background p-6 space-y-6 rounded-lg shadow-lg` for layout.",
|
|
1286
|
+
"Header uses `text-2xl font-semibold tracking-tight text-foreground text-center`.",
|
|
1287
|
+
"Form uses `space-y-4` with Input `h-10 w-full` and Textarea `min-h-[100px] w-full`.",
|
|
1288
|
+
"Actions uses `flex justify-end space-x-2` with Cancel `variant=\"outline\"` and Save `variant=\"default\"`.",
|
|
1289
|
+
"Feedback uses `text-sm text-destructive` for inline errors; submitting spinner `animate-spin h-5 w-5 text-primary`.",
|
|
1290
|
+
"Error Alert uses `p-4 border border-destructive rounded-md bg-destructive/10 text-destructive` with retry.",
|
|
1291
|
+
"Entrance animation uses useTransition(open, { from: { opacity: 0, scale: 0.95 }, enter: { opacity: 1, scale: 1 }, leave: { opacity: 0, scale: 0.95 }, config: { tension: 300, friction: 22 } }); respects useReducedMotion() with immediate: true.",
|
|
1292
|
+
"Loading skeletons use `h-10 w-full bg-muted animate-pulse` for input, `h-20 w-full bg-muted animate-pulse` for textarea."
|
|
1293
|
+
]
|
|
1294
|
+
},
|
|
1295
|
+
"props": [
|
|
1296
|
+
{
|
|
1297
|
+
"name": "open",
|
|
1298
|
+
"type": "boolean",
|
|
1299
|
+
"required": true,
|
|
1300
|
+
"description": "Controls open state.",
|
|
1301
|
+
"category": "state"
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
"name": "boardId",
|
|
1305
|
+
"type": "string",
|
|
1306
|
+
"required": true,
|
|
1307
|
+
"description": "Board ID.",
|
|
1308
|
+
"category": "config"
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
"name": "cardId",
|
|
1312
|
+
"type": "string | undefined",
|
|
1313
|
+
"required": false,
|
|
1314
|
+
"description": "Card ID for editing; undefined for new.",
|
|
1315
|
+
"category": "config"
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
"name": "onCancel",
|
|
1319
|
+
"type": "() => void",
|
|
1320
|
+
"required": true,
|
|
1321
|
+
"description": "Cancel callback.",
|
|
1322
|
+
"category": "callback"
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
"name": "onSubmit",
|
|
1326
|
+
"type": "(data: { title: string; description: string; cardId?: string; boardId: string }) => void",
|
|
1327
|
+
"required": true,
|
|
1328
|
+
"description": "Submit callback.",
|
|
1329
|
+
"category": "callback"
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
"name": "isLoading",
|
|
1333
|
+
"type": "boolean",
|
|
1334
|
+
"required": false,
|
|
1335
|
+
"default": "false",
|
|
1336
|
+
"description": "Loading state.",
|
|
1337
|
+
"category": "state"
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
"name": "error",
|
|
1341
|
+
"type": "string | null",
|
|
1342
|
+
"required": false,
|
|
1343
|
+
"default": "null",
|
|
1344
|
+
"description": "Error message.",
|
|
1345
|
+
"category": "state"
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
"name": "onRetry",
|
|
1349
|
+
"type": "() => void",
|
|
1350
|
+
"required": false,
|
|
1351
|
+
"description": "Retry callback.",
|
|
1352
|
+
"category": "callback"
|
|
1353
|
+
}
|
|
1354
|
+
],
|
|
1355
|
+
"storyVariants": [
|
|
1356
|
+
{
|
|
1357
|
+
"name": "Default",
|
|
1358
|
+
"description": "Ready state for new card.",
|
|
1359
|
+
"args": {
|
|
1360
|
+
"open": true,
|
|
1361
|
+
"boardId": "1",
|
|
1362
|
+
"onCancel": "fn()",
|
|
1363
|
+
"onSubmit": "fn()"
|
|
1364
|
+
},
|
|
1365
|
+
"needsPlayFunction": true,
|
|
1366
|
+
"playDescription": "1. Type title 'New Card'. 2. Click Save Card. 3. Verify onSubmit with { title: 'New Card', description: '', boardId: '1' }."
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
"name": "Loading",
|
|
1370
|
+
"description": "Loading state.",
|
|
1371
|
+
"args": {
|
|
1372
|
+
"open": true,
|
|
1373
|
+
"boardId": "1",
|
|
1374
|
+
"isLoading": true,
|
|
1375
|
+
"onCancel": "fn()",
|
|
1376
|
+
"onSubmit": "fn()"
|
|
1377
|
+
},
|
|
1378
|
+
"needsPlayFunction": false
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
"name": "Error",
|
|
1382
|
+
"description": "Error state.",
|
|
1383
|
+
"args": {
|
|
1384
|
+
"open": true,
|
|
1385
|
+
"boardId": "1",
|
|
1386
|
+
"error": "Failed to save card",
|
|
1387
|
+
"onCancel": "fn()",
|
|
1388
|
+
"onSubmit": "fn()",
|
|
1389
|
+
"onRetry": "fn()"
|
|
1390
|
+
},
|
|
1391
|
+
"needsPlayFunction": true,
|
|
1392
|
+
"playDescription": "1. Click retry. 2. Verify onRetry called."
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
"name": "EditingInvalid",
|
|
1396
|
+
"description": "Invalid state.",
|
|
1397
|
+
"args": {
|
|
1398
|
+
"open": true,
|
|
1399
|
+
"boardId": "1",
|
|
1400
|
+
"onCancel": "fn()",
|
|
1401
|
+
"onSubmit": "fn()"
|
|
1402
|
+
},
|
|
1403
|
+
"needsPlayFunction": true,
|
|
1404
|
+
"playDescription": "1. Click Save Card without input. 2. Verify 'Card title is required'."
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
"name": "Submitting",
|
|
1408
|
+
"description": "Submitting state.",
|
|
1409
|
+
"args": {
|
|
1410
|
+
"open": true,
|
|
1411
|
+
"boardId": "1",
|
|
1412
|
+
"isLoading": true,
|
|
1413
|
+
"onCancel": "fn()",
|
|
1414
|
+
"onSubmit": "fn()"
|
|
1415
|
+
},
|
|
1416
|
+
"needsPlayFunction": false
|
|
1417
|
+
}
|
|
1418
|
+
],
|
|
1419
|
+
"dataContract": {
|
|
1420
|
+
"source": "local-state",
|
|
1421
|
+
"fields": ["title", "description", "cardId", "boardId"]
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
]
|