@fpkit/acss 0.5.13 → 0.6.1

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 +37 -4
  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 +461 -81
  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 +32 -6
  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,71 +1,212 @@
1
- import UI from '#components/fp'
2
- import React from 'react'
1
+ import UI from "#components/ui";
2
+ import React from "react";
3
3
 
4
- import Input from './inputs'
5
- import Field from './fields'
6
- import Select from './select'
7
- import Textarea from './textarea'
4
+ import Input from "./inputs";
5
+ import Field from "./fields";
6
+ import Select from "./select";
7
+ import Textarea from "./textarea";
8
8
 
9
- export type FormProps = Partial<React.ComponentProps<typeof UI>> &
10
- React.ComponentProps<'form'>
9
+ /**
10
+ * Form submission status type
11
+ */
12
+ export type FormStatus = "idle" | "submitting" | "success" | "error";
13
+
14
+ /**
15
+ * Form component props interface
16
+ * @interface FormProps
17
+ * @extends {React.ComponentProps<'form'>}
18
+ */
19
+ export interface FormProps
20
+ extends Omit<React.ComponentProps<"form">, "className"> {
21
+ /**
22
+ * Unique identifier for the form
23
+ */
24
+ id?: string;
25
+ /**
26
+ * Name attribute for the form
27
+ */
28
+ name?: string;
29
+ /**
30
+ * Inline CSS styles object
31
+ */
32
+ styles?: React.CSSProperties;
33
+ /**
34
+ * CSS class names (preferred over 'className' for consistency with fpkit components)
35
+ */
36
+ classes?: string;
37
+ /**
38
+ * Form submission action URL
39
+ */
40
+ action?: string;
41
+ /**
42
+ * HTTP method for form submission
43
+ * @default 'post'
44
+ */
45
+ formMethod?: "get" | "post";
46
+ /**
47
+ * Form submission handler - prevents default browser submission
48
+ * @param {React.FormEvent<HTMLFormElement>} event - Form submit event
49
+ */
50
+ onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
51
+ /**
52
+ * Target for form submission (_blank, _self, _parent, _top)
53
+ */
54
+ target?: string;
55
+ /**
56
+ * Disable HTML5 validation
57
+ * @default false
58
+ */
59
+ noValidate?: boolean;
60
+ /**
61
+ * Current form status for accessibility and styling
62
+ * @default 'idle'
63
+ */
64
+ status?: FormStatus;
65
+ /**
66
+ * Accessible name for the form
67
+ * RECOMMENDED when multiple forms exist on the same page to help screen reader users distinguish between them.
68
+ * Use descriptive labels like "Contact form", "Login form", "Search form"
69
+ * @example "Contact form"
70
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/headings-and-labels.html|WCAG 2.4.6 Headings and Labels}
71
+ */
72
+ "aria-label"?: string;
73
+ /**
74
+ * ID of element that labels the form
75
+ * Alternative to aria-label. Use when a visible heading already labels the form.
76
+ * @example "contact-form-title"
77
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/headings-and-labels.html|WCAG 2.4.6 Headings and Labels}
78
+ */
79
+ "aria-labelledby"?: string;
80
+ /**
81
+ * Child elements (form fields, buttons, etc.)
82
+ */
83
+ children: React.ReactNode;
84
+ }
11
85
 
