@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.
Files changed (280) hide show
  1. package/libs/{chunk-PQ2K3BM6.cjs → chunk-2NRIP6RB.cjs} +3 -3
  2. package/libs/chunk-33PNJ4LO.cjs +15 -0
  3. package/libs/chunk-33PNJ4LO.cjs.map +1 -0
  4. package/libs/chunk-4BZKFPEC.cjs +17 -0
  5. package/libs/chunk-4BZKFPEC.cjs.map +1 -0
  6. package/libs/{chunk-772NRB75.js → chunk-5QD3DWFI.js} +2 -2
  7. package/libs/chunk-6SAHIYCZ.js +7 -0
  8. package/libs/chunk-6SAHIYCZ.js.map +1 -0
  9. package/libs/{chunk-3MKLDCKQ.cjs → chunk-6WTC4JXH.cjs} +3 -3
  10. package/libs/chunk-75QHTLFO.js +7 -0
  11. package/libs/chunk-75QHTLFO.js.map +1 -0
  12. package/libs/{chunk-ZANSFMTD.js → chunk-7XPFW7CB.js} +3 -3
  13. package/libs/chunk-BFK62VX5.js +5 -0
  14. package/libs/chunk-BFK62VX5.js.map +1 -0
  15. package/libs/{chunk-ROZI23GS.cjs → chunk-DKTHCQ5P.cjs} +4 -4
  16. package/libs/chunk-E2AJURUW.cjs +13 -0
  17. package/libs/chunk-E2AJURUW.cjs.map +1 -0
  18. package/libs/{chunk-L75OQKEI.cjs → chunk-ENTCUJ3A.cjs} +3 -3
  19. package/libs/chunk-ENTCUJ3A.cjs.map +1 -0
  20. package/libs/chunk-F5EYMVQM.js +10 -0
  21. package/libs/chunk-F5EYMVQM.js.map +1 -0
  22. package/libs/chunk-FVROL3V5.js +9 -0
  23. package/libs/chunk-FVROL3V5.js.map +1 -0
  24. package/libs/chunk-GT77BX4L.cjs +17 -0
  25. package/libs/chunk-GT77BX4L.cjs.map +1 -0
  26. package/libs/chunk-GUJSMQ3V.cjs +16 -0
  27. package/libs/chunk-GUJSMQ3V.cjs.map +1 -0
  28. package/libs/chunk-HHLNOC5T.js +7 -0
  29. package/libs/chunk-HHLNOC5T.js.map +1 -0
  30. package/libs/chunk-HRRHPLER.js +8 -0
  31. package/libs/chunk-HRRHPLER.js.map +1 -0
  32. package/libs/chunk-IEB64SWY.js +8 -0
  33. package/libs/chunk-IEB64SWY.js.map +1 -0
  34. package/libs/{chunk-NGTJDDFO.js → chunk-IQ76HGVP.js} +2 -2
  35. package/libs/chunk-IRLFZ3OL.js +9 -0
  36. package/libs/chunk-IRLFZ3OL.js.map +1 -0
  37. package/libs/{chunk-JJ43O4Y5.js → chunk-KK47SYZI.js} +2 -2
  38. package/libs/chunk-O3JIHC5M.cjs +15 -0
  39. package/libs/chunk-O3JIHC5M.cjs.map +1 -0
  40. package/libs/chunk-O5XAJ7BY.cjs +18 -0
  41. package/libs/chunk-O5XAJ7BY.cjs.map +1 -0
  42. package/libs/chunk-OVWLQYMK.js +10 -0
  43. package/libs/chunk-OVWLQYMK.js.map +1 -0
  44. package/libs/chunk-PNWIRCG3.cjs +7 -0
  45. package/libs/chunk-PNWIRCG3.cjs.map +1 -0
  46. package/libs/{chunk-D4YLRWAO.cjs → chunk-QVW6W76L.cjs} +6 -6
  47. package/libs/chunk-T4T6GWYQ.cjs +17 -0
  48. package/libs/chunk-T4T6GWYQ.cjs.map +1 -0
  49. package/libs/chunk-TON2YGMD.cjs +9 -0
  50. package/libs/chunk-TON2YGMD.cjs.map +1 -0
  51. package/libs/chunk-UEPAWMDF.js +8 -0
  52. package/libs/chunk-UEPAWMDF.js.map +1 -0
  53. package/libs/{chunk-LT5KZ2QW.cjs → chunk-US2I5GI7.cjs} +3 -3
  54. package/libs/{chunk-B7F5FS6D.cjs → chunk-W2UIN7EV.cjs} +3 -3
  55. package/libs/{chunk-P2DC76ZZ.cjs → chunk-W5TKWBFC.cjs} +3 -3
  56. package/libs/chunk-WXBFBWYF.cjs +16 -0
  57. package/libs/chunk-WXBFBWYF.cjs.map +1 -0
  58. package/libs/{chunk-VUH3FXGJ.js → chunk-X3JCTEPD.js} +5 -5
  59. package/libs/chunk-X5LGFCWG.js +9 -0
  60. package/libs/chunk-X5LGFCWG.js.map +1 -0
  61. package/libs/{chunk-5M57K4SW.js → chunk-Y2PFDELK.js} +2 -2
  62. package/libs/{chunk-ETFLFC2S.js → chunk-ZFJ4U45S.js} +2 -2
  63. package/libs/{component-props-a8a2f97e.d.ts → component-props-67d978a2.d.ts} +4 -4
  64. package/libs/components/alert/alert.css +1 -1
  65. package/libs/components/alert/alert.css.map +1 -1
  66. package/libs/components/alert/alert.min.css +2 -2
  67. package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
  68. package/libs/components/breadcrumbs/breadcrumb.d.cts +11 -11
  69. package/libs/components/breadcrumbs/breadcrumb.d.ts +11 -11
  70. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  71. package/libs/components/button.cjs +6 -4
  72. package/libs/components/button.d.cts +97 -4
  73. package/libs/components/button.d.ts +97 -4
  74. package/libs/components/button.js +4 -2
  75. package/libs/components/card.cjs +7 -7
  76. package/libs/components/card.d.cts +14 -14
  77. package/libs/components/card.d.ts +14 -14
  78. package/libs/components/card.js +2 -2
  79. package/libs/components/dialog/dialog.cjs +9 -7
  80. package/libs/components/dialog/dialog.d.cts +3 -3
  81. package/libs/components/dialog/dialog.d.ts +3 -3
  82. package/libs/components/dialog/dialog.js +7 -5
  83. package/libs/components/form/fields.cjs +4 -4
  84. package/libs/components/form/fields.d.cts +16 -7
  85. package/libs/components/form/fields.d.ts +16 -7
  86. package/libs/components/form/fields.js +2 -2
  87. package/libs/components/form/inputs.cjs +6 -4
  88. package/libs/components/form/inputs.d.cts +50 -2
  89. package/libs/components/form/inputs.d.ts +50 -2
  90. package/libs/components/form/inputs.js +4 -2
  91. package/libs/components/form/textarea.cjs +5 -4
  92. package/libs/components/form/textarea.d.cts +32 -23
  93. package/libs/components/form/textarea.d.ts +32 -23
  94. package/libs/components/form/textarea.js +3 -2
  95. package/libs/components/heading/heading.cjs +3 -3
  96. package/libs/components/heading/heading.d.cts +2 -2
  97. package/libs/components/heading/heading.d.ts +2 -2
  98. package/libs/components/heading/heading.js +2 -2
  99. package/libs/components/icons/icon.cjs +4 -4
  100. package/libs/components/icons/icon.d.cts +38 -38
  101. package/libs/components/icons/icon.d.ts +38 -38
  102. package/libs/components/icons/icon.js +2 -2
  103. package/libs/components/link/link.cjs +4 -4
  104. package/libs/components/link/link.css +1 -1
  105. package/libs/components/link/link.css.map +1 -1
  106. package/libs/components/link/link.d.cts +3 -19
  107. package/libs/components/link/link.d.ts +3 -19
  108. package/libs/components/link/link.js +2 -2
  109. package/libs/components/link/link.min.css +2 -2
  110. package/libs/components/list/list.cjs +5 -5
  111. package/libs/components/list/list.css +1 -0
  112. package/libs/components/list/list.css.map +1 -0
  113. package/libs/components/list/list.d.cts +120 -33
  114. package/libs/components/list/list.d.ts +120 -33
  115. package/libs/components/list/list.js +2 -2
  116. package/libs/components/list/list.min.css +3 -0
  117. package/libs/components/modal.cjs +6 -4
  118. package/libs/components/modal.d.cts +8 -8
  119. package/libs/components/modal.d.ts +8 -8
  120. package/libs/components/modal.js +5 -3
  121. package/libs/components/nav/nav.cjs +7 -7
  122. package/libs/components/nav/nav.css +1 -1
  123. package/libs/components/nav/nav.css.map +1 -1
  124. package/libs/components/nav/nav.d.cts +550 -34
  125. package/libs/components/nav/nav.d.ts +550 -34
  126. package/libs/components/nav/nav.js +3 -3
  127. package/libs/components/nav/nav.min.css +2 -2
  128. package/libs/components/popover/popover.d.cts +5 -5
  129. package/libs/components/popover/popover.d.ts +5 -5
  130. package/libs/components/tables/table.cjs +5 -5
  131. package/libs/components/tables/table.d.cts +8 -8
  132. package/libs/components/tables/table.d.ts +8 -8
  133. package/libs/components/tables/table.js +2 -2
  134. package/libs/components/tag/tag.css +1 -1
  135. package/libs/components/tag/tag.css.map +1 -1
  136. package/libs/components/tag/tag.min.css +2 -2
  137. package/libs/components/text/text.cjs +5 -5
  138. package/libs/components/text/text.d.cts +5 -5
  139. package/libs/components/text/text.d.ts +5 -5
  140. package/libs/components/text/text.js +2 -2
  141. package/libs/form.types-d25ebfac.d.ts +233 -0
  142. package/libs/{heading-3648c538.d.ts → heading-7446cb46.d.ts} +8 -8
  143. package/libs/hooks.cjs +9 -4
  144. package/libs/hooks.d.cts +137 -3
  145. package/libs/hooks.d.ts +137 -3
  146. package/libs/hooks.js +4 -3
  147. package/libs/icons.cjs +3 -3
  148. package/libs/icons.d.cts +2 -2
  149. package/libs/icons.d.ts +2 -2
  150. package/libs/icons.js +2 -2
  151. package/libs/index.cjs +53 -51
  152. package/libs/index.cjs.map +1 -1
  153. package/libs/index.css +1 -1
  154. package/libs/index.css.map +1 -1
  155. package/libs/index.d.cts +338 -49
  156. package/libs/index.d.ts +338 -49
  157. package/libs/index.js +24 -22
  158. package/libs/index.js.map +1 -1
  159. package/libs/link-5192f411.d.ts +323 -0
  160. package/libs/list.types-d26de310.d.ts +245 -0
  161. package/libs/{ui-645f95b5.d.ts → ui-d01b50d4.d.ts} +16 -12
  162. package/package.json +4 -6
  163. package/src/components/alert/alert.scss +1 -4
  164. package/src/components/breadcrumbs/breadcrumb.tsx +4 -1
  165. package/src/components/buttons/README.mdx +102 -1
  166. package/src/components/buttons/button.stories.tsx +106 -0
  167. package/src/components/buttons/button.tsx +82 -52
  168. package/src/components/dialog/dialog-a11y-review.md +653 -0
  169. package/src/components/form/README.mdx +725 -43
  170. package/src/components/form/WCAG-REVIEW.md +654 -0
  171. package/src/components/form/fields.tsx +10 -1
  172. package/src/components/form/form.stories.tsx +604 -23
  173. package/src/components/form/form.tsx +204 -63
  174. package/src/components/form/form.types.ts +378 -0
  175. package/src/components/form/input.stories.tsx +71 -3
  176. package/src/components/form/inputs.tsx +159 -67
  177. package/src/components/form/select.tsx +122 -66
  178. package/src/components/form/textarea.tsx +120 -73
  179. package/src/components/fp.tsx +86 -11
  180. package/src/components/link/README.mdx +923 -0
  181. package/src/components/link/link.scss +79 -26
  182. package/src/components/link/link.stories.tsx +383 -30
  183. package/src/components/link/link.test.tsx +677 -0
  184. package/src/components/link/link.tsx +163 -57
  185. package/src/components/link/link.types.ts +261 -0
  186. package/src/components/list/README.mdx +764 -0
  187. package/src/components/list/list.scss +285 -0
  188. package/src/components/list/list.stories.tsx +514 -27
  189. package/src/components/list/list.test.tsx +554 -0
  190. package/src/components/list/list.tsx +153 -51
  191. package/src/components/list/list.types.ts +255 -0
  192. package/src/components/nav/ACCESSIBILITY.md +649 -0
  193. package/src/components/nav/README.mdx +782 -0
  194. package/src/components/nav/nav.scss +32 -1
  195. package/src/components/nav/nav.stories.tsx +44 -6
  196. package/src/components/nav/nav.tsx +302 -51
  197. package/src/components/nav/nav.types.ts +308 -0
  198. package/src/components/tag/README.mdx +426 -0
  199. package/src/components/tag/tag.scss +101 -27
  200. package/src/components/tag/tag.stories.tsx +384 -10
  201. package/src/components/tag/tag.test.tsx +210 -0
  202. package/src/components/tag/tag.tsx +106 -9
  203. package/src/components/tag/tag.types.ts +107 -0
  204. package/src/components/ui.tsx +8 -3
  205. package/src/hooks/use-disabled-state.test.tsx +536 -0
  206. package/src/hooks/use-disabled-state.ts +246 -0
  207. package/src/hooks/useDisabledState.md +393 -0
  208. package/src/hooks.ts +6 -0
  209. package/src/index.scss +2 -0
  210. package/src/index.ts +2 -1
  211. package/src/sass/_globals.scss +2 -7
  212. package/src/styles/alert/alert.css +1 -3
  213. package/src/styles/alert/alert.css.map +1 -1
  214. package/src/styles/index.css +450 -76
  215. package/src/styles/index.css.map +1 -1
  216. package/src/styles/link/link.css +45 -28
  217. package/src/styles/link/link.css.map +1 -1
  218. package/src/styles/list/list.css +214 -0
  219. package/src/styles/list/list.css.map +1 -0
  220. package/src/styles/nav/nav.css +21 -1
  221. package/src/styles/nav/nav.css.map +1 -1
  222. package/src/styles/tag/tag.css +113 -35
  223. package/src/styles/tag/tag.css.map +1 -1
  224. package/src/styles/utilities/_disabled.scss +58 -0
  225. package/src/types/shared.ts +43 -6
  226. package/src/utils/accessibility.ts +109 -0
  227. package/libs/chunk-2LTJ7HHX.cjs +0 -18
  228. package/libs/chunk-2LTJ7HHX.cjs.map +0 -1
  229. package/libs/chunk-2Y7W75TT.js +0 -9
  230. package/libs/chunk-2Y7W75TT.js.map +0 -1
  231. package/libs/chunk-5S4ORA4C.cjs +0 -15
  232. package/libs/chunk-5S4ORA4C.cjs.map +0 -1
  233. package/libs/chunk-AHDJGCG5.cjs +0 -15
  234. package/libs/chunk-AHDJGCG5.cjs.map +0 -1
  235. package/libs/chunk-BHRQBJRY.js +0 -8
  236. package/libs/chunk-BHRQBJRY.js.map +0 -1
  237. package/libs/chunk-GZ4QFPRY.js +0 -9
  238. package/libs/chunk-GZ4QFPRY.js.map +0 -1
  239. package/libs/chunk-IYUN2EW3.cjs +0 -15
  240. package/libs/chunk-IYUN2EW3.cjs.map +0 -1
  241. package/libs/chunk-J32EZPYD.cjs +0 -15
  242. package/libs/chunk-J32EZPYD.cjs.map +0 -1
  243. package/libs/chunk-KUKIVRC2.js +0 -7
  244. package/libs/chunk-KUKIVRC2.js.map +0 -1
  245. package/libs/chunk-L75OQKEI.cjs.map +0 -1
  246. package/libs/chunk-M5RRNTVX.cjs +0 -15
  247. package/libs/chunk-M5RRNTVX.cjs.map +0 -1
  248. package/libs/chunk-OK5QEIMD.cjs +0 -17
  249. package/libs/chunk-OK5QEIMD.cjs.map +0 -1
  250. package/libs/chunk-P7TTEYCD.js +0 -7
  251. package/libs/chunk-P7TTEYCD.js.map +0 -1
  252. package/libs/chunk-QLZWHAMK.js +0 -8
  253. package/libs/chunk-QLZWHAMK.js.map +0 -1
  254. package/libs/chunk-RIVUMPOG.js +0 -8
  255. package/libs/chunk-RIVUMPOG.js.map +0 -1
  256. package/libs/chunk-S7BABR7Z.cjs +0 -13
  257. package/libs/chunk-S7BABR7Z.cjs.map +0 -1
  258. package/libs/chunk-SMYRLO3E.js +0 -8
  259. package/libs/chunk-SMYRLO3E.js.map +0 -1
  260. package/libs/chunk-TYRCEX2L.js +0 -8
  261. package/libs/chunk-TYRCEX2L.js.map +0 -1
  262. package/libs/chunk-XBA562WW.js +0 -8
  263. package/libs/chunk-XBA562WW.js.map +0 -1
  264. package/libs/chunk-XTQKWY7W.cjs +0 -32
  265. package/libs/chunk-XTQKWY7W.cjs.map +0 -1
  266. package/libs/inputs-f3a216db.d.ts +0 -45
  267. /package/libs/{chunk-PQ2K3BM6.cjs.map → chunk-2NRIP6RB.cjs.map} +0 -0
  268. /package/libs/{chunk-772NRB75.js.map → chunk-5QD3DWFI.js.map} +0 -0
  269. /package/libs/{chunk-3MKLDCKQ.cjs.map → chunk-6WTC4JXH.cjs.map} +0 -0
  270. /package/libs/{chunk-ZANSFMTD.js.map → chunk-7XPFW7CB.js.map} +0 -0
  271. /package/libs/{chunk-ROZI23GS.cjs.map → chunk-DKTHCQ5P.cjs.map} +0 -0
  272. /package/libs/{chunk-NGTJDDFO.js.map → chunk-IQ76HGVP.js.map} +0 -0
  273. /package/libs/{chunk-JJ43O4Y5.js.map → chunk-KK47SYZI.js.map} +0 -0
  274. /package/libs/{chunk-D4YLRWAO.cjs.map → chunk-QVW6W76L.cjs.map} +0 -0
  275. /package/libs/{chunk-LT5KZ2QW.cjs.map → chunk-US2I5GI7.cjs.map} +0 -0
  276. /package/libs/{chunk-B7F5FS6D.cjs.map → chunk-W2UIN7EV.cjs.map} +0 -0
  277. /package/libs/{chunk-P2DC76ZZ.cjs.map → chunk-W5TKWBFC.cjs.map} +0 -0
  278. /package/libs/{chunk-VUH3FXGJ.js.map → chunk-X3JCTEPD.js.map} +0 -0
  279. /package/libs/{chunk-5M57K4SW.js.map → chunk-Y2PFDELK.js.map} +0 -0
  280. /package/libs/{chunk-ETFLFC2S.js.map → chunk-ZFJ4U45S.js.map} +0 -0
