Skip to content

Commit 8cac1e2

Browse files
codingwolf-atlifeiscontent
authored andcommitted
stories: standardize story render/interaction-twin patterns
Squashed from the branch's 9 incremental commits (shared render functions -> story spread -> tags consolidation) into one commit to simplify rebasing onto main. - Inline render implementations directly on each story's `render` property; hook-requiring stories declare a named function inside `render` instead of a top-level helper. - Remove unnecessary `ComponentProps` imports; story types are inferred from args. - Single-interaction stories spread directly from their display story (`...Default`) and declare their own tags. - Multi-interaction stories share a non-exported, untagged base and each exported twin declares its own tags. - Consolidate accessibility parameter objects inline where only one story needs them.
1 parent 1078e66 commit 8cac1e2

54 files changed

Lines changed: 721 additions & 159 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/propel/src/components/alert-dialog/alert-dialog.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ export const Default: Story = {
9090
</AlertDialogContent>
9191
</AlertDialog>
9292
),
93+
};
94+
95+
/**
96+
* Interaction test: it opens, an outside click does NOT dismiss it (always modal), and Cancel
97+
* closes it. Tagged out of the sidebar/docs/manifest while still running under the default `test`
98+
* tag — so a browsing user never sees the dialog flash open and then close.
99+
*/
100+
export const ModalDismissal: Story = {
101+
...Default,
102+
tags: ["!dev", "!autodocs", "!manifest"],
93103
play: async ({ canvas, step }) => {
94104
await step("open the alert dialog", async () => {
95105
await userEvent.click(canvas.getByRole("button", { name: "Delete project" }));

packages/propel/src/components/autocomplete-field/autocomplete-field.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Story = StoryObj<typeof meta>;
4949
export const Default: Story = {};
5050

5151
export const RendersInput: Story = {
52+
...Default,
5253
tags: ["!dev", "!autodocs", "!manifest"],
5354
play: async ({ canvas }) => {
5455
await expect(canvas.getByRole("combobox", { name: "Container image" })).toBeInTheDocument();
@@ -58,6 +59,16 @@ export const RendersInput: Story = {
5859
/** Setting `error` marks the field invalid, which recolors the input group border to danger. */
5960
export const Invalid: Story = {
6061
args: { error: "Enter a container image." },
62+
};
63+
64+
/**
65+
* Interaction test: the invalid field marks the input `aria-invalid`/`data-invalid` and recolors
66+
* the group border. Tagged out of the sidebar/docs/manifest while still running under the default
67+
* `test` tag.
68+
*/
69+
export const InvalidInteraction: Story = {
70+
...Invalid,
71+
tags: ["!dev", "!autodocs", "!manifest"],
6172
play: async ({ canvas }) => {
6273
const input = canvas.getByRole("combobox", { name: "Container image" });
6374
await expect(input).toHaveAttribute("aria-invalid", "true");

packages/propel/src/components/autocomplete/autocomplete.stories.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ export const Default: Story = {
9292
</Autocomplete>
9393
</Field>
9494
),
95+
};
96+
97+
export const DefaultInteraction: Story = {
98+
...Default,
99+
tags: ["!dev", "!autodocs", "!manifest"],
95100
play: async ({ canvas, userEvent }) => {
96101
await userEvent.type(canvas.getByRole("combobox", { name: "Container image" }), "node");
97102
await expect(within(document.body).getByText("node:22-slim")).toBeInTheDocument();

packages/propel/src/components/avatar-group/avatar-group.stories.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ export const ThreeMembers: Story = {
4040
<Avatar alt="Linus" fallback="LT" />
4141
</AvatarGroup>
4242
),
43+
};
44+
45+
/**
46+
* Interaction test: all three avatars render and the group's `magnitude` flows to each. Tagged out
47+
* of the sidebar/docs/manifest while still running under the default `test` tag.
48+
*/
49+
export const ThreeMembersInteraction: Story = {
50+
...ThreeMembers,
51+
tags: ["!dev", "!autodocs", "!manifest"],
4352
play: async ({ canvas }) => {
4453
// Each avatar exposes role="img"; querying by role proves all three rendered.
4554
const avatars = canvas.getAllByRole("img");

packages/propel/src/components/breadcrumb/breadcrumb.stories.tsx

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ export const Default: Story = {
7878
</BreadcrumbList>
7979
</Breadcrumb>
8080
),
81+
};
82+
83+
/**
84+
* Interaction test: the trail exposes its landmark name and the last crumb is the current page.
85+
* Tagged out of the sidebar/docs/manifest while still running under the default `test` tag.
86+
*/
87+
export const DefaultInteraction: Story = {
88+
...Default,
89+
tags: ["!dev", "!autodocs", "!manifest"],
8190
play: async ({ canvas }) => {
8291
// The trail exposes its accessible landmark name, and the last crumb is the
8392
// current page.
@@ -136,36 +145,8 @@ export const WithCollapsedCrumbs: Story = {
136145
* behavior canary, not a designer example — but still runs under the default `test` tag.
137146
*/
138147
export const CollapsedCrumbsInteraction: Story = {
148+
...WithCollapsedCrumbs,
139149
tags: ["!dev", "!autodocs", "!manifest"],
140-
render: () => (
141-
<Breadcrumb aria-label="Breadcrumb">
142-
<BreadcrumbList>
143-
<BreadcrumbItem>
144-
<BreadcrumbLink href="#" onClick={(event) => event.preventDefault()}>
145-
Plane
146-
</BreadcrumbLink>
147-
</BreadcrumbItem>
148-
<BreadcrumbSeparator />
149-
<BreadcrumbItem>
150-
<Menu>
151-
<BaseMenu.Trigger aria-label="Show more breadcrumbs" render={<BreadcrumbTrigger />}>
152-
<BreadcrumbTriggerIcon>
153-
<Ellipsis />
154-
</BreadcrumbTriggerIcon>
155-
</BaseMenu.Trigger>
156-
<MenuContent width="auto">
157-
<MenuItem render={inertAnchor()}>Projects</MenuItem>
158-
<MenuItem render={inertAnchor()}>Design</MenuItem>
159-
</MenuContent>
160-
</Menu>
161-
</BreadcrumbItem>
162-
<BreadcrumbSeparator />
163-
<BreadcrumbItem>
164-
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
165-
</BreadcrumbItem>
166-
</BreadcrumbList>
167-
</Breadcrumb>
168-
),
169150
play: async ({ canvas }) => {
170151
// The current crumb is marked for assistive tech.
171152
await expect(canvas.getByText("Breadcrumb")).toHaveAttribute("aria-current", "page");

packages/propel/src/components/checkbox-field/checkbox-field.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ export const Invalid: Story = {
4545
/>
4646
</div>
4747
),
48+
};
49+
50+
/**
51+
* Interaction test: the invalid field propagates `data-invalid` and renders a different border
52+
* color than the resting field. Tagged out of the sidebar/docs/manifest while still running under
53+
* the default `test` tag.
54+
*/
55+
export const InvalidInteraction: Story = {
56+
...Invalid,
57+
tags: ["!dev", "!autodocs", "!manifest"],
4858
play: async ({ canvas }) => {
4959
const [resting, invalid] = canvas.getAllByRole("checkbox");
5060
// The error-free field leaves the box in its resting (non-invalid) state.
@@ -60,6 +70,7 @@ export const Invalid: Story = {
6070
};
6171

6272
export const RendersCheckbox: Story = {
73+
...Default,
6374
tags: ["!dev", "!autodocs", "!manifest"],
6475
play: async ({ canvas }) => {
6576
await expect(canvas.getByRole("checkbox", { name: "Email updates" })).toBeInTheDocument();

packages/propel/src/components/checkbox-group-field/checkbox-group-field.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ export const Default: Story = { args: { hint: "At least one channel is recommend
3636
*/
3737
export const Invalid: Story = {
3838
args: { error: "Choose at least one channel." },
39+
};
40+
41+
/**
42+
* Interaction test: every option's box propagates `data-invalid` and the danger border class.
43+
* Tagged out of the sidebar/docs/manifest while still running under the default `test` tag.
44+
*/
45+
export const InvalidInteraction: Story = {
46+
...Invalid,
47+
tags: ["!dev", "!autodocs", "!manifest"],
3948
play: async ({ canvas }) => {
4049
for (const box of canvas.getAllByRole("checkbox")) {
4150
await expect(box).toHaveAttribute("data-invalid");
@@ -45,6 +54,7 @@ export const Invalid: Story = {
4554
};
4655

4756
export const RendersGroup: Story = {
57+
...Default,
4858
tags: ["!dev", "!autodocs", "!manifest"],
4959
play: async ({ canvas }) => {
5060
await expect(canvas.getByRole("checkbox", { name: "Email" })).toBeInTheDocument();

packages/propel/src/components/checkbox-group/checkbox-group.stories.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ export const Default: Story = {
2727
<Checkbox value="ssh" label="SSH" />
2828
</CheckboxGroup>
2929
),
30+
};
31+
32+
/**
33+
* Interaction test: the default-selected row reports `aria-checked="true"`. Tagged out of the
34+
* sidebar/docs/manifest while still running under the default `test` tag.
35+
*/
36+
export const DefaultInteraction: Story = {
37+
...Default,
38+
tags: ["!dev", "!autodocs", "!manifest"],
3039
play: async ({ canvas }) => {
3140
await expect(canvas.getByRole("checkbox", { name: "HTTPS" })).toHaveAttribute(
3241
"aria-checked",

packages/propel/src/components/checkbox/checkbox.stories.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,20 @@ type Story = StoryObj<typeof meta>;
3939
* Interactive, label-less checkbox — just the box. Toggle it from the controls or by clicking. With
4040
* no visible `label`, the accessible name comes from `aria-label`.
4141
*/
42-
export const Default: Story = {
42+
export const Default: Story = {};
43+
44+
/**
45+
* Interaction test: clicking the box toggles `aria-checked` on and off. Tagged out of the
46+
* sidebar/docs/manifest while still running under the default `test` tag — so a browsing user never
47+
* sees the tick flip on its own.
48+
*/
49+
const Interaction: Story = {
50+
...Default,
51+
};
52+
53+
export const DefaultInteraction: Story = {
54+
...Interaction,
55+
tags: ["!dev", "!autodocs", "!manifest"],
4356
play: async ({ canvas, userEvent }) => {
4457
const checkbox = canvas.getByRole("checkbox");
4558
await expect(checkbox).toHaveAttribute("aria-checked", "false");
@@ -62,6 +75,15 @@ export const States: Story = {
6275
<Checkbox label="Disabled checked" disabled defaultChecked />
6376
</div>
6477
),
78+
};
79+
80+
/**
81+
* Interaction test: the unchecked box mounts no tick while the checked box renders its check icon.
82+
* Tagged out of the sidebar/docs/manifest while still running under the default `test` tag.
83+
*/
84+
export const StatesInteraction: Story = {
85+
...States,
86+
tags: ["!dev", "!autodocs", "!manifest"],
6587
play: async ({ canvas }) => {
6688
const [unchecked, checked] = canvas.getAllByRole("checkbox");
6789
// No tick is rendered while unchecked (the indicator is not mounted).
@@ -105,6 +127,15 @@ export const WithIcon: Story = {
105127
export const Indeterminate: Story = {
106128
parameters: { controls: { disable: true } },
107129
render: () => <Checkbox label="Select all" indeterminate />,
130+
};
131+
132+
/**
133+
* Interaction test: the mixed state reports `aria-checked="mixed"`. Tagged out of the
134+
* sidebar/docs/manifest while still running under the default `test` tag.
135+
*/
136+
export const IndeterminateInteraction: Story = {
137+
...Indeterminate,
138+
tags: ["!dev", "!autodocs", "!manifest"],
108139
play: async ({ canvas }) => {
109140
await expect(canvas.getByRole("checkbox")).toHaveAttribute("aria-checked", "mixed");
110141
},
@@ -129,6 +160,16 @@ export const Invalid: Story = {
129160
</Field>
130161
</div>
131162
),
163+
};
164+
165+
/**
166+
* Interaction test: the invalid `Field` propagates `data-invalid` and the danger border, while the
167+
* checked box keeps the accent fill. Tagged out of the sidebar/docs/manifest while still running
168+
* under the default `test` tag.
169+
*/
170+
export const InvalidInteraction: Story = {
171+
...Invalid,
172+
tags: ["!dev", "!autodocs", "!manifest"],
132173
play: async ({ canvas }) => {
133174
const [resting, unchecked, checked] = canvas.getAllByRole("checkbox");
134175
// The resting box has no field-invalid state.
@@ -153,6 +194,7 @@ export const Invalid: Story = {
153194
* the default `test` tag.
154195
*/
155196
export const KeyboardToggle: Story = {
197+
...Interaction,
156198
tags: ["!dev", "!autodocs", "!manifest"],
157199
args: { label: "Subscribe", defaultChecked: false },
158200
play: async ({ canvas, userEvent }) => {
@@ -188,6 +230,7 @@ export const KeyboardToggle: Story = {
188230
* manifest, but still runs under the default `test` tag.
189231
*/
190232
export const BoxDoesNotShiftOnToggle: Story = {
233+
...Interaction,
191234
tags: ["!dev", "!autodocs", "!manifest"],
192235
parameters: { controls: { disable: true } },
193236
// A controlled wrapper so a single, stable box element can be driven through
@@ -243,6 +286,7 @@ export const BoxDoesNotShiftOnToggle: Story = {
243286
* still runs under the default `test` tag.
244287
*/
245288
export const DisabledDoesNotToggle: Story = {
289+
...Interaction,
246290
tags: ["!dev", "!autodocs", "!manifest"],
247291
args: { label: "Disabled", disabled: true },
248292
play: async ({ canvas, userEvent }) => {

packages/propel/src/components/combobox-field/combobox-field.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Story = StoryObj<typeof meta>;
3939
export const Default: Story = {};
4040

4141
export const RendersInput: Story = {
42+
...Default,
4243
tags: ["!dev", "!autodocs", "!manifest"],
4344
play: async ({ canvas }) => {
4445
await expect(canvas.getByRole("combobox", { name: "Region" })).toBeInTheDocument();
@@ -48,6 +49,16 @@ export const RendersInput: Story = {
4849
/** Setting `error` marks the field invalid, which recolors the input group border to danger. */
4950
export const Invalid: Story = {
5051
args: { error: "Choose a deployment region." },
52+
};
53+
54+
/**
55+
* Interaction test: the invalid field marks the input `aria-invalid`/`data-invalid` and recolors
56+
* the group border. Tagged out of the sidebar/docs/manifest while still running under the default
57+
* `test` tag.
58+
*/
59+
export const InvalidInteraction: Story = {
60+
...Invalid,
61+
tags: ["!dev", "!autodocs", "!manifest"],
5162
play: async ({ canvas }) => {
5263
const input = canvas.getByRole("combobox", { name: "Region" });
5364
await expect(input).toHaveAttribute("aria-invalid", "true");

0 commit comments

Comments
 (0)