12
86
  /**
13
- * Form component
14
- * @param {Object} props - Form component props
15
- * @param {string} [id] - Unique identifier for form
16
- * @param {string} [name] - Name for form
17
- * @param {Object} [styles] - Inline styles
18
- * @param {string} [classes] - Additional classes
19
- * @param {ReactNode} children - Child elements
20
- * @param {string} [action] - Form action URL
21
- * @param {('get'|'post')} [formMethod='post'] - Form method
22
- * @param {Function} [onSubmit] - Submit callback
23
- * @param {string} [target] - Form submit target
24
- * @param {boolean} [noValidate=false] - Disable validation
25
- * @param {Object} ...props - Additional props
87
+ * Form component - Accessible HTML form wrapper with validation support
88
+ *
89
+ * A flexible form component that provides proper ARIA attributes, form submission
90
+ * handling, and validation state management. Supports both controlled and uncontrolled
91
+ * form patterns, with status management for loading states and error handling.
92
+ *
93
+ * @component
94
+ * @example
95
+ * // Basic form with validation
96
+ * <Form onSubmit={handleSubmit} aria-label="Contact form">
97
+ * <Form.Field label="Name" labelFor="name" required>
98
+ * <Form.Input id="name" name="name" required />
99
+ * </Form.Field>
100
+ * <button type="submit">Submit</button>
101
+ * </Form>
102
+ *
103
+ * @example
104
+ * // Form with loading state
105
+ * <Form
106
+ * status={isSubmitting ? 'submitting' : 'idle'}
107
+ * onSubmit={handleSubmit}
108
+ * >
109
+ * <Form.Field label="Email" labelFor="email">
110
+ * <Form.Input id="email" type="email" disabled={isSubmitting} />
111
+ * </Form.Field>
112
+ * <button type="submit" disabled={isSubmitting}>
113
+ * {isSubmitting ? 'Submitting...' : 'Submit'}
114
+ * </button>
115
+ * </Form>
116
+ *
117
+ * @example
118
+ * // Uncontrolled form with native submission
119
+ * <Form action="/api/contact" formMethod="post">
120
+ * <Form.Field label="Message" labelFor="message">
121
+ * <Form.Textarea id="message" name="message" required />
122
+ * </Form.Field>
123
+ * <button type="submit">Send</button>
124
+ * </Form>
125
+ *
126
+ * @param {FormProps} props - Component props
127
+ * @returns {JSX.Element} Form element with proper accessibility attributes
128
+ *
129
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html|WCAG 4.1.2 Name, Role, Value}
130
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/error-identification.html|WCAG 3.3.1 Error Identification}
26
131
  */
27
- export const Form = ({
28
- id,
29
- name,
30
- styles,
31
- classes,
32
- children,
33
- action,
34
- formMethod,
35
- onSubmit,
36
- target,
37
- noValidate,
38
- ...props
39
- }: FormProps) => {
40
- const onSubmitCallback = (e: React.FormEvent<HTMLFormElement>) => {
41
- if (onSubmit) {
42
- e.preventDefault()
43
- onSubmit?.(e)
44
- }
132
+ export const Form = React.forwardRef<HTMLFormElement, FormProps>(
133
+ (
134
+ {
135
+ id,
136
+ name,
137
+ styles,
138
+ classes,
139
+ children,
140
+ action,
141
+ formMethod = "post",
142
+ onSubmit,
143
+ target,
144
+ noValidate = false,
145
+ status = "idle",
146
+ ...props
147
+ },
148
+ ref: React.ForwardedRef<HTMLFormElement>
149
+ ) => {
150
+ /**
151
+ * Form submission handler
152
+ * Prevents default browser submission only when onSubmit handler is provided
153
+ * @param {React.FormEvent<HTMLFormElement>} event - Form submit event
154
+ */
155
+ const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
156
+ if (onSubmit) {
157
+ event.preventDefault();
158
+ onSubmit(event);
159
+ }
160
+ // If no onSubmit handler, allow native form submission
161
+ };
162
+
163
+ // Determine if form is busy (submitting)
164
+ const isBusy = status === "submitting";
165
+
166
+ return (
167
+ <UI
168
+ as="form"
169
+ // @ts-expect-error - ForwardedRef includes legacy string refs in its type union,
170
+ // but modern React (18+) and our polymorphic types correctly exclude them.
171
+ // String refs are deprecated and will not be passed at runtime.
172
+ ref={ref}
173
+ id={id}
174
+ name={name}
175
+ className={classes}
176
+ styles={styles}
177
+ action={action}
178
+ method={formMethod}
179
+ onSubmit={handleSubmit}
180
+ target={target}
181
+ noValidate={noValidate}
182
+ // Accessibility attributes
183
+ aria-busy={isBusy}
184
+ // Data attribute for CSS styling hooks
185
+ data-status={status}
186
+ {...props}
187
+ >
188
+ {children}
189
+ </UI>
190
+ );
45
191
  }
192
+ );
46
193
 
47
- return (
48
- <UI
49
- as="form"
50
- id={id}
51
- name={name}
52
- className={classes}
53
- styles={styles}
54
- action={action}
55
- novalidate={noValidate}
56
- method={formMethod}
57
- onSubmit={onSubmitCallback}
58
- target={target}
59
- {...props}
60
- >
61
- {children}
62
- </UI>
63
- )
64
- }
194
+ // Compound component type with sub-components
195
+ type FormComponent = typeof Form & {
196
+ Field: typeof Field;
197
+ Input: typeof Input;
198
+ Select: typeof Select;
199
+ Textarea: typeof Textarea;
200
+ };
201
+
202
+ // Display name for React DevTools
203
+ Form.displayName = "Form";
204
+
205
+ // Compound component pattern - attach sub-components with proper typing
206
+ const FormWithSubComponents = Form as FormComponent;
207
+ FormWithSubComponents.Field = Field;
208
+ FormWithSubComponents.Input = Input;
209
+ FormWithSubComponents.Select = Select;
210
+ FormWithSubComponents.Textarea = Textarea;
65
211
 