@@ -1,70 +1,752 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="FP.REACT Forms/Form/Readme" />
4
+
1
5
  # Form Components
2
6
 
3
- This directory contains a set of React components for building forms in web applications. The components are designed to be reusable, accessible, and customizable.
7
+ A comprehensive set of accessible React form components built with TypeScript, designed for building robust, WCAG 2.1 AA compliant forms with proper validation, error handling, and status management.
8
+
9
+ > 💡 **Interactive Examples**: See the [Form stories](./?path=/docs/fp-react-forms-form--docs) for live, interactive examples with automated accessibility testing.
10
+
11
+ ## Features
12
+
13
+ - ✅ **WCAG 2.1 AA Compliant** - Full accessibility support with proper ARIA attributes
14
+ - ✅ **Compound Component Pattern** - Intuitive API with Form.Field, Form.Input, etc.
15
+ - ✅ **TypeScript First** - Full type safety with comprehensive interfaces
16
+ - ✅ **Status Management** - Built-in loading states and submission tracking
17
+ - ✅ **Validation Support** - Client-side and server-side validation patterns
18
+ - ✅ **Flexible** - Supports both controlled and uncontrolled form patterns
19
+ - ✅ **Keyboard Navigation** - Full keyboard accessibility including Enter key handlers
20
+
21
+ ---
22
+
23
+ ## Components Overview
24
+
25
+ ### Core Components
26
+
27
+ | Component | Purpose | Key Props |
28
+ |-----------|---------|-----------|
29
+ | **Form** | Form wrapper with submission handling | `onSubmit`, `status`, `noValidate` |
30
+ | **Form.Field** | Label + input wrapper for accessibility | `label`, `labelFor`, `required`, `optional` |
31
+ | **Form.Input** | Text input with validation | `type`, `validationState`, `onEnter` |
32
+ | **Form.Textarea** | Multi-line text input | `rows`, `cols`, `onEnter` |
33
+ | **Form.Select** | Dropdown select with options | `onSelectionChange`, `onEnter` |
34
+
35
+ ---
36
+
37
+ ## Installation & Import
38
+
39
+ ```tsx
40
+ import Form from '@fpkit/acss';
41
+ // Or import specific components
42
+ import { Form, Input, Field } from '@fpkit/acss';
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Basic Usage
48
+
49
+ ### Simple Contact Form
50
+
51
+ A basic form with required fields, proper label associations, and submission handling.
52
+
53
+ ```tsx
54
+ import Form from '@fpkit/acss';
55
+
56
+ function ContactForm() {
57
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
58
+ e.preventDefault();
59
+ // Handle form submission
60
+ };
61
+
62
+ return (
63
+ <Form onSubmit={handleSubmit} aria-label="Contact form">
64
+ <Form.Field label="Name" labelFor="name" required>
65
+ <Form.Input id="name" name="name" required />
66
+ </Form.Field>
67
+
68
+ <Form.Field label="Email" labelFor="email" required>
69
+ <Form.Input id="email" name="email" type="email" required />
70
+ </Form.Field>
71
+
72
+ <Form.Field label="Message" labelFor="message">
73
+ <Form.Textarea id="message" name="message" rows={4} />
74
+ </Form.Field>
75
+
76
+ <button type="submit">Submit</button>
77
+ </Form>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Advanced Patterns
85
+
86
+ ### Form with Status Management
87
+
88
+ Use the `status` prop to manage form submission states. This example shows how the form automatically sets `aria-busy` and disables fields during submission.
89
+
90
+ ```tsx
91
+ import { useState } from 'react';
92
+ import Form, { FormStatus } from '@fpkit/acss';
4
93
 
5
- ## Overview
94
+ function RegistrationForm() {
95
+ const [status, setStatus] = useState<FormStatus>('idle');
6
96
 
7
- The main components in this directory are:
97
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
98
+ e.preventDefault();
8
99
 
9
- - `Form`: A wrapper component for creating HTML forms with various input fields.
10
- - `Input`: A component for rendering input fields of different types (text, email, password, etc.).
11
- - `Textarea`: A component for rendering a textarea input field.
12
- - `Select`: A component for rendering a dropdown select input field with options.
13
- - `Field`: A component for rendering a label and an input field together.
100
+ setStatus('submitting');
14
101
 
15
- These components are built with accessibility and customization in mind, providing props for setting labels, placeholders, validation, and styling.
102
+ try {
103
+ const formData = new FormData(e.currentTarget);
104
+ await fetch('/api/register', {
105
+ method: 'POST',
106
+ body: formData,
107
+ });
108
+ setStatus('success');
109
+ } catch (error) {
110
+ setStatus('error');
111
+ }
112
+ };
16
113
 
17
- ## Usage
114
+ const isSubmitting = status === 'submitting';
18
115
 
19
- To use these components in your React application, import them from the respective files:
116
+ return (
117
+ <Form
118
+ status={status}
119
+ onSubmit={handleSubmit}
120
+ aria-label="Registration form"
121
+ >
122
+ <Form.Field label="Username" labelFor="username" required>
123
+ <Form.Input
124
+ id="username"
125
+ name="username"
126
+ disabled={isSubmitting}
127
+ required
128
+ />
129
+ </Form.Field>
20
130
 
21
- ```jsx
22
- import { Form, Input, Textarea, Select, Field } from './path/to/form/components';
131
+ <Form.Field label="Password" labelFor="password" required>
132
+ <Form.Input
133
+ id="password"
134
+ name="password"
135
+ type="password"
136
+ minLength={8}
137
+ disabled={isSubmitting}
138
+ required
139
+ />
140
+ </Form.Field>
141
+
142
+ <button type="submit" disabled={isSubmitting}>
143
+ {isSubmitting ? 'Submitting...' : 'Create Account'}
144
+ </button>
145
+
146
+ {status === 'success' && <p>Account created successfully!</p>}
147
+ {status === 'error' && <p>Error creating account. Please try again.</p>}
148
+ </Form>
149
+ );
150
+ }
23
151
  ```
24
152
 
25
- Then, you can use them in your JSX like any other React component:
153
+ ### Form with Validation
154
+
155
+ Implement client-side validation with error messages. Notice how the input uses `validationState` to show visual feedback and `aria-invalid` for screen readers.
156
+
157
+ ```tsx
158
+ import { useState } from 'react';
159
+ import Form from '@fpkit/acss';
160
+
161
+ function ValidatedForm() {
162
+ const [email, setEmail] = useState('');
163
+ const [emailError, setEmailError] = useState('');
164
+
165
+ const validateEmail = (value: string) => {
166
+ if (!value) {
167
+ setEmailError('Email is required');
168
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
169
+ setEmailError('Please enter a valid email address');
170
+ } else {
171
+ setEmailError('');
172
+ }
173
+ };
26
174
 
27
- ```jsx
175
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
176
+ e.preventDefault();
177
+ if (!emailError) {
178
+ // Submit form
179
+ }
180
+ };
28
181
 
29
- <Form onSubmit={handleSubmit}>
30
- <Field label="Name" labelFor="name">
31
- <Input id="name" name="name" required />
32
- </Field>
33
- <Field label="Email" labelFor="email">
34
- <Input id="email" name="email" type="email" required />
35
- </Field>
36
- <Field label="Message" labelFor="message">
37
- <Textarea id="message" name="message" required />
38
- </Field>
39
- <Select id="option" name="option" required>
40
- <Select.Option value="option1">Option 1</Select.Option>
41
- <Select.Option value="option2">Option 2</Select.Option>
42
- </Select>
43
- <button type="submit">Submit</button>
44
- </Form>
182
+ return (
183
+ <Form onSubmit={handleSubmit}>
184
+ <Form.Field
185
+ label="Email"
186
+ labelFor="email"
187
+ required
188
+ errorMessage={emailError}
189
+ >
190
+ <Form.Input
191
+ id="email"
192
+ name="email"
193
+ type="email"
194
+ value={email}
195
+ onChange={(e) => setEmail(e.target.value)}
196
+ onBlur={(e) => validateEmail(e.target.value)}
197
+ validationState={emailError ? 'invalid' : email ? 'valid' : 'none'}
198
+ required
199
+ />
200
+ </Form.Field>
201
+
202
+ <button type="submit">Submit</button>
203
+ </Form>
204
+ );
205
+ }
45
206
  ```
46
207
 
47
- ## Key Features
208
+ ### Keyboard-Driven Workflows
209
+
210
+ Use the `onEnter` prop for keyboard-friendly interactions. This example demonstrates `onEnter` on inputs, textareas, and selects.
211
+
212
+ ```tsx
213
+ import { useState } from 'react';
214
+ import Form from '@fpkit/acss';
215
+
216
+ function SearchForm() {
217
+ const [query, setQuery] = useState('');
218
+
219
+ const handleSearch = () => {
220
+ console.log('Searching for:', query);
221
+ // Perform search
222
+ };
223
+
224
+ return (
225
+ <Form aria-label="Search form">
226
+ <Form.Field
227
+ label="Search"
228
+ labelFor="search"
229
+ hintText="Press Enter to search"
230
+ >
231
+ <Form.Input
232
+ id="search"
233
+ name="search"
234
+ type="search"
235
+ value={query}
236
+ onChange={(e) => setQuery(e.target.value)}
237
+ onEnter={handleSearch}
238
+ />
239
+ </Form.Field>
240
+ </Form>
241
+ );
242
+ }
243
+ ```
244
+
245
+ ### Uncontrolled Form with Native Submission
246
+
247
+ For server-side form handling:
248
+
249
+ ```tsx
250
+ function ServerSideForm() {
251
+ return (
252
+ <Form action="/api/contact" formMethod="post">
253
+ <Form.Field label="Name" labelFor="name" required>
254
+ <Form.Input id="name" name="name" required />
255
+ </Form.Field>
256
+
257
+ <Form.Field label="Email" labelFor="email" required>
258
+ <Form.Input id="email" name="email" type="email" required />
259
+ </Form.Field>
260
+
261
+ <button type="submit">Send</button>
262
+ </Form>
263
+ );
264
+ }
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Disabled State
270
+
271
+ Form components support an accessible disabled state using the `aria-disabled` pattern, which provides better accessibility than the native HTML `disabled` attribute.
272
+
273
+ ### Why aria-disabled?
274
+
275
+ The `aria-disabled` pattern offers key advantages:
276
+
277
+ - **Keyboard Accessibility**: Disabled elements remain in the tab order, allowing keyboard users to discover them
278
+ - **Screen Reader Discovery**: Screen readers can announce the disabled state and read associated tooltips or help text
279
+ - **WCAG Compliance**: Meets WCAG 2.1.1 (Keyboard) and 4.1.2 (Name, Role, Value) requirements
280
+ - **Interactive Help**: Enables tooltips or contextual help on disabled elements to explain why they're disabled
281
+
282
+ ### Basic Usage
283
+
284
+ Use the `disabled` prop to disable form inputs, textareas, and selects:
285
+
286
+ ```tsx
287
+ import Form from '@fpkit/acss';
288
+
289
+ function DisabledInputExample() {
290
+ return (
291
+ <Form>
292
+ <Form.Field label="Email" labelFor="email">
293
+ <Form.Input
294
+ id="email"
295
+ name="email"
296
+ type="email"
297
+ disabled={true}
298
+ value="locked@example.com"
299
+ />
300
+ </Form.Field>
301
+
302
+ <Form.Field label="Comments" labelFor="comments">
303
+ <Form.Textarea
304
+ id="comments"
305
+ name="comments"
306
+ disabled={true}
307
+ />
308
+ </Form.Field>
309
+
310
+ <Form.Field label="Country" labelFor="country">
311
+ <Form.Select id="country" name="country" disabled={true}>
312
+ <option value="us">United States</option>
313
+ <option value="ca">Canada</option>
314
+ </Form.Select>
315
+ </Form.Field>
316
+ </Form>
317
+ );
318
+ }
319
+ ```
320
+
321
+ ### Migration from isDisabled
322
+
323
+ For backward compatibility, the deprecated `isDisabled` prop is still supported but will be removed in a future version.
324
+
325
+ **Before (deprecated):**
326
+ ```tsx
327
+ <Form.Input isDisabled={true} />
328
+ ```
329
+
330
+ **After (recommended):**
331
+ ```tsx
332
+ <Form.Input disabled={true} />
333
+ ```
334
+
335
+ Both props work identically, but `disabled` follows standard HTML conventions and should be used for all new code.
336
+
337
+ ### Disabled State During Form Submission
338
+
339
+ A common pattern is disabling all form fields while a form is submitting:
340
+
341
+ ```tsx
342
+ import { useState } from 'react';
343
+ import Form, { FormStatus } from '@fpkit/acss';
344
+
345
+ function SubmitDisabledForm() {
346
+ const [status, setStatus] = useState<FormStatus>('idle');
347
+
348
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
349
+ e.preventDefault();
350
+ setStatus('submitting');
351
+
352
+ try {
353
+ const formData = new FormData(e.currentTarget);
354
+ await fetch('/api/save', {
355
+ method: 'POST',
356
+ body: formData,
357
+ });
358
+ setStatus('success');
359
+ } catch (error) {
360
+ setStatus('error');
361
+ }
362
+ };
363
+
364
+ const isSubmitting = status === 'submitting';
365
+
366
+ return (
367
+ <Form status={status} onSubmit={handleSubmit}>
368
+ <Form.Field label="Username" labelFor="username" required>
369
+ <Form.Input
370
+ id="username"
371
+ name="username"
372
+ disabled={isSubmitting}
373
+ required
374
+ />
375
+ </Form.Field>
376
+
377
+ <Form.Field label="Bio" labelFor="bio">
378
+ <Form.Textarea
379
+ id="bio"
380
+ name="bio"
381
+ disabled={isSubmitting}
382
+ />
383
+ </Form.Field>
384
+
385
+ <button type="submit" disabled={isSubmitting}>
386
+ {isSubmitting ? 'Saving...' : 'Save Profile'}
387
+ </button>
388
+ </Form>
389
+ );
390
+ }
391
+ ```
392
+
393
+ ### Behavior
394
+
395
+ When a form control is disabled:
396
+
397
+ - **Keyboard Navigation**: Element remains focusable via Tab key (maintains tab order)
398
+ - **Interaction Prevention**: All interaction events are prevented (click, change, keydown, etc.)
399
+ - **Focus Events**: Focus events still work, allowing screen readers to discover and announce the element
400
+ - **Visual Styling**: `.is-disabled` class and `aria-disabled="true"` attribute are applied
401
+ - **Screen Readers**: Announce "disabled" state when focused
402
+
403
+ ### Styling
404
+
405
+ Disabled elements can be styled using CSS custom properties:
406
+
407
+ ```css
408
+ :root {
409
+ --disabled-opacity: 0.6; /* Visual opacity for disabled state */
410
+ --disabled-cursor: not-allowed; /* Cursor style */
411
+ --disabled-color: hsl(0 0% 40%); /* Text color (3:1 contrast minimum) */
412
+ }
413
+ ```
414
+
415
+ Override these properties for custom styling:
416
+
417
+ ```tsx
418
+ <Form.Input
419
+ disabled={true}
420
+ styles={{
421
+ '--disabled-opacity': '0.5',
422
+ '--disabled-color': '#666666',
423
+ }}
424
+ />
425
+ ```
426
+
427
+ **Selectors Available:**
428
+ - `.is-disabled` - Class added to disabled elements
429
+ - `[aria-disabled="true"]` - Attribute selector
430
+
431
+ ### WCAG Compliance
48
432
 
49
- - **Accessibility**: The components are built with accessibility in mind, providing props for setting labels, placeholders, and ARIA attributes.
50
- - **Validation**: The `Input`, `Textarea`, and `Select` components support validation through the `required` prop.
51
- - **Styling**: The components can be styled using CSS classes or inline styles passed through props.
52
- - **Customization**: The components are designed to be flexible and customizable, allowing you to pass additional props or render custom content within them.
433
+ The `aria-disabled` pattern ensures compliance with:
434
+
435
+ - **WCAG 2.1.1 (Keyboard)**: Elements remain in keyboard tab order for discovery
436
+ - **WCAG 4.1.2 (Name, Role, Value)**: `aria-disabled` announces state to screen readers
437
+ - **WCAG 1.4.3 (Contrast Minimum)**: Disabled text maintains 3:1 contrast ratio
438
+ - **WCAG 2.4.7 (Focus Visible)**: Focus indicators preserved on disabled elements
439
+
440
+ ---
441
+
442
+ ## Component API
443
+
444
+ ### Form
445
+
446
+ The main form wrapper component.
447
+
448
+ #### Props
449
+
450
+ | Prop | Type | Default | Description |
451
+ |------|------|---------|-------------|
452
+ | `onSubmit` | `(event: FormEvent) => void` | - | Form submission handler (prevents default) |
453
+ | `status` | `'idle' \| 'submitting' \| 'success' \| 'error'` | `'idle'` | Current form status |
454
+ | `action` | `string` | - | Form submission URL |
455
+ | `formMethod` | `'get' \| 'post'` | `'post'` | HTTP method |
456
+ | `noValidate` | `boolean` | `false` | Disable HTML5 validation |
457
+ | `target` | `string` | - | Form submission target |
458
+ | `id` | `string` | - | Unique form identifier |
459
+ | `name` | `string` | - | Form name attribute |
460
+ | `classes` | `string` | - | CSS class names |
461
+ | `styles` | `CSSProperties` | - | Inline styles |
462
+
463
+ #### Accessibility
464
+
465
+ - **role**: Automatically set to `"form"`
466
+ - **aria-busy**: Set to `true` when `status="submitting"`
467
+ - **data-status**: Reflects current status for CSS styling
468
+
469
+ ### Form.Field
470
+
471
+ Wrapper component that associates labels with inputs for accessibility.
472
+
473
+ #### Props
474
+
475
+ | Prop | Type | Default | Description |
476
+ |------|------|---------|-------------|
477
+ | `label` | `ReactNode` | - | **Required.** Label text or element |
478
+ | `labelFor` | `string` | - | ID of associated input (for `htmlFor`) |
479
+ | `required` | `boolean` | `false` | Show required indicator (*) |
480
+ | `optional` | `boolean` | `false` | Show optional indicator |
481
+ | `errorMessage` | `string` | - | Error message to display |
482
+ | `hintText` | `string` | - | Helper text below input |
483
+
484
+ ### Form.Input
485
+
486
+ Text input component with validation support.
487
+
488
+ #### Props
489
+
490
+ | Prop | Type | Default | Description |
491
+ |------|------|---------|-------------|
492
+ | `type` | `string` | `'text'` | Input type (text, email, password, etc.) |
493
+ | `validationState` | `'none' \| 'valid' \| 'invalid'` | `'none'` | Validation state |
494
+ | `errorMessage` | `string` | - | Error message for `aria-describedby` |
495
+ | `hintText` | `string` | - | Hint text for `aria-describedby` |
496
+ | `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter key press |
497
+ | `disabled` | `boolean` | `false` | Disable input using `aria-disabled` pattern (remains focusable) |
498
+ | `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
499
+ | `readOnly` | `boolean` | `false` | Make input read-only |
500
+ | `required` | `boolean` | `false` | Mark input as required |
501
+
502
+ ### Form.Textarea
503
+
504
+ Multi-line text input component.
505
+
506
+ #### Props
507
+
508
+ | Prop | Type | Default | Description |
509
+ |------|------|---------|-------------|
510
+ | `rows` | `number` | `5` | Number of visible rows |
511
+ | `cols` | `number` | `25` | Number of visible columns |
512
+ | `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter (without Shift) |
513
+ | `disabled` | `boolean` | `false` | Disable textarea using `aria-disabled` pattern (remains focusable) |
514
+ | `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
515
+
516
+ **Note**: Shift+Enter adds a new line without triggering `onEnter`.
517
+
518
+ ### Form.Select
519
+
520
+ Dropdown select component with keyboard support.
521
+
522
+ #### Props
523
+
524
+ | Prop | Type | Default | Description |
525
+ |------|------|---------|-------------|
526
+ | `onSelectionChange` | `(event: ChangeEvent) => void` | - | Selection change handler |
527
+ | `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter key press |
528
+ | `required` | `boolean` | `false` | Mark select as required |
529
+ | `disabled` | `boolean` | `false` | Disable select using `aria-disabled` pattern (remains focusable) |
530
+ | `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
531
+
532
+ ---
533
+
534
+ ## Accessibility Features
535
+
536
+ ### WCAG 2.1 AA Compliance
537
+
538
+ All form components meet WCAG 2.1 Level AA standards:
539
+
540
+ 1. **WCAG 3.3.1 Error Identification** ✅
541
+ - Error messages clearly associated with inputs via `aria-describedby`
542
+ - Validation states communicated via `aria-invalid`
543
+
544
+ 2. **WCAG 3.3.2 Labels or Instructions** ✅
545
+ - All inputs have associated labels via `<label htmlFor>`
546
+ - Required fields marked with visual and programmatic indicators
547
+
548
+ 3. **WCAG 4.1.2 Name, Role, Value** ✅
549
+ - All inputs have proper `role`, `name`, and `aria-` attributes
550
+ - Form status communicated via `aria-busy`
551
+
552
+ 4. **WCAG 2.4.7 Focus Visible** ✅
553
+ - Focus indicators styled via `:focus-visible` in SCSS
554
+
555
+ 5. **WCAG 2.1.1 Keyboard** ✅
556
+ - All interactive elements keyboard accessible
557
+ - `onEnter` prop for custom keyboard workflows
558
+
559
+ 6. **Accessible Disabled State** ✅
560
+ - Uses `aria-disabled` pattern instead of native `disabled` attribute
561
+ - Elements remain keyboard focusable (stay in tab order)
562
+ - Screen readers announce disabled state while allowing discovery
563
+ - See [Disabled State](#disabled-state) section for details
564
+
565
+ ### Screen Reader Support
566
+
567
+ - Form status changes announced via `aria-busy`
568
+ - Error messages linked to inputs via `aria-describedby`
569
+ - Required fields announced via `aria-required`
570
+ - Validation states communicated via `aria-invalid`
571
+
572
+ ### Keyboard Navigation
573
+
574
+ - **Tab**: Navigate between form fields
575
+ - **Shift+Tab**: Navigate backwards
576
+ - **Enter**:
577
+ - In Input: Trigger `onEnter` handler
578
+ - In Textarea: Trigger `onEnter` (or new line with Shift)
579
+ - In Select: Trigger `onEnter` after selection
580
+ - In Form: Submit form (default button behavior)
581
+ - **Escape**: Clear focus (native browser behavior)
582
+
583
+ ---
53
584
 
54
585
  ## Styling
55
586
 
56
- The components in this directory are styled using a combination of CSS modules and inline styles. The styles can be overridden or extended by importing the corresponding CSS files or by passing custom styles through the `styles` prop.
587
+ Form components use CSS custom properties for theming:
588
+
589
+ ```css
590
+ :root {
591
+ --input-border-color: gray;
592
+ --input-bg: inherit;
593
+ --input-outline: thin solid var(--input-border-color);
594
+ --input-px: 0.6rem;
595
+ --input-py: 0.4rem;
596
+ --input-fs: var(--fs);
597
+ --input-w: clamp(200px, 100%, 500px);
598
+ --placeholder-color: gray;
599
+ --form-direction: column;
600
+ }
601
+ ```
602
+
603
+ ### Status-Based Styling
604
+
605
+ Use the `data-status` attribute for CSS styling:
57
606
 
58
- For example, to override the styles for the `Input` component, you can import the CSS file and define your custom styles:
607
+ ```css
608
+ form[data-status="submitting"] {
609
+ opacity: 0.6;
610
+ pointer-events: none;
611
+ }
59
612
 
