@fpkit/acss 0.5.13 → 0.6.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/libs/{chunk-PQ2K3BM6.cjs → chunk-2NRIP6RB.cjs} +3 -3
- package/libs/chunk-33PNJ4LO.cjs +15 -0
- package/libs/chunk-33PNJ4LO.cjs.map +1 -0
- package/libs/chunk-4BZKFPEC.cjs +17 -0
- package/libs/chunk-4BZKFPEC.cjs.map +1 -0
- package/libs/{chunk-772NRB75.js → chunk-5QD3DWFI.js} +2 -2
- package/libs/chunk-6SAHIYCZ.js +7 -0
- package/libs/chunk-6SAHIYCZ.js.map +1 -0
- package/libs/{chunk-3MKLDCKQ.cjs → chunk-6WTC4JXH.cjs} +3 -3
- package/libs/chunk-75QHTLFO.js +7 -0
- package/libs/chunk-75QHTLFO.js.map +1 -0
- package/libs/{chunk-ZANSFMTD.js → chunk-7XPFW7CB.js} +3 -3
- package/libs/chunk-BFK62VX5.js +5 -0
- package/libs/chunk-BFK62VX5.js.map +1 -0
- package/libs/{chunk-ROZI23GS.cjs → chunk-DKTHCQ5P.cjs} +4 -4
- package/libs/chunk-E2AJURUW.cjs +13 -0
- package/libs/chunk-E2AJURUW.cjs.map +1 -0
- package/libs/{chunk-L75OQKEI.cjs → chunk-ENTCUJ3A.cjs} +3 -3
- package/libs/chunk-ENTCUJ3A.cjs.map +1 -0
- package/libs/chunk-F5EYMVQM.js +10 -0
- package/libs/chunk-F5EYMVQM.js.map +1 -0
- package/libs/chunk-FVROL3V5.js +9 -0
- package/libs/chunk-FVROL3V5.js.map +1 -0
- package/libs/chunk-GT77BX4L.cjs +17 -0
- package/libs/chunk-GT77BX4L.cjs.map +1 -0
- package/libs/chunk-GUJSMQ3V.cjs +16 -0
- package/libs/chunk-GUJSMQ3V.cjs.map +1 -0
- package/libs/chunk-HHLNOC5T.js +7 -0
- package/libs/chunk-HHLNOC5T.js.map +1 -0
- package/libs/chunk-HRRHPLER.js +8 -0
- package/libs/chunk-HRRHPLER.js.map +1 -0
- package/libs/chunk-IEB64SWY.js +8 -0
- package/libs/chunk-IEB64SWY.js.map +1 -0
- package/libs/{chunk-NGTJDDFO.js → chunk-IQ76HGVP.js} +2 -2
- package/libs/chunk-IRLFZ3OL.js +9 -0
- package/libs/chunk-IRLFZ3OL.js.map +1 -0
- package/libs/{chunk-JJ43O4Y5.js → chunk-KK47SYZI.js} +2 -2
- package/libs/chunk-O3JIHC5M.cjs +15 -0
- package/libs/chunk-O3JIHC5M.cjs.map +1 -0
- package/libs/chunk-O5XAJ7BY.cjs +18 -0
- package/libs/chunk-O5XAJ7BY.cjs.map +1 -0
- package/libs/chunk-OVWLQYMK.js +10 -0
- package/libs/chunk-OVWLQYMK.js.map +1 -0
- package/libs/chunk-PNWIRCG3.cjs +7 -0
- package/libs/chunk-PNWIRCG3.cjs.map +1 -0
- package/libs/{chunk-D4YLRWAO.cjs → chunk-QVW6W76L.cjs} +6 -6
- package/libs/chunk-T4T6GWYQ.cjs +17 -0
- package/libs/chunk-T4T6GWYQ.cjs.map +1 -0
- package/libs/chunk-TON2YGMD.cjs +9 -0
- package/libs/chunk-TON2YGMD.cjs.map +1 -0
- package/libs/chunk-UEPAWMDF.js +8 -0
- package/libs/chunk-UEPAWMDF.js.map +1 -0
- package/libs/{chunk-LT5KZ2QW.cjs → chunk-US2I5GI7.cjs} +3 -3
- package/libs/{chunk-B7F5FS6D.cjs → chunk-W2UIN7EV.cjs} +3 -3
- package/libs/{chunk-P2DC76ZZ.cjs → chunk-W5TKWBFC.cjs} +3 -3
- package/libs/chunk-WXBFBWYF.cjs +16 -0
- package/libs/chunk-WXBFBWYF.cjs.map +1 -0
- package/libs/{chunk-VUH3FXGJ.js → chunk-X3JCTEPD.js} +5 -5
- package/libs/chunk-X5LGFCWG.js +9 -0
- package/libs/chunk-X5LGFCWG.js.map +1 -0
- package/libs/{chunk-5M57K4SW.js → chunk-Y2PFDELK.js} +2 -2
- package/libs/{chunk-ETFLFC2S.js → chunk-ZFJ4U45S.js} +2 -2
- package/libs/{component-props-a8a2f97e.d.ts → component-props-67d978a2.d.ts} +4 -4
- package/libs/components/alert/alert.css +1 -1
- package/libs/components/alert/alert.css.map +1 -1
- package/libs/components/alert/alert.min.css +2 -2
- package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
- package/libs/components/breadcrumbs/breadcrumb.d.cts +11 -11
- package/libs/components/breadcrumbs/breadcrumb.d.ts +11 -11
- package/libs/components/breadcrumbs/breadcrumb.js +3 -3
- package/libs/components/button.cjs +6 -4
- package/libs/components/button.d.cts +97 -4
- package/libs/components/button.d.ts +97 -4
- package/libs/components/button.js +4 -2
- package/libs/components/card.cjs +7 -7
- package/libs/components/card.d.cts +14 -14
- package/libs/components/card.d.ts +14 -14
- package/libs/components/card.js +2 -2
- package/libs/components/dialog/dialog.cjs +9 -7
- package/libs/components/dialog/dialog.d.cts +3 -3
- package/libs/components/dialog/dialog.d.ts +3 -3
- package/libs/components/dialog/dialog.js +7 -5
- package/libs/components/form/fields.cjs +4 -4
- package/libs/components/form/fields.d.cts +16 -7
- package/libs/components/form/fields.d.ts +16 -7
- package/libs/components/form/fields.js +2 -2
- package/libs/components/form/inputs.cjs +6 -4
- package/libs/components/form/inputs.d.cts +50 -2
- package/libs/components/form/inputs.d.ts +50 -2
- package/libs/components/form/inputs.js +4 -2
- package/libs/components/form/textarea.cjs +5 -4
- package/libs/components/form/textarea.d.cts +32 -23
- package/libs/components/form/textarea.d.ts +32 -23
- package/libs/components/form/textarea.js +3 -2
- package/libs/components/heading/heading.cjs +3 -3
- package/libs/components/heading/heading.d.cts +2 -2
- package/libs/components/heading/heading.d.ts +2 -2
- package/libs/components/heading/heading.js +2 -2
- package/libs/components/icons/icon.cjs +4 -4
- package/libs/components/icons/icon.d.cts +38 -38
- package/libs/components/icons/icon.d.ts +38 -38
- package/libs/components/icons/icon.js +2 -2
- package/libs/components/link/link.cjs +4 -4
- package/libs/components/link/link.css +1 -1
- package/libs/components/link/link.css.map +1 -1
- package/libs/components/link/link.d.cts +3 -19
- package/libs/components/link/link.d.ts +3 -19
- package/libs/components/link/link.js +2 -2
- package/libs/components/link/link.min.css +2 -2
- package/libs/components/list/list.cjs +5 -5
- package/libs/components/list/list.css +1 -0
- package/libs/components/list/list.css.map +1 -0
- package/libs/components/list/list.d.cts +120 -33
- package/libs/components/list/list.d.ts +120 -33
- package/libs/components/list/list.js +2 -2
- package/libs/components/list/list.min.css +3 -0
- package/libs/components/modal.cjs +6 -4
- package/libs/components/modal.d.cts +8 -8
- package/libs/components/modal.d.ts +8 -8
- package/libs/components/modal.js +5 -3
- package/libs/components/nav/nav.cjs +7 -7
- package/libs/components/nav/nav.css +1 -1
- package/libs/components/nav/nav.css.map +1 -1
- package/libs/components/nav/nav.d.cts +550 -34
- package/libs/components/nav/nav.d.ts +550 -34
- package/libs/components/nav/nav.js +3 -3
- package/libs/components/nav/nav.min.css +2 -2
- package/libs/components/popover/popover.d.cts +5 -5
- package/libs/components/popover/popover.d.ts +5 -5
- package/libs/components/tables/table.cjs +5 -5
- package/libs/components/tables/table.d.cts +8 -8
- package/libs/components/tables/table.d.ts +8 -8
- package/libs/components/tables/table.js +2 -2
- package/libs/components/tag/tag.css +1 -1
- package/libs/components/tag/tag.css.map +1 -1
- package/libs/components/tag/tag.min.css +2 -2
- package/libs/components/text/text.cjs +5 -5
- package/libs/components/text/text.d.cts +5 -5
- package/libs/components/text/text.d.ts +5 -5
- package/libs/components/text/text.js +2 -2
- package/libs/form.types-d25ebfac.d.ts +233 -0
- package/libs/{heading-3648c538.d.ts → heading-7446cb46.d.ts} +8 -8
- package/libs/hooks.cjs +9 -4
- package/libs/hooks.d.cts +137 -3
- package/libs/hooks.d.ts +137 -3
- package/libs/hooks.js +4 -3
- package/libs/icons.cjs +3 -3
- package/libs/icons.d.cts +2 -2
- package/libs/icons.d.ts +2 -2
- package/libs/icons.js +2 -2
- package/libs/index.cjs +53 -51
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +338 -49
- package/libs/index.d.ts +338 -49
- package/libs/index.js +24 -22
- package/libs/index.js.map +1 -1
- package/libs/link-5192f411.d.ts +323 -0
- package/libs/list.types-d26de310.d.ts +245 -0
- package/libs/{ui-645f95b5.d.ts → ui-d01b50d4.d.ts} +16 -12
- package/package.json +4 -6
- package/src/components/alert/alert.scss +1 -4
- package/src/components/breadcrumbs/breadcrumb.tsx +4 -1
- package/src/components/buttons/README.mdx +102 -1
- package/src/components/buttons/button.stories.tsx +106 -0
- package/src/components/buttons/button.tsx +82 -52
- package/src/components/dialog/dialog-a11y-review.md +653 -0
- package/src/components/form/README.mdx +725 -43
- package/src/components/form/WCAG-REVIEW.md +654 -0
- package/src/components/form/fields.tsx +10 -1
- package/src/components/form/form.stories.tsx +604 -23
- package/src/components/form/form.tsx +204 -63
- package/src/components/form/form.types.ts +378 -0
- package/src/components/form/input.stories.tsx +71 -3
- package/src/components/form/inputs.tsx +159 -67
- package/src/components/form/select.tsx +122 -66
- package/src/components/form/textarea.tsx +120 -73
- package/src/components/fp.tsx +86 -11
- package/src/components/link/README.mdx +923 -0
- package/src/components/link/link.scss +79 -26
- package/src/components/link/link.stories.tsx +383 -30
- package/src/components/link/link.test.tsx +677 -0
- package/src/components/link/link.tsx +163 -57
- package/src/components/link/link.types.ts +261 -0
- package/src/components/list/README.mdx +764 -0
- package/src/components/list/list.scss +285 -0
- package/src/components/list/list.stories.tsx +514 -27
- package/src/components/list/list.test.tsx +554 -0
- package/src/components/list/list.tsx +153 -51
- package/src/components/list/list.types.ts +255 -0
- package/src/components/nav/ACCESSIBILITY.md +649 -0
- package/src/components/nav/README.mdx +782 -0
- package/src/components/nav/nav.scss +32 -1
- package/src/components/nav/nav.stories.tsx +44 -6
- package/src/components/nav/nav.tsx +302 -51
- package/src/components/nav/nav.types.ts +308 -0
- package/src/components/tag/README.mdx +426 -0
- package/src/components/tag/tag.scss +101 -27
- package/src/components/tag/tag.stories.tsx +384 -10
- package/src/components/tag/tag.test.tsx +210 -0
- package/src/components/tag/tag.tsx +106 -9
- package/src/components/tag/tag.types.ts +107 -0
- package/src/components/ui.tsx +8 -3
- package/src/hooks/use-disabled-state.test.tsx +536 -0
- package/src/hooks/use-disabled-state.ts +246 -0
- package/src/hooks/useDisabledState.md +393 -0
- package/src/hooks.ts +6 -0
- package/src/index.scss +2 -0
- package/src/index.ts +2 -1
- package/src/sass/_globals.scss +2 -7
- package/src/styles/alert/alert.css +1 -3
- package/src/styles/alert/alert.css.map +1 -1
- package/src/styles/index.css +450 -76
- package/src/styles/index.css.map +1 -1
- package/src/styles/link/link.css +45 -28
- package/src/styles/link/link.css.map +1 -1
- package/src/styles/list/list.css +214 -0
- package/src/styles/list/list.css.map +1 -0
- package/src/styles/nav/nav.css +21 -1
- package/src/styles/nav/nav.css.map +1 -1
- package/src/styles/tag/tag.css +113 -35
- package/src/styles/tag/tag.css.map +1 -1
- package/src/styles/utilities/_disabled.scss +58 -0
- package/src/types/shared.ts +43 -6
- package/src/utils/accessibility.ts +109 -0
- package/libs/chunk-2LTJ7HHX.cjs +0 -18
- package/libs/chunk-2LTJ7HHX.cjs.map +0 -1
- package/libs/chunk-2Y7W75TT.js +0 -9
- package/libs/chunk-2Y7W75TT.js.map +0 -1
- package/libs/chunk-5S4ORA4C.cjs +0 -15
- package/libs/chunk-5S4ORA4C.cjs.map +0 -1
- package/libs/chunk-AHDJGCG5.cjs +0 -15
- package/libs/chunk-AHDJGCG5.cjs.map +0 -1
- package/libs/chunk-BHRQBJRY.js +0 -8
- package/libs/chunk-BHRQBJRY.js.map +0 -1
- package/libs/chunk-GZ4QFPRY.js +0 -9
- package/libs/chunk-GZ4QFPRY.js.map +0 -1
- package/libs/chunk-IYUN2EW3.cjs +0 -15
- package/libs/chunk-IYUN2EW3.cjs.map +0 -1
- package/libs/chunk-J32EZPYD.cjs +0 -15
- package/libs/chunk-J32EZPYD.cjs.map +0 -1
- package/libs/chunk-KUKIVRC2.js +0 -7
- package/libs/chunk-KUKIVRC2.js.map +0 -1
- package/libs/chunk-L75OQKEI.cjs.map +0 -1
- package/libs/chunk-M5RRNTVX.cjs +0 -15
- package/libs/chunk-M5RRNTVX.cjs.map +0 -1
- package/libs/chunk-OK5QEIMD.cjs +0 -17
- package/libs/chunk-OK5QEIMD.cjs.map +0 -1
- package/libs/chunk-P7TTEYCD.js +0 -7
- package/libs/chunk-P7TTEYCD.js.map +0 -1
- package/libs/chunk-QLZWHAMK.js +0 -8
- package/libs/chunk-QLZWHAMK.js.map +0 -1
- package/libs/chunk-RIVUMPOG.js +0 -8
- package/libs/chunk-RIVUMPOG.js.map +0 -1
- package/libs/chunk-S7BABR7Z.cjs +0 -13
- package/libs/chunk-S7BABR7Z.cjs.map +0 -1
- package/libs/chunk-SMYRLO3E.js +0 -8
- package/libs/chunk-SMYRLO3E.js.map +0 -1
- package/libs/chunk-TYRCEX2L.js +0 -8
- package/libs/chunk-TYRCEX2L.js.map +0 -1
- package/libs/chunk-XBA562WW.js +0 -8
- package/libs/chunk-XBA562WW.js.map +0 -1
- package/libs/chunk-XTQKWY7W.cjs +0 -32
- package/libs/chunk-XTQKWY7W.cjs.map +0 -1
- package/libs/inputs-f3a216db.d.ts +0 -45
- /package/libs/{chunk-PQ2K3BM6.cjs.map → chunk-2NRIP6RB.cjs.map} +0 -0
- /package/libs/{chunk-772NRB75.js.map → chunk-5QD3DWFI.js.map} +0 -0
- /package/libs/{chunk-3MKLDCKQ.cjs.map → chunk-6WTC4JXH.cjs.map} +0 -0
- /package/libs/{chunk-ZANSFMTD.js.map → chunk-7XPFW7CB.js.map} +0 -0
- /package/libs/{chunk-ROZI23GS.cjs.map → chunk-DKTHCQ5P.cjs.map} +0 -0
- /package/libs/{chunk-NGTJDDFO.js.map → chunk-IQ76HGVP.js.map} +0 -0
- /package/libs/{chunk-JJ43O4Y5.js.map → chunk-KK47SYZI.js.map} +0 -0
- /package/libs/{chunk-D4YLRWAO.cjs.map → chunk-QVW6W76L.cjs.map} +0 -0
- /package/libs/{chunk-LT5KZ2QW.cjs.map → chunk-US2I5GI7.cjs.map} +0 -0
- /package/libs/{chunk-B7F5FS6D.cjs.map → chunk-W2UIN7EV.cjs.map} +0 -0
- /package/libs/{chunk-P2DC76ZZ.cjs.map → chunk-W5TKWBFC.cjs.map} +0 -0
- /package/libs/{chunk-VUH3FXGJ.js.map → chunk-X3JCTEPD.js.map} +0 -0
- /package/libs/{chunk-5M57K4SW.js.map → chunk-Y2PFDELK.js.map} +0 -0
- /package/libs/{chunk-ETFLFC2S.js.map → chunk-ZFJ4U45S.js.map} +0 -0
|
@@ -1,49 +1,630 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { StoryObj, Meta } from "@storybook/react-vite";
|
|
2
|
-
import { within, expect } from "storybook/test";
|
|
3
|
+
import { within, expect, userEvent, fn } from "storybook/test";
|
|
4
|
+
import { useState } from "react";
|
|
3
5
|
|
|
4
6
|
import Form from "./form";
|
|
5
7
|
import "./form.scss";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Type assertion to resolve React type compatibility issues
|
|
10
|
+
const FormComponent = Form as unknown as typeof Form;
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof FormComponent> = {
|
|
13
|
+
title: "FP.REACT Forms/Form",
|
|
14
|
+
tags: ["rc", "autodocs"],
|
|
15
|
+
component: FormComponent,
|
|
11
16
|
parameters: {
|
|
12
17
|
docs: {
|
|
13
18
|
description: {
|
|
14
|
-
component:
|
|
19
|
+
component: `
|
|
20
|
+
An accessible HTML form wrapper with validation support and compound component pattern.
|
|
21
|
+
Provides proper ARIA attributes, form submission handling, and validation state management.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
- ✅ WCAG 2.1 AA compliant with proper ARIA attributes
|
|
25
|
+
- ✅ Compound component pattern (Form.Field, Form.Input, etc.)
|
|
26
|
+
- ✅ Form submission and validation state management
|
|
27
|
+
- ✅ Keyboard navigation support
|
|
28
|
+
- ✅ Controlled and uncontrolled form patterns
|
|
29
|
+
`,
|
|
15
30
|
},
|
|
16
31
|
},
|
|
17
32
|
},
|
|
18
33
|
args: {
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
name: "contact-form",
|
|
35
|
+
"aria-label": "Contact form",
|
|
21
36
|
},
|
|
22
|
-
} as
|
|
37
|
+
} as Meta;
|
|
23
38
|
|
|
24
39
|
export default meta;
|
|
25
40
|
type Story = StoryObj<typeof Form>;
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
// Mock submit handler for stories
|
|
43
|
+
const handleSubmit = fn();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Basic form example with required fields
|
|
47
|
+
*/
|
|
48
|
+
export const BasicForm: Story = {
|
|
28
49
|
args: {
|
|
50
|
+
onSubmit: handleSubmit,
|
|
29
51
|
children: (
|
|
30
52
|
<>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
</
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
</
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
</
|
|
40
|
-
<button type="submit">Submit
|
|
53
|
+
<FormComponent.Field label="Name" labelFor="name" required>
|
|
54
|
+
<FormComponent.Input id="name" name="name" required />
|
|
55
|
+
</FormComponent.Field>
|
|
56
|
+
<FormComponent.Field label="Email" labelFor="email" required>
|
|
57
|
+
<FormComponent.Input id="email" name="email" type="email" required />
|
|
58
|
+
</FormComponent.Field>
|
|
59
|
+
<FormComponent.Field label="Message" labelFor="message">
|
|
60
|
+
<FormComponent.Textarea id="message" name="message" rows={4} />
|
|
61
|
+
</FormComponent.Field>
|
|
62
|
+
<button type="submit">Submit</button>
|
|
41
63
|
</>
|
|
42
64
|
),
|
|
43
65
|
},
|
|
44
|
-
play: async ({ canvasElement }) => {
|
|
66
|
+
play: async ({ canvasElement, step }) => {
|
|
45
67
|
const canvas = within(canvasElement);
|
|
46
|
-
|
|
47
|
-
await
|
|
68
|
+
|
|
69
|
+
await step("Form renders correctly", async () => {
|
|
70
|
+
const form = canvas.getByRole("form");
|
|
71
|
+
expect(form).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await step("Required fields are marked", async () => {
|
|
75
|
+
expect(
|
|
76
|
+
canvas.getByText("*", { selector: ".field-required" })
|
|
77
|
+
).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await step("All inputs are accessible", async () => {
|
|
81
|
+
expect(canvas.getByLabelText(/name/i)).toBeInTheDocument();
|
|
82
|
+
expect(canvas.getByLabelText(/email/i)).toBeInTheDocument();
|
|
83
|
+
expect(canvas.getByLabelText(/message/i)).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await step("Submit button is present", async () => {
|
|
87
|
+
expect(
|
|
88
|
+
canvas.getByRole("button", { name: /submit/i })
|
|
89
|
+
).toBeInTheDocument();
|
|
90
|
+
});
|
|
48
91
|
},
|
|
49
92
|
} as Story;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Form with validation states and error messages
|
|
96
|
+
*/
|
|
97
|
+
export const WithValidation: Story = {
|
|
98
|
+
render: function ValidationExample() {
|
|
99
|
+
const [email, setEmail] = useState("");
|
|
100
|
+
const [emailError, setEmailError] = useState("");
|
|
101
|
+
|
|
102
|
+
const validateEmail = (value: string) => {
|
|
103
|
+
if (!value) {
|
|
104
|
+
setEmailError("Email is required");
|
|
105
|
+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
106
|
+
setEmailError("Please enter a valid email address");
|
|
107
|
+
} else {
|
|
108
|
+
setEmailError("");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<FormComponent
|
|
114
|
+
aria-label="Registration form"
|
|
115
|
+
onSubmit={(e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
handleSubmit(e);
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<FormComponent.Field
|
|
121
|
+
label="Email"
|
|
122
|
+
labelFor="email-validation"
|
|
123
|
+
required
|
|
124
|
+
errorMessage={emailError}
|
|
125
|
+
>
|
|
126
|
+
<FormComponent.Input
|
|
127
|
+
id="email-validation"
|
|
128
|
+
name="email"
|
|
129
|
+
type="email"
|
|
130
|
+
value={email}
|
|
131
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
132
|
+
onBlur={(e) => validateEmail(e.target.value)}
|
|
133
|
+
validationState={emailError ? "invalid" : email ? "valid" : "none"}
|
|
134
|
+
required
|
|
135
|
+
/>
|
|
136
|
+
</FormComponent.Field>
|
|
137
|
+
<button type="submit">Register</button>
|
|
138
|
+
</FormComponent>
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
play: async ({ canvasElement, step }) => {
|
|
142
|
+
const canvas = within(canvasElement);
|
|
143
|
+
const user = userEvent.setup();
|
|
144
|
+
|
|
145
|
+
await step("Enter invalid email", async () => {
|
|
146
|
+
const emailInput = canvas.getByLabelText(/email/i);
|
|
147
|
+
await user.type(emailInput, "invalid-email");
|
|
148
|
+
await user.tab();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await step("Error message is displayed", async () => {
|
|
152
|
+
expect(
|
|
153
|
+
canvas.getByText(/please enter a valid email/i)
|
|
154
|
+
).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await step("Input has aria-invalid", async () => {
|
|
158
|
+
const emailInput = canvas.getByLabelText(/email/i);
|
|
159
|
+
expect(emailInput).toHaveAttribute("aria-invalid", "true");
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Form with hint text to guide users
|
|
166
|
+
*/
|
|
167
|
+
export const WithHintText: Story = {
|
|
168
|
+
args: {
|
|
169
|
+
"aria-label": "Account creation form",
|
|
170
|
+
children: (
|
|
171
|
+
<>
|
|
172
|
+
<FormComponent.Field
|
|
173
|
+
label="Username"
|
|
174
|
+
labelFor="username"
|
|
175
|
+
required
|
|
176
|
+
hintText="Must be 3-20 characters, letters and numbers only"
|
|
177
|
+
>
|
|
178
|
+
<FormComponent.Input
|
|
179
|
+
id="username"
|
|
180
|
+
name="username"
|
|
181
|
+
minLength={3}
|
|
182
|
+
maxLength={20}
|
|
183
|
+
pattern="[a-zA-Z0-9]+"
|
|
184
|
+
required
|
|
185
|
+
/>
|
|
186
|
+
</FormComponent.Field>
|
|
187
|
+
<FormComponent.Field
|
|
188
|
+
label="Password"
|
|
189
|
+
labelFor="password"
|
|
190
|
+
required
|
|
191
|
+
hintText="Minimum 8 characters, include uppercase, lowercase, and number"
|
|
192
|
+
>
|
|
193
|
+
<FormComponent.Input
|
|
194
|
+
id="password"
|
|
195
|
+
name="password"
|
|
196
|
+
type="password"
|
|
197
|
+
minLength={8}
|
|
198
|
+
required
|
|
199
|
+
/>
|
|
200
|
+
</FormComponent.Field>
|
|
201
|
+
<button type="submit">Create Account</button>
|
|
202
|
+
</>
|
|
203
|
+
),
|
|
204
|
+
},
|
|
205
|
+
play: async ({ canvasElement, step }) => {
|
|
206
|
+
const canvas = within(canvasElement);
|
|
207
|
+
|
|
208
|
+
await step("Hint text is visible", async () => {
|
|
209
|
+
expect(canvas.getByText(/must be 3-20 characters/i)).toBeInTheDocument();
|
|
210
|
+
expect(canvas.getByText(/minimum 8 characters/i)).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await step("Hint text is associated with inputs", async () => {
|
|
214
|
+
const usernameInput = canvas.getByLabelText(/username/i);
|
|
215
|
+
expect(usernameInput).toHaveAttribute(
|
|
216
|
+
"aria-describedby",
|
|
217
|
+
"username-hint"
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Form with select dropdown
|
|
225
|
+
*/
|
|
226
|
+
export const WithSelect: Story = {
|
|
227
|
+
args: {
|
|
228
|
+
"aria-label": "Profile form",
|
|
229
|
+
children: (
|
|
230
|
+
<>
|
|
231
|
+
<FormComponent.Field label="Full Name" labelFor="fullname" required>
|
|
232
|
+
<FormComponent.Input id="fullname" name="fullname" required />
|
|
233
|
+
</FormComponent.Field>
|
|
234
|
+
<FormComponent.Field label="Country" labelFor="country" required>
|
|
235
|
+
<FormComponent.Select id="country" name="country" required>
|
|
236
|
+
<FormComponent.Select.Option value="">
|
|
237
|
+
Select a country
|
|
238
|
+
</FormComponent.Select.Option>
|
|
239
|
+
<FormComponent.Select.Option value="us">
|
|
240
|
+
United States
|
|
241
|
+
</FormComponent.Select.Option>
|
|
242
|
+
<FormComponent.Select.Option value="ca">
|
|
243
|
+
Canada
|
|
244
|
+
</FormComponent.Select.Option>
|
|
245
|
+
<FormComponent.Select.Option value="uk">
|
|
246
|
+
United Kingdom
|
|
247
|
+
</FormComponent.Select.Option>
|
|
248
|
+
<FormComponent.Select.Option value="au">
|
|
249
|
+
Australia
|
|
250
|
+
</FormComponent.Select.Option>
|
|
251
|
+
</FormComponent.Select>
|
|
252
|
+
</FormComponent.Field>
|
|
253
|
+
<button type="submit">Save Profile</button>
|
|
254
|
+
</>
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
play: async ({ canvasElement, step }) => {
|
|
258
|
+
const canvas = within(canvasElement);
|
|
259
|
+
const user = userEvent.setup();
|
|
260
|
+
|
|
261
|
+
await step("Select is accessible", async () => {
|
|
262
|
+
const select = canvas.getByLabelText(/country/i);
|
|
263
|
+
expect(select).toBeInTheDocument();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
await step("Select can be changed", async () => {
|
|
267
|
+
const select = canvas.getByLabelText(/country/i);
|
|
268
|
+
await user.selectOptions(select, "us");
|
|
269
|
+
expect(select).toHaveValue("us");
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Form with optional fields
|
|
276
|
+
*/
|
|
277
|
+
export const WithOptionalFields: Story = {
|
|
278
|
+
args: {
|
|
279
|
+
"aria-label": "Contact preferences",
|
|
280
|
+
children: (
|
|
281
|
+
<>
|
|
282
|
+
<FormComponent.Field label="Email" labelFor="email-req" required>
|
|
283
|
+
<FormComponent.Input
|
|
284
|
+
id="email-req"
|
|
285
|
+
name="email"
|
|
286
|
+
type="email"
|
|
287
|
+
required
|
|
288
|
+
/>
|
|
289
|
+
</FormComponent.Field>
|
|
290
|
+
<FormComponent.Field label="Phone" labelFor="phone-opt" optional>
|
|
291
|
+
<FormComponent.Input id="phone-opt" name="phone" type="tel" />
|
|
292
|
+
</FormComponent.Field>
|
|
293
|
+
<FormComponent.Field label="Address" labelFor="address-opt" optional>
|
|
294
|
+
<FormComponent.Textarea id="address-opt" name="address" rows={3} />
|
|
295
|
+
</FormComponent.Field>
|
|
296
|
+
<button type="submit">Save Preferences</button>
|
|
297
|
+
</>
|
|
298
|
+
),
|
|
299
|
+
},
|
|
300
|
+
play: async ({ canvasElement, step }) => {
|
|
301
|
+
const canvas = within(canvasElement);
|
|
302
|
+
|
|
303
|
+
await step("Required field is marked with asterisk", async () => {
|
|
304
|
+
expect(
|
|
305
|
+
canvas.getByText("*", { selector: ".field-required" })
|
|
306
|
+
).toBeInTheDocument();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await step("Optional fields are marked", async () => {
|
|
310
|
+
const optionalMarkers = canvas.getAllByText("(optional)");
|
|
311
|
+
expect(optionalMarkers).toHaveLength(2);
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Form submission loading state
|
|
318
|
+
*/
|
|
319
|
+
export const LoadingState: Story = {
|
|
320
|
+
render: function LoadingStateExample() {
|
|
321
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
322
|
+
|
|
323
|
+
const handleFormSubmit = (e: React.FormEvent) => {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
setIsSubmitting(true);
|
|
326
|
+
setTimeout(() => setIsSubmitting(false), 2000);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<FormComponent
|
|
331
|
+
aria-label="Submission form"
|
|
332
|
+
onSubmit={handleFormSubmit}
|
|
333
|
+
status={isSubmitting ? "submitting" : "idle"}
|
|
334
|
+
>
|
|
335
|
+
<FormComponent.Field label="Name" labelFor="loading-name" required>
|
|
336
|
+
<FormComponent.Input
|
|
337
|
+
id="loading-name"
|
|
338
|
+
name="name"
|
|
339
|
+
disabled={isSubmitting}
|
|
340
|
+
required
|
|
341
|
+
/>
|
|
342
|
+
</FormComponent.Field>
|
|
343
|
+
<FormComponent.Field label="Email" labelFor="loading-email" required>
|
|
344
|
+
<FormComponent.Input
|
|
345
|
+
id="loading-email"
|
|
346
|
+
name="email"
|
|
347
|
+
type="email"
|
|
348
|
+
disabled={isSubmitting}
|
|
349
|
+
required
|
|
350
|
+
/>
|
|
351
|
+
</FormComponent.Field>
|
|
352
|
+
<button type="submit" disabled={isSubmitting}>
|
|
353
|
+
{isSubmitting ? "Submitting..." : "Submit"}
|
|
354
|
+
</button>
|
|
355
|
+
</FormComponent>
|
|
356
|
+
);
|
|
357
|
+
},
|
|
358
|
+
play: async ({ canvasElement, step }) => {
|
|
359
|
+
const canvas = within(canvasElement);
|
|
360
|
+
const user = userEvent.setup();
|
|
361
|
+
|
|
362
|
+
await step("Submit the form", async () => {
|
|
363
|
+
const submitButton = canvas.getByRole("button");
|
|
364
|
+
await user.click(submitButton);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await step("Form shows loading state", async () => {
|
|
368
|
+
const form = canvas.getByRole("form");
|
|
369
|
+
expect(form).toHaveAttribute("aria-busy", "true");
|
|
370
|
+
expect(form).toHaveAttribute("data-status", "submitting");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await step("Submit button shows loading text", async () => {
|
|
374
|
+
expect(canvas.getByText(/submitting/i)).toBeInTheDocument();
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Complete registration form example
|
|
381
|
+
*/
|
|
382
|
+
export const RegistrationForm: Story = {
|
|
383
|
+
args: {
|
|
384
|
+
name: "registration",
|
|
385
|
+
"aria-labelledby": "registration-heading",
|
|
386
|
+
children: (
|
|
387
|
+
<>
|
|
388
|
+
<h2 id="registration-heading">Create Your Account</h2>
|
|
389
|
+
<FormComponent.Field
|
|
390
|
+
label="Email Address"
|
|
391
|
+
labelFor="reg-email"
|
|
392
|
+
required
|
|
393
|
+
hintText="We'll never share your email"
|
|
394
|
+
>
|
|
395
|
+
<FormComponent.Input
|
|
396
|
+
id="reg-email"
|
|
397
|
+
name="email"
|
|
398
|
+
type="email"
|
|
399
|
+
autoComplete="email"
|
|
400
|
+
required
|
|
401
|
+
/>
|
|
402
|
+
</FormComponent.Field>
|
|
403
|
+
<FormComponent.Field
|
|
404
|
+
label="Password"
|
|
405
|
+
labelFor="reg-password"
|
|
406
|
+
required
|
|
407
|
+
hintText="At least 8 characters"
|
|
408
|
+
>
|
|
409
|
+
<FormComponent.Input
|
|
410
|
+
id="reg-password"
|
|
411
|
+
name="password"
|
|
412
|
+
type="password"
|
|
413
|
+
autoComplete="new-password"
|
|
414
|
+
minLength={8}
|
|
415
|
+
required
|
|
416
|
+
/>
|
|
417
|
+
</FormComponent.Field>
|
|
418
|
+
<FormComponent.Field label="Country" labelFor="reg-country" required>
|
|
419
|
+
<FormComponent.Select id="reg-country" name="country" required>
|
|
420
|
+
<FormComponent.Select.Option value="">
|
|
421
|
+
Choose your country
|
|
422
|
+
</FormComponent.Select.Option>
|
|
423
|
+
<FormComponent.Select.Option value="us">
|
|
424
|
+
United States
|
|
425
|
+
</FormComponent.Select.Option>
|
|
426
|
+
<FormComponent.Select.Option value="ca">
|
|
427
|
+
Canada
|
|
428
|
+
</FormComponent.Select.Option>
|
|
429
|
+
<FormComponent.Select.Option value="uk">
|
|
430
|
+
United Kingdom
|
|
431
|
+
</FormComponent.Select.Option>
|
|
432
|
+
</FormComponent.Select>
|
|
433
|
+
</FormComponent.Field>
|
|
434
|
+
<FormComponent.Field label="Bio" labelFor="reg-bio" optional>
|
|
435
|
+
<FormComponent.Textarea
|
|
436
|
+
id="reg-bio"
|
|
437
|
+
name="bio"
|
|
438
|
+
placeholder="Tell us about yourself"
|
|
439
|
+
maxLength={500}
|
|
440
|
+
rows={4}
|
|
441
|
+
/>
|
|
442
|
+
</FormComponent.Field>
|
|
443
|
+
<button type="submit" style={{ marginTop: "1rem" }}>
|
|
444
|
+
Create Account
|
|
445
|
+
</button>
|
|
446
|
+
</>
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
play: async ({ canvasElement, step }) => {
|
|
450
|
+
const canvas = within(canvasElement);
|
|
451
|
+
|
|
452
|
+
await step("All form fields render", async () => {
|
|
453
|
+
expect(canvas.getByLabelText(/email address/i)).toBeInTheDocument();
|
|
454
|
+
expect(canvas.getByLabelText(/password/i)).toBeInTheDocument();
|
|
455
|
+
expect(canvas.getByLabelText(/country/i)).toBeInTheDocument();
|
|
456
|
+
expect(canvas.getByLabelText(/bio/i)).toBeInTheDocument();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await step("Form is properly labeled", async () => {
|
|
460
|
+
const form = canvas.getByRole("form");
|
|
461
|
+
expect(form).toHaveAttribute("aria-labelledby", "registration-heading");
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Form with onEnter accessibility handlers
|
|
468
|
+
* Demonstrates the onEnter prop for keyboard-driven workflows
|
|
469
|
+
*/
|
|
470
|
+
export const WithOnEnterHandler: Story = {
|
|
471
|
+
render: function OnEnterHandlerExample() {
|
|
472
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
473
|
+
const [comments, setComments] = useState("");
|
|
474
|
+
const [category, setCategory] = useState("");
|
|
475
|
+
const [messages, setMessages] = useState<string[]>([]);
|
|
476
|
+
|
|
477
|
+
const handleSearch = () => {
|
|
478
|
+
setMessages((prev) => [...prev, `🔍 Searching for: "${searchQuery}"`]);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const handleCommentSubmit = () => {
|
|
482
|
+
if (comments.trim()) {
|
|
483
|
+
setMessages((prev) => [
|
|
484
|
+
...prev,
|
|
485
|
+
`💬 Comment submitted: "${comments.trim()}"`,
|
|
486
|
+
]);
|
|
487
|
+
setComments("");
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const handleCategorySelect = () => {
|
|
492
|
+
setMessages((prev) => [...prev, `📁 Category selected: "${category}"`]);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<div>
|
|
497
|
+
<FormComponent aria-label="Keyboard accessibility demo">
|
|
498
|
+
<FormComponent.Field
|
|
499
|
+
label="Search"
|
|
500
|
+
labelFor="search-input"
|
|
501
|
+
hintText="Press Enter to search"
|
|
502
|
+
>
|
|
503
|
+
<FormComponent.Input
|
|
504
|
+
id="search-input"
|
|
505
|
+
name="search"
|
|
506
|
+
type="search"
|
|
507
|
+
placeholder="Type and press Enter..."
|
|
508
|
+
value={searchQuery}
|
|
509
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
510
|
+
setSearchQuery(e.target.value)
|
|
511
|
+
}
|
|
512
|
+
onEnter={handleSearch}
|
|
513
|
+
/>
|
|
514
|
+
</FormComponent.Field>
|
|
515
|
+
|
|
516
|
+
<FormComponent.Field
|
|
517
|
+
label="Comments"
|
|
518
|
+
labelFor="comments-textarea"
|
|
519
|
+
hintText="Press Enter to submit (Shift+Enter for new line)"
|
|
520
|
+
>
|
|
521
|
+
<FormComponent.Textarea
|
|
522
|
+
id="comments-textarea"
|
|
523
|
+
name="comments"
|
|
524
|
+
placeholder="Type your comment..."
|
|
525
|
+
value={comments}
|
|
526
|
+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
527
|
+
setComments(e.target.value)
|
|
528
|
+
}
|
|
529
|
+
onEnter={handleCommentSubmit}
|
|
530
|
+
rows={4}
|
|
531
|
+
/>
|
|
532
|
+
</FormComponent.Field>
|
|
533
|
+
|
|
534
|
+
<FormComponent.Field
|
|
535
|
+
label="Category"
|
|
536
|
+
labelFor="category-select"
|
|
537
|
+
hintText="Press Enter after selecting"
|
|
538
|
+
>
|
|
539
|
+
<FormComponent.Select
|
|
540
|
+
id="category-select"
|
|
541
|
+
name="category"
|
|
542
|
+
value={category}
|
|
543
|
+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
|
544
|
+
setCategory(e.target.value)
|
|
545
|
+
}
|
|
546
|
+
onEnter={handleCategorySelect}
|
|
547
|
+
>
|
|
548
|
+
<FormComponent.Select.Option value="">
|
|
549
|
+
Select category
|
|
550
|
+
</FormComponent.Select.Option>
|
|
551
|
+
<FormComponent.Select.Option value="bug">
|
|
552
|
+
Bug Report
|
|
553
|
+
</FormComponent.Select.Option>
|
|
554
|
+
<FormComponent.Select.Option value="feature">
|
|
555
|
+
Feature Request
|
|
556
|
+
</FormComponent.Select.Option>
|
|
557
|
+
<FormComponent.Select.Option value="question">
|
|
558
|
+
Question
|
|
559
|
+
</FormComponent.Select.Option>
|
|
560
|
+
</FormComponent.Select>
|
|
561
|
+
</FormComponent.Field>
|
|
562
|
+
</FormComponent>
|
|
563
|
+
|
|
564
|
+
{messages.length > 0 && (
|
|
565
|
+
<div
|
|
566
|
+
style={{
|
|
567
|
+
marginTop: "1rem",
|
|
568
|
+
padding: "1rem",
|
|
569
|
+
backgroundColor: "#f0f0f0",
|
|
570
|
+
borderRadius: "0.25rem",
|
|
571
|
+
}}
|
|
572
|
+
>
|
|
573
|
+
<h3 style={{ marginTop: 0 }}>Action Log:</h3>
|
|
574
|
+
<ul style={{ margin: 0, paddingLeft: "1.5rem" }}>
|
|
575
|
+
{messages.map((msg, idx) => (
|
|
576
|
+
<li key={idx}>{msg}</li>
|
|
577
|
+
))}
|
|
578
|
+
</ul>
|
|
579
|
+
</div>
|
|
580
|
+
)}
|
|
581
|
+
</div>
|
|
582
|
+
);
|
|
583
|
+
},
|
|
584
|
+
play: async ({ canvasElement, step }) => {
|
|
585
|
+
const canvas = within(canvasElement);
|
|
586
|
+
const user = userEvent.setup();
|
|
587
|
+
|
|
588
|
+
await step("Input onEnter: Type and press Enter", async () => {
|
|
589
|
+
const searchInput = canvas.getByLabelText(/search/i);
|
|
590
|
+
await user.type(searchInput, "accessibility test");
|
|
591
|
+
await user.type(searchInput, "{Enter}");
|
|
592
|
+
|
|
593
|
+
// Verify action was logged
|
|
594
|
+
expect(
|
|
595
|
+
canvas.getByText(/Searching for: "accessibility test"/i)
|
|
596
|
+
).toBeInTheDocument();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
await step("Textarea onEnter: Enter without Shift", async () => {
|
|
600
|
+
const textarea = canvas.getByLabelText(/comments/i);
|
|
601
|
+
await user.type(textarea, "This is a test comment");
|
|
602
|
+
await user.type(textarea, "{Enter}");
|
|
603
|
+
|
|
604
|
+
// Verify comment was submitted
|
|
605
|
+
expect(
|
|
606
|
+
canvas.getByText(/Comment submitted: "This is a test comment"/i)
|
|
607
|
+
).toBeInTheDocument();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
await step(
|
|
611
|
+
"Textarea Shift+Enter: Adds newline without triggering onEnter",
|
|
612
|
+
async () => {
|
|
613
|
+
const textarea = canvas.getByLabelText(/comments/i);
|
|
614
|
+
await user.type(textarea, "Line 1{Shift>}{Enter}{/Shift}Line 2");
|
|
615
|
+
|
|
616
|
+
// Verify textarea contains newline
|
|
617
|
+
expect(textarea).toHaveValue("Line 1\nLine 2");
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
await step("Select onEnter: Select and press Enter", async () => {
|
|
622
|
+
const select = canvas.getByLabelText(/category/i);
|
|
623
|
+
await user.selectOptions(select, "bug");
|
|
624
|
+
await user.type(select, "{Enter}");
|
|
625
|
+
|
|
626
|
+
// Verify category selection was logged
|
|
627
|
+
expect(canvas.getByText(/Category selected: "bug"/i)).toBeInTheDocument();
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
};
|