66
- export default Form
67
- Form.displayName = 'Form'
68
- Form.Field = Field
69
- Form.Input = Input
70
- Form.Select = Select
71
- Form.Textarea = Textarea
212
+ export default FormWithSubComponents;
@@ -0,0 +1,378 @@
1
+ import React from 'react'
2
+
3
+ /**
4
+ * Validation state for form inputs
5
+ */
6
+ export type ValidationState = 'valid' | 'invalid' | 'none'
7
+
8
+ /**
9
+ * Input component props interface
10
+ * Extends native HTML input props with custom validation and accessibility features
11
+ *
12
+ * @interface InputProps
13
+ */
14
+ export interface InputProps extends Omit<React.ComponentPropsWithoutRef<'input'>, 'className'> {
15
+ /**
16
+ * Input type attribute
17
+ * @default 'text'
18
+ */
19
+ type?: React.HTMLInputTypeAttribute
20
+
21
+ /**
22
+ * Unique identifier for the input element
23
+ */
24
+ id?: string
25
+
26
+ /**
27
+ * Name attribute for form submission
28
+ */
29
+ name?: string
30
+
31
+ /**
32
+ * Controlled value
33
+ */
34
+ value?: string | number | readonly string[]
35
+
36
+ /**
37
+ * Uncontrolled default value
38
+ */
39
+ defaultValue?: string | number | readonly string[]
40
+
41
+ /**
42
+ * Placeholder text
43
+ */
44
+ placeholder?: string
45
+
46
+ /**
47
+ * CSS class names (preferred over 'className' for consistency with fpkit components)
48
+ */
49
+ classes?: string
50
+
51
+ /**
52
+ * Inline CSS styles object
53
+ */
54
+ styles?: React.CSSProperties
55
+
56
+ /**
57
+ * Disabled state (standard prop)
58
+ */
59
+ disabled?: boolean
60
+
61
+ /**
62
+ * Disabled state (legacy support)
63
+ * @deprecated Use `disabled` instead
64
+ */
65
+ isDisabled?: boolean
66
+
67
+ /**
68
+ * Read-only state
69
+ */
70
+ readOnly?: boolean
71
+
72
+ /**
73
+ * Required field indicator
74
+ * @default false
75
+ */
76
+ required?: boolean
77
+
78
+ /**
79
+ * Current validation state for styling and accessibility
80
+ * @default 'none'
81
+ */
82
+ validationState?: ValidationState
83
+
84
+ /**
85
+ * Error message to display and link via aria-describedby
86
+ */
87
+ errorMessage?: string
88
+
89
+ /**
90
+ * Hint text to display and link via aria-describedby
91
+ */
92
+ hintText?: string
93
+
94
+ /**
95
+ * Maximum character length
96
+ */
97
+ maxLength?: number
98
+
99
+ /**
100
+ * Minimum character length
101
+ */
102
+ minLength?: number
103
+
104
+ /**
105
+ * Validation pattern regex
106
+ */
107
+ pattern?: string
108
+
109
+ /**
110
+ * Autocomplete attribute for browser autofill
111
+ */
112
+ autoComplete?: string
113
+
114
+ /**
115
+ * Auto-focus on mount
116
+ * @default false
117
+ */
118
+ autoFocus?: boolean
119
+
120
+ /**
121
+ * Input mode for virtual keyboards
122
+ */
123
+ inputMode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'
124
+
125
+ /**
126
+ * Change event handler
127
+ */
128
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
129
+
130
+ /**
131
+ * Blur event handler
132
+ */
133
+ onBlur?: React.FocusEventHandler<HTMLInputElement>
134
+
135
+ /**
136
+ * Focus event handler
137
+ */
138
+ onFocus?: React.FocusEventHandler<HTMLInputElement>
139
+
140
+ /**
141
+ * Key down event handler
142
+ */
143
+ onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
144
+
145
+ /**
146
+ * Accessibility handler for Enter key press
147
+ * Enables keyboard-only users to trigger actions without requiring mouse interaction
148
+ * @param {React.KeyboardEvent<HTMLInputElement>} event - Keyboard event
149
+ *
150
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html|WCAG 2.1.1 Keyboard}
151
+ */
152
+ onEnter?: (event: React.KeyboardEvent<HTMLInputElement>) => void
153
+ }
154
+
155
+ /**
156
+ * Textarea component props interface
157
+ * Extends native HTML textarea props with custom validation and accessibility features
158
+ *
159
+ * @interface TextareaProps
160
+ */
161
+ export interface TextareaProps extends Omit<React.ComponentPropsWithoutRef<'textarea'>, 'className'> {
162
+ /**
163
+ * Unique identifier for the textarea element
164
+ */
165
+ id?: string
166
+
167
+ /**
168
+ * Name attribute for form submission
169
+ */
170
+ name?: string
171
+
172
+ /**
173
+ * Controlled value
174
+ */
175
+ value?: string | number | readonly string[]
176
+
177
+ /**
178
+ * Uncontrolled default value
179
+ */
180
+ defaultValue?: string | number | readonly string[]
181
+
182
+ /**
183
+ * Placeholder text
184
+ */
185
+ placeholder?: string
186
+
187
+ /**
188
+ * CSS class names (preferred over 'className' for consistency with fpkit components)
189
+ */
190
+ classes?: string
191
+
192
+ /**
193
+ * Inline CSS styles object
194
+ */
195
+ styles?: React.CSSProperties
196
+
197
+ /**
198
+ * Number of visible text rows
199
+ * @default 5
200
+ */
201
+ rows?: number
202
+
203
+ /**
204
+ * Number of visible text columns
205
+ * @default 25
206
+ */
207
+ cols?: number
208
+
209
+ /**
210
+ * Disabled state
211
+ */
212
+ disabled?: boolean
213
+
214
+ /**
215
+ * Read-only state
216
+ */
217
+ readOnly?: boolean
218
+
219
+ /**
220
+ * Required field indicator
221
+ * @default false
222
+ */
223
+ required?: boolean
224
+
225
+ /**
226
+ * Current validation state for styling and accessibility
227
+ * @default 'none'
228
+ */
229
+ validationState?: ValidationState
230
+
231
+ /**
232
+ * Error message to display and link via aria-describedby
233
+ */
234
+ errorMessage?: string
235
+
236
+ /**
237
+ * Hint text to display and link via aria-describedby
238
+ */
239
+ hintText?: string
240
+
241
+ /**
242
+ * Change event handler
243
+ */
244
+ onChange?: React.ChangeEventHandler<HTMLTextAreaElement>
245
+
246
+ /**
247
+ * Blur event handler
248
+ */
249
+ onBlur?: React.FocusEventHandler<HTMLTextAreaElement>
250
+
251
+ /**
252
+ * Focus event handler
253
+ */
254
+ onFocus?: React.FocusEventHandler<HTMLTextAreaElement>
255
+
256
+ /**
257
+ * Pointer down event handler
258
+ */
259
+ onPointerDown?: React.PointerEventHandler<HTMLTextAreaElement>
260
+
261
+ /**
262
+ * Key down event handler
263
+ */
264
+ onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>
265
+
266
+ /**
267
+ * Accessibility handler for Enter key press (without Shift modifier)
268
+ * Enables keyboard-only users to submit forms with Enter, while Shift+Enter creates new lines
269
+ * @param {React.KeyboardEvent<HTMLTextAreaElement>} event - Keyboard event
270
+ *
271
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html|WCAG 2.1.1 Keyboard}
272
+ */
273
+ onEnter?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void
274
+
275
+ /**
276
+ * Ref for the textarea element
277
+ */
278
+ ref?: React.Ref<HTMLTextAreaElement>
279
+ }
280
+
281
+ /**
282
+ * Select component props interface
283
+ * Extends native HTML select props with custom validation and accessibility features
284
+ *
285
+ * @interface SelectProps
286
+ */
287
+ export interface SelectProps extends Omit<React.ComponentPropsWithoutRef<'select'>, 'className'> {
288
+ /**
289
+ * Unique identifier for the select element
290
+ */
291
+ id?: string
292
+
293
+ /**
294
+ * Name attribute for form submission
295
+ */
296
+ name?: string
297
+
298
+ /**
299
+ * CSS class names (preferred over 'className' for consistency with fpkit components)
300
+ */
301
+ classes?: string
302
+
303
+ /**
304
+ * Inline CSS styles object
305
+ */
306
+ styles?: React.CSSProperties
307
+
308
+ /**
309
+ * Disabled state
310
+ */
311
+ disabled?: boolean
312
+
313
+ /**
314
+ * Required field indicator
315
+ * @default false
316
+ */
317
+ required?: boolean
318
+
319
+ /**
320
+ * Selected option value(s)
321
+ */
322
+ selected?: string | number | string[]
323
+
324
+ /**
325
+ * Current validation state for styling and accessibility
326
+ * @default 'none'
327
+ */
328
+ validationState?: ValidationState
329
+
330
+ /**
331
+ * Error message to display and link via aria-describedby
332
+ */
333
+ errorMessage?: string
334
+
335
+ /**
336
+ * Hint text to display and link via aria-describedby
337
+ */
338
+ hintText?: string
339
+
340
+ /**
341
+ * Blur event handler
342
+ */
343
+ onBlur?: React.FocusEventHandler<HTMLSelectElement>
344
+
345
+ /**
346
+ * Selection change handler (alternative to onChange)
347
+ */
348
+ onSelectionChange?: React.ChangeEventHandler<HTMLSelectElement>
349
+
350
+ /**
351
+ * Pointer down event handler
352
+ */
353
+ onPointerDown?: React.PointerEventHandler<HTMLSelectElement>
354
+
355
+ /**
356
+ * Key down event handler
357
+ */
358
+ onKeyDown?: React.KeyboardEventHandler<HTMLSelectElement>
359
+
360
+ /**
361
+ * Accessibility handler for Enter key press
362
+ * Enables keyboard-only users to trigger actions after selection
363
+ * @param {React.KeyboardEvent<HTMLSelectElement>} event - Keyboard event
364
+ *
365
+ * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html|WCAG 2.1.1 Keyboard}
366
+ */
367
+ onEnter?: (event: React.KeyboardEvent<HTMLSelectElement>) => void
368
+
369
+ /**
370
+ * Ref for the select element
371
+ */
372
+ ref?: React.Ref<HTMLSelectElement>
373
+
374
+ /**
375
+ * Child option elements
376
+ */
377
+ children?: React.ReactNode
378
+ }