60
- ```jsx
61
- import './input.css';
613
+ form[data-status="error"] {
614
+ border-left: 0.25rem solid var(--color-error);
615
+ }
62
616
 
63
- // ...
617
+ form[data-status="success"] {
618
+ border-left: 0.25rem solid var(--color-success);
619
+ }
620
+ ```
621
+
622
+ ### Validation Styling
623
+
624
+ Use the `data-validation` attribute on inputs:
625
+
626
+ ```css
627
+ input[data-validation="invalid"] {
628
+ outline-color: var(--color-error);
629
+ }
630
+
631
+ input[data-validation="valid"] {
632
+ outline-color: var(--color-success);
633
+ }
634
+ ```
635
+
636
+ ---
637
+
638
+ ## TypeScript Support
639
+
640
+ All components are fully typed with comprehensive interfaces:
641
+
642
+ ```tsx
643
+ import Form, {
644
+ FormProps,
645
+ FormStatus,
646
+ InputProps
647
+ } from '@fpkit/acss';
648
+
649
+ const MyForm: React.FC = () => {
650
+ const [status, setStatus] = useState<FormStatus>('idle');
651
+
652
+ const handleSubmit: FormProps['onSubmit'] = (e) => {
653
+ // Fully typed event
654
+ };
64
655
 
65
- <Input id="name" name="name" required styles={{ color: 'red' }} />
656
+ return <Form status={status} onSubmit={handleSubmit}>...</Form>;
657
+ };
66
658
  ```
