@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
|
@@ -68,18 +68,86 @@ export const DefaultRequired: Story = {
|
|
|
68
68
|
},
|
|
69
69
|
} as Story;
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Disabled input using WCAG-compliant aria-disabled pattern.
|
|
73
|
+
*
|
|
74
|
+
* Key accessibility features implemented by the optimized useDisabledState hook:
|
|
75
|
+
* - Uses aria-disabled instead of native disabled attribute
|
|
76
|
+
* - Remains keyboard focusable (in tab order)
|
|
77
|
+
* - Prevents all interactions (typing, onChange events)
|
|
78
|
+
* - Screen readers can discover and announce disabled state
|
|
79
|
+
* - Automatic className merging (.is-disabled + custom classes)
|
|
80
|
+
*/
|
|
71
81
|
export const InputDisabled: Story = {
|
|
72
82
|
parameters: {
|
|
73
83
|
docs: {
|
|
74
84
|
description: {
|
|
75
|
-
story:
|
|
76
|
-
|
|
85
|
+
story: `
|
|
86
|
+
Displays a disabled input with \`aria-disabled="true"\`.
|
|
87
|
+
|
|
88
|
+
**Why aria-disabled instead of disabled?**
|
|
89
|
+
- Keeps input in tab order for screen reader users
|
|
90
|
+
- Allows focus for screen reader announcement
|
|
91
|
+
- Better contrast control for WCAG AA compliance
|
|
92
|
+
- Can show tooltips/help text even when disabled
|
|
93
|
+
|
|
94
|
+
Try tabbing to the input - it receives focus!
|
|
95
|
+
Try typing - interactions are prevented by the hook.
|
|
96
|
+
`,
|
|
77
97
|
},
|
|
78
98
|
},
|
|
79
99
|
},
|
|
80
100
|
args: {
|
|
81
101
|
type: "text",
|
|
82
|
-
|
|
102
|
+
disabled: true,
|
|
103
|
+
placeholder: "This input is disabled",
|
|
104
|
+
},
|
|
105
|
+
play: async ({ canvasElement, step }) => {
|
|
106
|
+
const canvas = within(canvasElement);
|
|
107
|
+
const input = canvas.getByRole("textbox");
|
|
108
|
+
|
|
109
|
+
await step("Disabled input has aria-disabled attribute", async () => {
|
|
110
|
+
expect(input).toHaveAttribute("aria-disabled", "true");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await step("Disabled input remains focusable", async () => {
|
|
114
|
+
await userEvent.tab();
|
|
115
|
+
expect(input).toHaveFocus();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await step("Disabled input has .is-disabled class", async () => {
|
|
119
|
+
expect(input).toHaveClass("is-disabled");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await step("Disabled input prevents typing interactions", async () => {
|
|
123
|
+
const initialValue = input.getAttribute("value") || "";
|
|
124
|
+
await userEvent.type(input, "test");
|
|
125
|
+
// Value should remain unchanged due to disabled state
|
|
126
|
+
expect(input).toHaveValue(initialValue);
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
} as Story;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Disabled input with custom classes.
|
|
133
|
+
*
|
|
134
|
+
* Demonstrates the hook's automatic className merging feature.
|
|
135
|
+
* The .is-disabled class is automatically combined with custom classes.
|
|
136
|
+
*/
|
|
137
|
+
export const DisabledWithCustomClass: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
type: "text",
|
|
140
|
+
disabled: true,
|
|
141
|
+
classes: "custom-input highlight-disabled",
|
|
142
|
+
placeholder: "Disabled with custom classes",
|
|
143
|
+
},
|
|
144
|
+
parameters: {
|
|
145
|
+
docs: {
|
|
146
|
+
description: {
|
|
147
|
+
story:
|
|
148
|
+
"Shows how the optimized hook automatically merges `.is-disabled` with custom classes (`custom-input highlight-disabled`).",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
83
151
|
},
|
|
84
152
|
} as Story;
|
|
85
153
|
|
|
@@ -1,78 +1,170 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import FP from "../fp";
|
|
3
|
+
import type { InputProps } from "./form.types";
|
|
4
|
+
import { useDisabledState } from "../../hooks/use-disabled-state";
|
|
5
|
+
import { resolveDisabledState } from "../../utils/accessibility";
|
|
3
6
|
|
|
4
|
-
export type InputProps
|
|
5
|
-
/**
|
|
6
|
-
* The type of the input.
|
|
7
|
-
*/
|
|
8
|
-
type?: "text" | "password" | "email" | "number" | "tel" | "url" | "search";
|
|
7
|
+
export type { InputProps } from "./form.types";
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Input component - Accessible text input with validation support
|
|
11
|
+
*
|
|
12
|
+
* A flexible input component that supports various input types, validation states,
|
|
13
|
+
* and proper ARIA attributes for accessibility. Integrates seamlessly with the
|
|
14
|
+
* Field component for complete form control composition.
|
|
15
|
+
*
|
|
16
|
+
* @component
|
|
17
|
+
* @example
|
|
18
|
+
* // Basic text input
|
|
19
|
+
* <Input
|
|
20
|
+
* id="username"
|
|
21
|
+
* name="username"
|
|
22
|
+
* placeholder="Enter username"
|
|
23
|
+
* required
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Input with error state
|
|
28
|
+
* <Input
|
|
29
|
+
* id="email"
|
|
30
|
+
* type="email"
|
|
31
|
+
* validationState="invalid"
|
|
32
|
+
* errorMessage="Please enter a valid email"
|
|
33
|
+
* aria-describedby="email-error"
|
|
34
|
+
* />
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Controlled input with validation
|
|
38
|
+
* <Input
|
|
39
|
+
* id="password"
|
|
40
|
+
* type="password"
|
|
41
|
+
* value={password}
|
|
42
|
+
* onChange={(e) => setPassword(e.target.value)}
|
|
43
|
+
* minLength={8}
|
|
44
|
+
* required
|
|
45
|
+
* />
|
|
46
|
+
*
|
|
47
|
+
* @param {InputProps} props - Component props
|
|
48
|
+
* @returns {JSX.Element} Input element with proper accessibility attributes
|
|
49
|
+
*
|
|
50
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/error-identification.html|WCAG 3.3.1 Error Identification}
|
|
51
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html|WCAG 4.1.2 Name, Role, Value}
|
|
52
|
+
*/
|
|
53
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
54
|
+
(
|
|
55
|
+
{
|
|
56
|
+
type = "text",
|
|
57
|
+
name,
|
|
58
|
+
value,
|
|
59
|
+
defaultValue,
|
|
60
|
+
placeholder,
|
|
61
|
+
id,
|
|
62
|
+
styles,
|
|
63
|
+
classes,
|
|
64
|
+
isDisabled, // Legacy support
|
|
65
|
+
disabled,
|
|
66
|
+
readOnly,
|
|
67
|
+
required = false,
|
|
68
|
+
validationState = "none",
|
|
69
|
+
errorMessage,
|
|
70
|
+
hintText,
|
|
71
|
+
onChange,
|
|
72
|
+
onBlur,
|
|
73
|
+
onFocus,
|
|
74
|
+
onKeyDown,
|
|
75
|
+
onEnter,
|
|
76
|
+
maxLength,
|
|
77
|
+
minLength,
|
|
78
|
+
pattern,
|
|
79
|
+
autoComplete,
|
|
80
|
+
autoFocus = false,
|
|
81
|
+
inputMode,
|
|
82
|
+
...props
|
|
83
|
+
},
|
|
84
|
+
ref
|
|
85
|
+
) => {
|
|
86
|
+
// Support both `disabled` and `isDisabled` props (legacy compatibility)
|
|
87
|
+
const isInputDisabled = resolveDisabledState(disabled, isDisabled);
|
|
15
88
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
89
|
+
// Use the disabled state hook with enhanced API for automatic className merging
|
|
90
|
+
const { disabledProps, handlers } = useDisabledState<HTMLInputElement>(
|
|
91
|
+
isInputDisabled,
|
|
92
|
+
{
|
|
93
|
+
handlers: {
|
|
94
|
+
onChange,
|
|
95
|
+
onBlur,
|
|
96
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
97
|
+
// Handle Enter key press for accessibility
|
|
98
|
+
if (e.key === "Enter" && onEnter) {
|
|
99
|
+
onEnter(e);
|
|
100
|
+
}
|
|
101
|
+
// Always call consumer's onKeyDown if provided
|
|
102
|
+
if (onKeyDown) {
|
|
103
|
+
onKeyDown(e);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
// Note: onFocus is NOT wrapped to allow focus on disabled inputs
|
|
107
|
+
// This is handled automatically by useDisabledState
|
|
108
|
+
},
|
|
109
|
+
// Automatic className merging - hook combines disabled class with user classes
|
|
110
|
+
className: classes,
|
|
111
|
+
}
|
|
112
|
+
);
|
|
39
113
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onBlur?.(e);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
114
|
+
// Determine aria-invalid based on validation state
|
|
115
|
+
const isInvalid = validationState === "invalid";
|
|
45
116
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
117
|
+
// Generate describedby IDs for error and hint text
|
|
118
|
+
const describedByIds: string[] = [];
|
|
119
|
+
if (errorMessage && id) {
|
|
120
|
+
describedByIds.push(`${id}-error`);
|
|
121
|
+
}
|
|
122
|
+
if (hintText && id) {
|
|
123
|
+
describedByIds.push(`${id}-hint`);
|
|
50
124
|
}
|
|
51
|
-
|
|
125
|
+
const ariaDescribedBy =
|
|
126
|
+
describedByIds.length > 0 ? describedByIds.join(" ") : undefined;
|
|
127
|
+
|
|
128
|
+
// Generate accessible placeholder if not provided
|
|
129
|
+
const accessiblePlaceholder =
|
|
130
|
+
placeholder || (required ? `* ${type} input` : `${type} input`);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<FP
|
|
134
|
+
as="input"
|
|
135
|
+
ref={ref}
|
|
136
|
+
id={id}
|
|
137
|
+
type={type}
|
|
138
|
+
name={name}
|
|
139
|
+
value={value}
|
|
140
|
+
defaultValue={defaultValue}
|
|
141
|
+
placeholder={accessiblePlaceholder}
|
|
142
|
+
className={disabledProps.className}
|
|
143
|
+
styles={styles}
|
|
144
|
+
readOnly={readOnly}
|
|
145
|
+
required={required}
|
|
146
|
+
maxLength={maxLength}
|
|
147
|
+
minLength={minLength}
|
|
148
|
+
pattern={pattern}
|
|
149
|
+
autoComplete={autoComplete}
|
|
150
|
+
autoFocus={autoFocus}
|
|
151
|
+
inputMode={inputMode}
|
|
152
|
+
// Event handlers (wrapped by useDisabledState)
|
|
153
|
+
{...handlers}
|
|
154
|
+
onFocus={onFocus}
|
|
155
|
+
// ARIA attributes
|
|
156
|
+
aria-disabled={disabledProps["aria-disabled"]}
|
|
157
|
+
aria-readonly={readOnly}
|
|
158
|
+
aria-required={required}
|
|
159
|
+
aria-invalid={isInvalid}
|
|
160
|
+
aria-describedby={ariaDescribedBy}
|
|
161
|
+
// Data attributes for styling
|
|
162
|
+
data-validation={validationState}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
);
|
|
52
168
|
|
|
53
|
-
return (
|
|
54
|
-
<FP
|
|
55
|
-
as="input"
|
|
56
|
-
id={id}
|
|
57
|
-
type={type}
|
|
58
|
-
placeholder={placeholder || `${required ? "*" : ""} ${type} input `}
|
|
59
|
-
className={classes}
|
|
60
|
-
styles={styles}
|
|
61
|
-
onChange={handleChange}
|
|
62
|
-
onBlur={handleBlur}
|
|
63
|
-
onKeyDown={handleKeyDown}
|
|
64
|
-
value={value}
|
|
65
|
-
name={name}
|
|
66
|
-
ref={ref}
|
|
67
|
-
aria-disabled={isDisabled}
|
|
68
|
-
tabIndex={isDisabled ? -1 : undefined}
|
|
69
|
-
aria-readonly={readonly}
|
|
70
|
-
aria-required={required}
|
|
71
|
-
required={required}
|
|
72
|
-
readOnly={readonly}
|
|
73
|
-
{...props}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
169
|
Input.displayName = "Input";
|
|
78
170
|
export default Input;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import UI from '../ui'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import { useDisabledState } from '../../hooks/use-disabled-state'
|
|
3
4
|
|
|
4
|
-
export type SelectProps
|
|
5
|
+
export type { SelectProps } from './form.types'
|
|
6
|
+
import type { SelectProps } from './form.types'
|
|
5
7
|
|
|
6
8
|
export type SelectOptionsProps = {
|
|
7
9
|
/**
|
|
@@ -13,7 +15,7 @@ export type SelectOptionsProps = {
|
|
|
13
15
|
* Value for the select option. Can be a number or string.
|
|
14
16
|
*/
|
|
15
17
|
selectValue: string
|
|
16
|
-
}
|
|
18
|
+
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Option component for select.
|
|
@@ -23,80 +25,134 @@ export type SelectOptionsProps = {
|
|
|
23
25
|
*/
|
|
24
26
|
export const Option = ({ selectValue, selectLabel }: SelectOptionsProps) => {
|
|
25
27
|
return (
|
|
26
|
-
<option
|
|
28
|
+
<option value={selectValue}>
|
|
27
29
|
{selectLabel || selectValue}
|
|
28
30
|
</option>
|
|
29
31
|
)
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* Select component
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
35
|
+
* Select component - Accessible dropdown selection input with validation support
|
|
36
|
+
*
|
|
37
|
+
* A flexible select component that supports validation states, proper ARIA attributes
|
|
38
|
+
* for accessibility, and an onEnter handler for keyboard interactions. Enables keyboard-only
|
|
39
|
+
* users to trigger actions after making a selection.
|
|
40
|
+
*
|
|
41
|
+
* @component
|
|
42
|
+
* @example
|
|
43
|
+
* // Basic select
|
|
44
|
+
* <Select id="country" name="country" required>
|
|
45
|
+
* <option value="us">United States</option>
|
|
46
|
+
* <option value="uk">United Kingdom</option>
|
|
47
|
+
* </Select>
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Select with Enter key handler for quick submission
|
|
51
|
+
* <Select
|
|
52
|
+
* id="status"
|
|
53
|
+
* name="status"
|
|
54
|
+
* onEnter={(e) => handleSubmit()}
|
|
55
|
+
* onSelectionChange={(e) => setStatus(e.target.value)}
|
|
56
|
+
* >
|
|
57
|
+
* <option value="active">Active</option>
|
|
58
|
+
* <option value="inactive">Inactive</option>
|
|
59
|
+
* </Select>
|
|
60
|
+
*
|
|
61
|
+
* @param {SelectProps} props - Component props
|
|
62
|
+
* @returns {JSX.Element} Select element with proper accessibility attributes
|
|
63
|
+
*
|
|
64
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html|WCAG 2.1.1 Keyboard}
|
|
65
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html|WCAG 4.1.2 Name, Role, Value}
|
|
47
66
|
*/
|
|
48
|
-
export const Select = (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
68
|
+
(
|
|
69
|
+
{
|
|
70
|
+
id,
|
|
71
|
+
name,
|
|
72
|
+
styles,
|
|
73
|
+
classes,
|
|
74
|
+
disabled,
|
|
75
|
+
children,
|
|
76
|
+
required,
|
|
77
|
+
selected,
|
|
78
|
+
validationState = 'none',
|
|
79
|
+
errorMessage,
|
|
80
|
+
hintText,
|
|
81
|
+
onBlur,
|
|
82
|
+
onSelectionChange,
|
|
83
|
+
onPointerDown,
|
|
84
|
+
onKeyDown,
|
|
85
|
+
onEnter,
|
|
86
|
+
...props
|
|
87
|
+
},
|
|
88
|
+
ref
|
|
89
|
+
) => {
|
|
90
|
+
// Use the disabled state hook with enhanced API for automatic className merging
|
|
91
|
+
const { disabledProps, handlers } = useDisabledState<HTMLSelectElement>(
|
|
92
|
+
disabled,
|
|
93
|
+
{
|
|
94
|
+
handlers: {
|
|
95
|
+
onChange: onSelectionChange,
|
|
96
|
+
onPointerDown,
|
|
97
|
+
onBlur,
|
|
98
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLSelectElement>) => {
|
|
99
|
+
// Handle Enter key press for accessibility
|
|
100
|
+
// Enables keyboard-only users to trigger actions after selection
|
|
101
|
+
if (e.key === 'Enter' && onEnter) {
|
|
102
|
+
onEnter(e)
|
|
103
|
+
}
|
|
104
|
+
// Always call consumer's onKeyDown if provided
|
|
105
|
+
if (onKeyDown) {
|
|
106
|
+
onKeyDown(e)
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// Automatic className merging - hook combines disabled class with user classes
|
|
111
|
+
className: classes,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
66
114
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
115
|
+
// Determine aria-invalid based on validation state
|
|
116
|
+
const isInvalid = validationState === 'invalid'
|
|
70
117
|
|
|
71
|
-
|
|
72
|
-
|
|
118
|
+
// Generate describedby IDs for error and hint text
|
|
119
|
+
const describedByIds: string[] = []
|
|
120
|
+
if (errorMessage && id) {
|
|
121
|
+
describedByIds.push(`${id}-error`)
|
|
122
|
+
}
|
|
123
|
+
if (hintText && id) {
|
|
124
|
+
describedByIds.push(`${id}-hint`)
|
|
125
|
+
}
|
|
126
|
+
const ariaDescribedBy =
|
|
127
|
+
describedByIds.length > 0 ? describedByIds.join(' ') : undefined
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<UI
|
|
131
|
+
as="select"
|
|
132
|
+
id={id}
|
|
133
|
+
ref={ref}
|
|
134
|
+
name={name}
|
|
135
|
+
className={disabledProps.className}
|
|
136
|
+
defaultValue={selected}
|
|
137
|
+
{...handlers}
|
|
138
|
+
required={required}
|
|
139
|
+
aria-required={required}
|
|
140
|
+
aria-disabled={disabledProps['aria-disabled']}
|
|
141
|
+
aria-invalid={isInvalid}
|
|
142
|
+
aria-describedby={ariaDescribedBy}
|
|
143
|
+
style={styles}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
{children || <option value="" />}
|
|
147
|
+
</UI>
|
|
148
|
+
)
|
|
73
149
|
}
|
|
150
|
+
)
|
|
74
151
|
|
|
75
|
-
|
|
76
|
-
<UI
|
|
77
|
-
as="select"
|
|
78
|
-
id={id}
|
|
79
|
-
ref={ref}
|
|
80
|
-
name={name}
|
|
81
|
-
className={classes}
|
|
82
|
-
selected={selected}
|
|
83
|
-
onChange={handleOnChange}
|
|
84
|
-
onPointerDown={handlePointerDown}
|
|
85
|
-
onBlur={handleOnBlur}
|
|
86
|
-
required={required}
|
|
87
|
-
aria-required={required} // Accessibility
|
|
88
|
-
disabled={disabled}
|
|
89
|
-
aria-disabled={disabled ? true : false}
|
|
90
|
-
style={styles}
|
|
91
|
-
{...props} // Accessibility
|
|
92
|
-
>
|
|
93
|
-
<option value="" />
|
|
94
|
-
</UI>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
152
|
+
Select.displayName = 'Select'
|
|
97
153
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Select.Option = Option
|
|
154
|
+
// Type assertion to allow adding static property to ForwardRefExoticComponent
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
;(Select as any).Option = Option
|
|
101
157
|
|
|
102
|
-
|
|
158
|
+
export default Select
|