67
659
 
660
+ ---
661
+
662
+ ## Testing
663
+
664
+ Form components are fully tested with Vitest and React Testing Library:
665
+
666
+ ```tsx
667
+ import { render, screen, userEvent } from '@testing-library/react';
668
+ import Form from '@fpkit/acss';
669
+
670
+ test('submits form data', async () => {
671
+ const handleSubmit = vi.fn();
672
+ const user = userEvent.setup();
673
+
674
+ render(
675
+ <Form onSubmit={handleSubmit}>
676
+ <Form.Input name="email" />
677
+ <button type="submit">Submit</button>
678
+ </Form>
679
+ );
680
+
681
+ await user.type(screen.getByRole('textbox'), 'test@example.com');
682
+ await user.click(screen.getByRole('button'));
683
+
684
+ expect(handleSubmit).toHaveBeenCalled();
685
+ });
686
+ ```
687
+
688
+ ---
689
+
690
+ ## Best Practices
691
+
692
+ ### ✅ DO
693
+
694
+ - Use `Form.Field` to wrap inputs for proper label association
695
+ - Provide `aria-label` or `aria-labelledby` for forms
696
+ - Use the `status` prop to manage loading states
697
+ - Disable form fields when `status="submitting"`
698
+ - Validate on `onBlur` for better UX
699
+ - Use `required` attribute for HTML5 validation
700
+ - Provide clear error messages
701
+
702
+ ### ❌ DON'T
703
+
704
+ - Don't forget to associate labels with inputs
705
+ - Don't submit forms without validation
706
+ - Don't disable submit buttons without explaining why
707
+ - Don't use placeholders as labels
708
+ - Don't forget to handle keyboard events
709
+
710
+ ---
711
+
712
+ ## More Examples
713
+
714
+ See the [Form Interactive Guide](./?path=/docs/fp-react-forms-form--docs) for complete examples including:
715
+
716
+ ### Form with Hint Text
717
+
718
+ Guide users with helpful hint text below fields, properly associated using `aria-describedby`.
719
+
720
+ ### Form with Select Dropdown
721
+
722
+ Combine different form control types for rich data collection.
723
+
724
+ ### Form with Optional Fields
725
+
726
+ Clearly distinguish between required and optional fields for better UX.
727
+
728
+ ### Complete Registration Form
729
+
730
+ A comprehensive example combining all features: validation, hints, different field types, and proper accessibility.
731
+
732
+ ---
733
+
734
+ ## Browser Support
735
+
736
+ - ✅ Chrome/Edge 90+
737
+ - ✅ Firefox 88+
738
+ - ✅ Safari 14+
739
+ - ✅ Mobile Safari (iOS 14+)
740
+ - ✅ Chrome Android
741
+
742
+ ---
743
+
68
744
  ## Contributing
69
745
 
70
- If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on the project's repository.
746
+ Found an issue or have a suggestion? Please [open an issue](https://github.com/your-repo/issues) or submit a pull request.
747
+
748
+ ---
749
+
750
+ ## License
751
+
752
+ MIT License - see LICENSE file for details