@fpkit/acss 0.5.12 → 0.5.13

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 (264) hide show
  1. package/README.md +89 -0
  2. package/libs/{chunk-DV56L5YX.cjs → chunk-2LTJ7HHX.cjs} +4 -4
  3. package/libs/{chunk-EQ67LF46.js → chunk-2Y7W75TT.js} +3 -3
  4. package/libs/{chunk-KKLTUJFB.cjs → chunk-3MKLDCKQ.cjs} +5 -5
  5. package/libs/chunk-3MKLDCKQ.cjs.map +1 -0
  6. package/libs/{chunk-X3EVB7VS.cjs → chunk-5S4ORA4C.cjs} +3 -3
  7. package/libs/{chunk-O6QZBB6G.js → chunk-772NRB75.js} +5 -5
  8. package/libs/chunk-772NRB75.js.map +1 -0
  9. package/libs/{chunk-6BVXFW7U.cjs → chunk-AHDJGCG5.cjs} +3 -3
  10. package/libs/{chunk-E3XP6BEX.cjs → chunk-B7F5FS6D.cjs} +3 -3
  11. package/libs/chunk-D4YLRWAO.cjs +18 -0
  12. package/libs/chunk-D4YLRWAO.cjs.map +1 -0
  13. package/libs/chunk-ETFLFC2S.js +10 -0
  14. package/libs/chunk-ETFLFC2S.js.map +1 -0
  15. package/libs/chunk-GZ4QFPRY.js +9 -0
  16. package/libs/chunk-GZ4QFPRY.js.map +1 -0
  17. package/libs/{chunk-LHVJKDMA.cjs → chunk-J32EZPYD.cjs} +3 -3
  18. package/libs/chunk-JJ43O4Y5.js +8 -0
  19. package/libs/chunk-JJ43O4Y5.js.map +1 -0
  20. package/libs/chunk-KUKIVRC2.js +7 -0
  21. package/libs/chunk-KUKIVRC2.js.map +1 -0
  22. package/libs/chunk-L75OQKEI.cjs +13 -0
  23. package/libs/chunk-L75OQKEI.cjs.map +1 -0
  24. package/libs/{chunk-LL7HTLMS.cjs → chunk-M5RRNTVX.cjs} +3 -3
  25. package/libs/{chunk-LIQJ7ZZR.js → chunk-NGTJDDFO.js} +2 -2
  26. package/libs/chunk-OK5QEIMD.cjs +17 -0
  27. package/libs/chunk-OK5QEIMD.cjs.map +1 -0
  28. package/libs/chunk-P2DC76ZZ.cjs +18 -0
  29. package/libs/chunk-P2DC76ZZ.cjs.map +1 -0
  30. package/libs/chunk-PQ2K3BM6.cjs +17 -0
  31. package/libs/chunk-PQ2K3BM6.cjs.map +1 -0
  32. package/libs/{chunk-QCMV4VQZ.js → chunk-QLZWHAMK.js} +2 -2
  33. package/libs/{chunk-BIP2NY53.js → chunk-RIVUMPOG.js} +2 -2
  34. package/libs/{chunk-ICCKQ2GC.cjs → chunk-ROZI23GS.cjs} +4 -4
  35. package/libs/{chunk-NHYXGV3L.js → chunk-SMYRLO3E.js} +2 -2
  36. package/libs/{chunk-5ZM4XL44.js → chunk-TYRCEX2L.js} +2 -2
  37. package/libs/chunk-VUH3FXGJ.js +11 -0
  38. package/libs/chunk-VUH3FXGJ.js.map +1 -0
  39. package/libs/{chunk-PPOOBUOS.js → chunk-XBA562WW.js} +2 -2
  40. package/libs/{chunk-QVV34QEH.cjs → chunk-XTQKWY7W.cjs} +3 -3
  41. package/libs/{chunk-YWOYVRFT.js → chunk-ZANSFMTD.js} +3 -3
  42. package/libs/components/alert/alert.css +1 -1
  43. package/libs/components/alert/alert.css.map +1 -1
  44. package/libs/components/alert/alert.min.css +2 -2
  45. package/libs/components/badge/badge.css +1 -1
  46. package/libs/components/badge/badge.css.map +1 -1
  47. package/libs/components/badge/badge.min.css +2 -2
  48. package/libs/components/breadcrumbs/breadcrumb.cjs +9 -5
  49. package/libs/components/breadcrumbs/breadcrumb.d.cts +271 -32
  50. package/libs/components/breadcrumbs/breadcrumb.d.ts +271 -32
  51. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  52. package/libs/components/button.cjs +4 -4
  53. package/libs/components/button.d.cts +2 -2
  54. package/libs/components/button.d.ts +2 -2
  55. package/libs/components/button.js +2 -2
  56. package/libs/components/buttons/button.css +1 -1
  57. package/libs/components/buttons/button.css.map +1 -1
  58. package/libs/components/buttons/button.min.css +2 -2
  59. package/libs/components/card.cjs +7 -7
  60. package/libs/components/card.d.cts +277 -33
  61. package/libs/components/card.d.ts +277 -33
  62. package/libs/components/card.js +2 -2
  63. package/libs/components/cards/card.css +1 -1
  64. package/libs/components/cards/card.css.map +1 -1
  65. package/libs/components/cards/card.min.css +2 -2
  66. package/libs/components/details/details.css +1 -1
  67. package/libs/components/details/details.css.map +1 -1
  68. package/libs/components/details/details.min.css +2 -2
  69. package/libs/components/dialog/dialog.cjs +7 -7
  70. package/libs/components/dialog/dialog.css +1 -1
  71. package/libs/components/dialog/dialog.css.map +1 -1
  72. package/libs/components/dialog/dialog.d.cts +88 -34
  73. package/libs/components/dialog/dialog.d.ts +88 -34
  74. package/libs/components/dialog/dialog.js +5 -5
  75. package/libs/components/dialog/dialog.min.css +2 -2
  76. package/libs/components/form/fields.cjs +4 -4
  77. package/libs/components/form/fields.d.cts +2 -2
  78. package/libs/components/form/fields.d.ts +2 -2
  79. package/libs/components/form/fields.js +2 -2
  80. package/libs/components/form/textarea.cjs +4 -4
  81. package/libs/components/form/textarea.d.cts +2 -2
  82. package/libs/components/form/textarea.d.ts +2 -2
  83. package/libs/components/form/textarea.js +2 -2
  84. package/libs/components/heading/heading.cjs +3 -3
  85. package/libs/components/heading/heading.d.cts +3 -14
  86. package/libs/components/heading/heading.d.ts +3 -14
  87. package/libs/components/heading/heading.js +2 -2
  88. package/libs/components/icons/icon.cjs +4 -4
  89. package/libs/components/icons/icon.d.cts +148 -4
  90. package/libs/components/icons/icon.d.ts +148 -4
  91. package/libs/components/icons/icon.js +2 -2
  92. package/libs/components/images/img.css +1 -1
  93. package/libs/components/images/img.css.map +1 -1
  94. package/libs/components/images/img.min.css +2 -2
  95. package/libs/components/link/link.cjs +4 -4
  96. package/libs/components/link/link.d.cts +2 -2
  97. package/libs/components/link/link.d.ts +2 -2
  98. package/libs/components/link/link.js +2 -2
  99. package/libs/components/list/list.cjs +5 -5
  100. package/libs/components/list/list.d.cts +3 -3
  101. package/libs/components/list/list.d.ts +3 -3
  102. package/libs/components/list/list.js +2 -2
  103. package/libs/components/modal.cjs +4 -4
  104. package/libs/components/modal.js +3 -3
  105. package/libs/components/nav/nav.cjs +7 -7
  106. package/libs/components/nav/nav.d.cts +2 -2
  107. package/libs/components/nav/nav.d.ts +2 -2
  108. package/libs/components/nav/nav.js +3 -3
  109. package/libs/components/text/text.cjs +5 -5
  110. package/libs/components/text/text.d.cts +2 -2
  111. package/libs/components/text/text.d.ts +2 -2
  112. package/libs/components/text/text.js +2 -2
  113. package/libs/heading-3648c538.d.ts +250 -0
  114. package/libs/hooks.cjs +7 -0
  115. package/libs/hooks.d.cts +5 -0
  116. package/libs/hooks.d.ts +5 -0
  117. package/libs/hooks.js +3 -0
  118. package/libs/icons.cjs +3 -3
  119. package/libs/icons.d.cts +1 -1
  120. package/libs/icons.d.ts +1 -1
  121. package/libs/icons.js +2 -2
  122. package/libs/index.cjs +112 -91
  123. package/libs/index.cjs.map +1 -1
  124. package/libs/index.css +1 -1
  125. package/libs/index.css.map +1 -1
  126. package/libs/index.d.cts +515 -31
  127. package/libs/index.d.ts +515 -31
  128. package/libs/index.js +31 -19
  129. package/libs/index.js.map +1 -1
  130. package/libs/ui-645f95b5.d.ts +285 -0
  131. package/package.json +2 -83
  132. package/src/components/README-UI.mdx +416 -0
  133. package/src/components/alert/ACCESSIBILITY.md +319 -0
  134. package/src/components/alert/README.mdx +475 -19
  135. package/src/components/alert/alert.scss +113 -6
  136. package/src/components/alert/alert.stories.tsx +372 -0
  137. package/src/components/alert/alert.test.tsx +762 -0
  138. package/src/components/alert/alert.tsx +331 -66
  139. package/src/components/alert/views/alert-actions.tsx +13 -0
  140. package/src/components/alert/views/alert-content.tsx +17 -0
  141. package/src/components/alert/views/alert-icon.tsx +53 -0
  142. package/src/components/alert/views/alert-screen-reader-text.tsx +30 -0
  143. package/src/components/alert/views/alert-title.tsx +23 -0
  144. package/src/components/alert/views/alert-view.tsx +158 -0
  145. package/src/components/alert/views/index.ts +12 -0
  146. package/src/components/badge/badge.mdx +186 -49
  147. package/src/components/badge/badge.scss +20 -2
  148. package/src/components/badge/badge.stories.tsx +160 -14
  149. package/src/components/badge/badge.test.tsx +179 -0
  150. package/src/components/badge/badge.tsx +97 -4
  151. package/src/components/breadcrumbs/README.mdx +364 -45
  152. package/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +152 -0
  153. package/src/components/breadcrumbs/breadcrumb.stories.tsx +7 -3
  154. package/src/components/breadcrumbs/breadcrumb.test.tsx +490 -0
  155. package/src/components/breadcrumbs/breadcrumb.tsx +427 -170
  156. package/src/components/buttons/button.scss +34 -31
  157. package/src/components/buttons/button.stories.tsx +35 -0
  158. package/src/components/cards/README.mdx +657 -0
  159. package/src/components/cards/card.scss +22 -0
  160. package/src/components/cards/card.stories.tsx +167 -5
  161. package/src/components/cards/card.test.tsx +360 -20
  162. package/src/components/cards/card.tsx +200 -79
  163. package/src/components/cards/card.types.ts +135 -0
  164. package/src/components/cards/card.utils.ts +79 -0
  165. package/src/components/details/ACCESSIBILITY-REVIEW-LIVE.md +1050 -0
  166. package/src/components/details/ACCESSIBILITY-REVIEW.md +502 -0
  167. package/src/components/details/README.mdx +437 -69
  168. package/src/components/details/details.scss +16 -7
  169. package/src/components/details/details.test.tsx +385 -0
  170. package/src/components/details/details.tsx +101 -69
  171. package/src/components/details/details.types.ts +76 -0
  172. package/src/components/dialog/README.mdx +513 -110
  173. package/src/components/dialog/dialog-modal.tsx +79 -56
  174. package/src/components/dialog/dialog.scss +53 -3
  175. package/src/components/dialog/dialog.stories.tsx +10 -7
  176. package/src/components/dialog/dialog.test.tsx +450 -0
  177. package/src/components/dialog/dialog.tsx +69 -59
  178. package/src/components/dialog/dialog.types.ts +133 -0
  179. package/src/components/dialog/views/dialog-footer.tsx +54 -11
  180. package/src/components/dialog/views/dialog-header.tsx +20 -15
  181. package/src/components/heading/heading.stories.tsx +44 -4
  182. package/src/components/heading/heading.tsx +89 -23
  183. package/src/components/icons/README.mdx +332 -0
  184. package/src/components/icons/icon.stories.tsx +74 -1
  185. package/src/components/icons/icon.tsx +89 -1
  186. package/src/components/icons/types.ts +47 -0
  187. package/src/components/images/README.mdx +340 -24
  188. package/src/components/images/img.scss +19 -3
  189. package/src/components/images/img.stories.tsx +424 -15
  190. package/src/components/images/img.test.tsx +354 -25
  191. package/src/components/images/img.tsx +186 -63
  192. package/src/components/images/img.types.ts +211 -0
  193. package/src/components/title/MIGRATION.md +199 -0
  194. package/src/components/title/README.md +326 -0
  195. package/src/components/title/README.mdx +452 -0
  196. package/src/components/title/title.stories.tsx +393 -0
  197. package/src/components/title/title.test.tsx +251 -0
  198. package/src/components/title/title.tsx +219 -0
  199. package/src/components/ui.stories.tsx +894 -0
  200. package/src/components/ui.test.tsx +559 -0
  201. package/src/components/ui.tsx +266 -15
  202. package/src/components/word-count/README.md +240 -0
  203. package/src/hooks.ts +1 -0
  204. package/src/index.ts +10 -2
  205. package/src/sass/_properties.scss +1 -0
  206. package/src/styles/alert/alert.css +94 -4
  207. package/src/styles/alert/alert.css.map +1 -1
  208. package/src/styles/badge/badge.css +20 -2
  209. package/src/styles/badge/badge.css.map +1 -1
  210. package/src/styles/buttons/button.css +31 -31
  211. package/src/styles/buttons/button.css.map +1 -1
  212. package/src/styles/cards/card.css +16 -0
  213. package/src/styles/cards/card.css.map +1 -1
  214. package/src/styles/details/details.css +19 -8
  215. package/src/styles/details/details.css.map +1 -1
  216. package/src/styles/dialog/dialog.css +43 -2
  217. package/src/styles/dialog/dialog.css.map +1 -1
  218. package/src/styles/images/img.css +15 -3
  219. package/src/styles/images/img.css.map +1 -1
  220. package/src/styles/index.css +240 -51
  221. package/src/styles/index.css.map +1 -1
  222. package/src/test/setup.d.ts +9 -0
  223. package/src/test/setup.ts +53 -1
  224. package/libs/chunk-6TE5QEVE.cjs +0 -13
  225. package/libs/chunk-6TE5QEVE.cjs.map +0 -1
  226. package/libs/chunk-7K76RW2A.cjs +0 -18
  227. package/libs/chunk-7K76RW2A.cjs.map +0 -1
  228. package/libs/chunk-BSPKFLO4.js +0 -8
  229. package/libs/chunk-BSPKFLO4.js.map +0 -1
  230. package/libs/chunk-BV5CLH44.cjs +0 -18
  231. package/libs/chunk-BV5CLH44.cjs.map +0 -1
  232. package/libs/chunk-DKGJHKGW.js +0 -9
  233. package/libs/chunk-DKGJHKGW.js.map +0 -1
  234. package/libs/chunk-ECLD37WN.cjs +0 -16
  235. package/libs/chunk-ECLD37WN.cjs.map +0 -1
  236. package/libs/chunk-HYBZBN4G.js +0 -8
  237. package/libs/chunk-HYBZBN4G.js.map +0 -1
  238. package/libs/chunk-KKLTUJFB.cjs.map +0 -1
  239. package/libs/chunk-M5QL5TAE.cjs +0 -14
  240. package/libs/chunk-M5QL5TAE.cjs.map +0 -1
  241. package/libs/chunk-NE6YXTMC.js +0 -7
  242. package/libs/chunk-NE6YXTMC.js.map +0 -1
  243. package/libs/chunk-O6QZBB6G.js.map +0 -1
  244. package/libs/chunk-SXVZSWX6.js +0 -11
  245. package/libs/chunk-SXVZSWX6.js.map +0 -1
  246. package/libs/ui-9a6f9f8d.d.ts +0 -24
  247. package/src/components/cards/README.md +0 -80
  248. package/src/components/dialog/hooks/useClickOutside.ts +0 -33
  249. /package/libs/{chunk-DV56L5YX.cjs.map → chunk-2LTJ7HHX.cjs.map} +0 -0
  250. /package/libs/{chunk-EQ67LF46.js.map → chunk-2Y7W75TT.js.map} +0 -0
  251. /package/libs/{chunk-X3EVB7VS.cjs.map → chunk-5S4ORA4C.cjs.map} +0 -0
  252. /package/libs/{chunk-6BVXFW7U.cjs.map → chunk-AHDJGCG5.cjs.map} +0 -0
  253. /package/libs/{chunk-E3XP6BEX.cjs.map → chunk-B7F5FS6D.cjs.map} +0 -0
  254. /package/libs/{chunk-LHVJKDMA.cjs.map → chunk-J32EZPYD.cjs.map} +0 -0
  255. /package/libs/{chunk-LL7HTLMS.cjs.map → chunk-M5RRNTVX.cjs.map} +0 -0
  256. /package/libs/{chunk-LIQJ7ZZR.js.map → chunk-NGTJDDFO.js.map} +0 -0
  257. /package/libs/{chunk-QCMV4VQZ.js.map → chunk-QLZWHAMK.js.map} +0 -0
  258. /package/libs/{chunk-BIP2NY53.js.map → chunk-RIVUMPOG.js.map} +0 -0
  259. /package/libs/{chunk-ICCKQ2GC.cjs.map → chunk-ROZI23GS.cjs.map} +0 -0
  260. /package/libs/{chunk-NHYXGV3L.js.map → chunk-SMYRLO3E.js.map} +0 -0
  261. /package/libs/{chunk-5ZM4XL44.js.map → chunk-TYRCEX2L.js.map} +0 -0
  262. /package/libs/{chunk-PPOOBUOS.js.map → chunk-XBA562WW.js.map} +0 -0
  263. /package/libs/{chunk-QVV34QEH.cjs.map → chunk-XTQKWY7W.cjs.map} +0 -0
  264. /package/libs/{chunk-YWOYVRFT.js.map → chunk-ZANSFMTD.js.map} +0 -0
@@ -2,21 +2,93 @@
2
2
  /* eslint-disable */
3
3
  import React from "react";
4
4
 
5
+ /**
6
+ * Extracts the appropriate ref type for a given element type.
7
+ *
8
+ * This utility type ensures that refs are properly typed based on the element
9
+ * being rendered. For example, a button element receives HTMLButtonElement ref.
10
+ *
11
+ * @typeParam C - The HTML element type (e.g., 'button', 'div', 'a')
12
+ * @example
13
+ * ```typescript
14
+ * type ButtonRef = PolymorphicRef<'button'>; // React.Ref<HTMLButtonElement>
15
+ * type DivRef = PolymorphicRef<'div'>; // React.Ref<HTMLDivElement>
16
+ * ```
17
+ */
5
18
  type PolymorphicRef<C extends React.ElementType> =
6
19
  React.ComponentPropsWithRef<C>["ref"];
7
20
 
21
+ /**
22
+ * Defines the 'as' prop that determines which HTML element to render.
23
+ *
24
+ * This is the core prop that enables polymorphic behavior, allowing components
25
+ * to render as any valid React element type while maintaining type safety.
26
+ *
27
+ * @typeParam C - The HTML element type to render
28
+ * @example
29
+ * ```typescript
30
+ * <UI as="button">Click me</UI>
31
+ * <UI as="a" href="/home">Link</UI>
32
+ * ```
33
+ */
8
34
  type AsProp<C extends React.ElementType> = {
9
35
  as?: C;
10
36
  };
11
37
 
38
+ /**
39
+ * Identifies props that should be omitted to prevent type conflicts.
40
+ *
41
+ * This type ensures that our custom props don't conflict with native element
42
+ * props by calculating which keys need to be omitted from the native props.
43
+ *
44
+ * @typeParam C - The HTML element type
45
+ * @typeParam P - The custom props to merge
46
+ */
12
47
  type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
13
48
 
49
+ /**
50
+ * Merges custom props with native element props while preventing conflicts.
51
+ *
52
+ * This creates a union of our custom props and the native props for the chosen
53
+ * element, omitting any conflicting keys to ensure type safety.
54
+ *
55
+ * @typeParam C - The HTML element type
56
+ * @typeParam Props - The custom props to add
57
+ * @example
58
+ * ```typescript
59
+ * // For a button, this merges custom props with HTMLButtonElement props
60
+ * type ButtonProps = PolymorphicComponentProp<'button', { variant?: string }>;
61
+ * ```
62
+ */
14
63
  type PolymorphicComponentProp<
15
64
  C extends React.ElementType,
16
65
  Props = {},
17
66
  > = React.PropsWithChildren<Props & AsProp<C>> &
18
67
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;
19
68
 
69
+ /**
70
+ * Extends PolymorphicComponentProp to include properly-typed ref support.
71
+ *
72
+ * This is the final type in the polymorphic type chain, adding ref forwarding
73
+ * with the correct ref type for the chosen element. The ref is properly typed
74
+ * to match the element being rendered, enabling focus management and direct
75
+ * DOM access for accessibility features like programmatic focus control.
76
+ *
77
+ * @typeParam C - The HTML element type
78
+ * @typeParam Props - The custom props to add
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Using ref for focus management (important for accessibility)
83
+ * const buttonRef = useRef<HTMLButtonElement>(null);
84
+ * useEffect(() => {
85
+ * // Programmatically focus for keyboard navigation
86
+ * buttonRef.current?.focus();
87
+ * }, []);
88
+ *
89
+ * return <UI as="button" ref={buttonRef}>Accessible Button</UI>;
90
+ * ```
91
+ */
20
92
  type PolymorphicComponentPropWithRef<
21
93
  C extends React.ElementType,
22
94
  Props = {},
@@ -24,33 +96,213 @@ type PolymorphicComponentPropWithRef<
24
96
  ref?: PolymorphicRef<C>;
25
97
  };
26
98
 
27
- type FPProps<C extends React.ElementType> = PolymorphicComponentPropWithRef<
99
+ /**
100
+ * Props for the UI component, extending polymorphic props with style and class support.
101
+ *
102
+ * The UI component automatically forwards all ARIA attributes and native HTML props
103
+ * to the rendered element, ensuring full accessibility support. This includes:
104
+ * - `aria-label`, `aria-labelledby` - Accessible names for screen readers
105
+ * - `aria-describedby` - Additional descriptive text references
106
+ * - `aria-expanded`, `aria-controls` - Interactive widget states
107
+ * - `role` - Semantic role override when needed
108
+ * - All other ARIA attributes and HTML props
109
+ *
110
+ * @typeParam C - The HTML element type to render
111
+ * @property {boolean} [renderStyles] - Reserved for future use. Currently has no effect.
112
+ * Styles are always rendered regardless of this prop value.
113
+ * @property {React.CSSProperties} [styles] - Inline styles to apply (overrides defaultStyles)
114
+ * @property {React.CSSProperties} [defaultStyles] - Base styles that can be overridden by styles prop
115
+ * @property {string} [classes] - CSS class names to apply to the element
116
+ * @property {string} [id] - HTML id attribute
117
+ * @property {React.ReactNode} [children] - Child elements to render
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * // All ARIA attributes are automatically forwarded
122
+ * <UI as="button" aria-label="Close dialog" aria-expanded={isOpen}>
123
+ * <CloseIcon />
124
+ * </UI>
125
+ * ```
126
+ */
127
+ type UIProps<C extends React.ElementType> = PolymorphicComponentPropWithRef<
28
128
  C,
29
129
  {
130
+ /** @deprecated Reserved for future use. Currently has no effect. Styles are always rendered. */
30
131
  renderStyles?: boolean;
31
132
  styles?: React.CSSProperties;
133
+ defaultStyles?: React.CSSProperties;
32
134
  classes?: string;
33
135
  id?: string;
34
136
  children?: React.ReactNode;
35
137
  }
36
138
  >;
37
139
 
38
- /*
39
- * FPComponent type definition
140
+ /**
141
+ * UI Component function signature.
40
142
  *
41
- * Defines the component function signature for the FP component.
143
+ * Defines the polymorphic component that can render as any HTML element while
144
+ * maintaining full TypeScript type safety for element-specific props.
42
145
  *
43
- * @typeParam C - The HTML element type to render
44
- * @param props - The component props
45
- * @returns React component
146
+ * @typeParam C - The HTML element type to render (defaults to 'div')
147
+ * @param {UIProps<C>} props - Component props including 'as', styles, and native element props
148
+ * @returns {React.ReactElement} A React element of the specified type
149
+ * @example
150
+ * ```typescript
151
+ * <UI as="button" onClick={handler}>Button</UI>
152
+ * <UI as="a" href="/link">Link</UI>
153
+ * <UI>Default div</UI>
154
+ * ```
46
155
  */
47
- type FPComponent = <C extends React.ElementType = "span">(
48
- props: FPProps<C>
49
- ) => React.ReactElement | (any & { displayName?: String });
156
+ type UIComponent = (<C extends React.ElementType = "div">(
157
+ props: UIProps<C>
158
+ ) => React.ReactElement | any) & { displayName?: string };
50
159
 
51
- const FP: FPComponent = React.forwardRef(
160
+ /**
161
+ * UI - A polymorphic React component that can render as any HTML element.
162
+ *
163
+ * The UI component is a foundational primitive used throughout fpkit to create
164
+ * flexible, type-safe components. It implements the polymorphic component pattern,
165
+ * allowing a single component to render as different HTML elements while maintaining
166
+ * full TypeScript type safety for element-specific props.
167
+ *
168
+ * ## Accessibility Considerations
169
+ *
170
+ * The UI component forwards all ARIA attributes and native HTML props, placing
171
+ * accessibility responsibility on the consumer. When creating interactive elements,
172
+ * you MUST ensure WCAG 2.1 AA compliance:
173
+ *
174
+ * - **Accessible Names**: All interactive elements need an accessible name via
175
+ * `aria-label`, `aria-labelledby`, or visible text content
176
+ * - **Semantic HTML**: Prefer semantic elements (`button`, `a`, `nav`) over
177
+ * generic containers (`div`, `span`) with ARIA roles when possible
178
+ * - **Focus Indicators**: Ensure focus indicators meet WCAG 2.4.7 (3:1 contrast)
179
+ * - **Keyboard Support**: Interactive elements must be keyboard accessible
180
+ *
181
+ * @typeParam C - The HTML element type to render (e.g., 'button', 'div', 'a')
182
+ *
183
+ * @param {C} [as='div'] - The HTML element type to render. Defaults to 'div'.
184
+ * @param {React.CSSProperties} [styles] - Inline styles to apply. Overrides defaultStyles.
185
+ * @param {string} [classes] - CSS class names to apply to the element.
186
+ * @param {React.CSSProperties} [defaultStyles] - Base styles that can be overridden by styles prop.
187
+ * @param {React.ReactNode} [children] - Child elements to render inside the component.
188
+ * @param {PolymorphicRef<C>} [ref] - Forwarded ref with proper typing for the element type.
189
+ * @param {boolean} [renderStyles] - Reserved for future use. Currently has no effect.
190
+ *
191
+ * @returns {React.ReactElement} A React element of the specified type with merged props.
192
+ *
193
+ * @example
194
+ * // Basic usage - renders as div
195
+ * <UI>Hello World</UI>
196
+ *
197
+ * @example
198
+ * // Polymorphic rendering - renders as button with type-safe props
199
+ * <UI as="button" onClick={handleClick} disabled>
200
+ * Click me
201
+ * </UI>
202
+ *
203
+ * @example
204
+ * // ✅ GOOD: Accessible button with aria-label for icon-only button
205
+ * <UI as="button" aria-label="Close dialog" onClick={handleClose}>
206
+ * <CloseIcon />
207
+ * </UI>
208
+ *
209
+ * @example
210
+ * // ✅ GOOD: Accessible link with descriptive text
211
+ * <UI as="a" href="/products">
212
+ * View all products
213
+ * </UI>
214
+ *
215
+ * @example
216
+ * // ✅ GOOD: Interactive element with proper role and keyboard support
217
+ * <UI
218
+ * as="div"
219
+ * role="button"
220
+ * tabIndex={0}
221
+ * aria-label="Toggle menu"
222
+ * onClick={handleToggle}
223
+ * onKeyDown={(e) => e.key === 'Enter' && handleToggle()}
224
+ * >
225
+ * Menu
226
+ * </UI>
227
+ *
228
+ * @example
229
+ * // ❌ BAD: Button without accessible name (screen readers can't identify it)
230
+ * <UI as="button" onClick={handleClose}>
231
+ * <CloseIcon />
232
+ * </UI>
233
+ *
234
+ * @example
235
+ * // ❌ BAD: Non-semantic div with click handler (not keyboard accessible)
236
+ * <UI as="div" onClick={handleClick}>
237
+ * Click me
238
+ * </UI>
239
+ *
240
+ * @example
241
+ * // ✅ GOOD: Custom focus indicator with WCAG 2.4.7 compliant contrast
242
+ * <UI
243
+ * as="button"
244
+ * styles={{
245
+ * outline: '2px solid transparent',
246
+ * outlineOffset: '2px',
247
+ * }}
248
+ * classes="focus:outline-blue-500"
249
+ * >
250
+ * Accessible Button
251
+ * </UI>
252
+ *
253
+ * @example
254
+ * // Style merging - defaultStyles provide base, styles override
255
+ * <UI
256
+ * as="span"
257
+ * defaultStyles={{ padding: '0.5rem', color: 'blue' }}
258
+ * styles={{ color: 'red' }}
259
+ * >
260
+ * Red text with padding
261
+ * </UI>
262
+ *
263
+ * @example
264
+ * // Ref forwarding for focus management
265
+ * const buttonRef = useRef<HTMLButtonElement>(null);
266
+ * useEffect(() => {
267
+ * // Programmatically focus for keyboard navigation
268
+ * buttonRef.current?.focus();
269
+ * }, []);
270
+ * <UI as="button" ref={buttonRef}>Auto-focused Button</UI>
271
+ *
272
+ * @example
273
+ * // Building accessible higher-level components with TypeScript
274
+ * interface AccessibleButtonProps extends React.ComponentPropsWithoutRef<'button'> {
275
+ * variant?: 'primary' | 'secondary';
276
+ * // Require either aria-label or children for accessibility
277
+ * 'aria-label'?: string;
278
+ * children?: React.ReactNode;
279
+ * }
280
+ *
281
+ * const AccessibleButton = React.forwardRef<HTMLButtonElement, AccessibleButtonProps>(
282
+ * ({ variant = 'primary', ...props }, ref) => {
283
+ * // Runtime check: ensure accessible name is provided
284
+ * if (!props['aria-label'] && !props.children) {
285
+ * console.warn('AccessibleButton requires either aria-label or children');
286
+ * }
287
+ *
288
+ * return (
289
+ * <UI
290
+ * as="button"
291
+ * ref={ref}
292
+ * defaultStyles={{
293
+ * padding: '0.5rem 1rem',
294
+ * borderRadius: '0.25rem',
295
+ * backgroundColor: variant === 'primary' ? '#007bff' : '#6c757d',
296
+ * }}
297
+ * {...props}
298
+ * />
299
+ * );
300
+ * }
301
+ * );
302
+ */
303
+ const UI: UIComponent = React.forwardRef(
52
304
  <C extends React.ElementType>(
53
- { as, styles, classes, children, defaultStyles, ...props }: FPProps<C>,
305
+ { as, styles, classes, children, defaultStyles, ...props }: UIProps<C>,
54
306
  ref?: PolymorphicRef<C>
55
307
  ) => {
56
308
  const Component = as ?? "div";
@@ -65,6 +317,5 @@ const FP: FPComponent = React.forwardRef(
65
317
  }
66
318
  );
67
319
 
68
- export default FP;
69
- // @ts-expect-error -- React component displayName
70
- FP.displayName = "FP";
320
+ export default UI;
321
+ UI.displayName = "UI";
@@ -0,0 +1,240 @@
1
+ # WordCount Component
2
+
3
+ A React component that displays word count, character count, and reading time estimates for text content. Built with TypeScript and follows WCAG 2.1 accessibility guidelines.
4
+
5
+ ## Features
6
+
7
+ - 📊 **Word Count**: Accurate word counting with proper handling of whitespace
8
+ - 🔢 **Character Count**: Optional character counting functionality
9
+ - ⏱️ **Reading Time**: Estimated reading time based on average reading speed
10
+ - ♿ **Accessible**: Full WCAG 2.1 compliance with proper ARIA labels
11
+ - 🎨 **Customizable**: Custom labels and styling options
12
+ - 📱 **Responsive**: Works across all device sizes
13
+ - 🎯 **Type Safe**: Full TypeScript support
14
+
15
+ ## Installation
16
+
17
+ The WordCount component is part of the FPKit component library:
18
+
19
+ ```bash
20
+ npm install @fpkit/acss
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ```tsx
26
+ import { WordCount } from '@fpkit/acss'
27
+
28
+ function MyComponent() {
29
+ const text = "Hello world, this is a sample text for word counting."
30
+
31
+ return <WordCount text={text} />
32
+ }
33
+ ```
34
+
35
+ ## Props
36
+
37
+ | Prop | Type | Default | Description |
38
+ |------|------|---------|-------------|
39
+ | `text` | `string` | **required** | The text content to analyze |
40
+ | `showCharacterCount` | `boolean` | `false` | Whether to display character count |
41
+ | `showReadingTime` | `boolean` | `false` | Whether to display estimated reading time |
42
+ | `wordLabel` | `string` | `"Words"` | Custom label for word count |
43
+ | `characterLabel` | `string` | `"Characters"` | Custom label for character count |
44
+ | `readingTimeLabel` | `string` | `"Reading time"` | Custom label for reading time |
45
+ | `className` | `string` | `""` | Additional CSS classes |
46
+
47
+ ## Examples
48
+
49
+ ### Basic Word Count
50
+
51
+ ```tsx
52
+ <WordCount text="Hello world, this is a test." />
53
+ // Output: "7 Words"
54
+ ```
55
+
56
+ ### With Character Count
57
+
58
+ ```tsx
59
+ <WordCount
60
+ text="Hello world, this is a test."
61
+ showCharacterCount={true}
62
+ />
63
+ // Output: "7 Words • 28 Characters"
64
+ ```
65
+
66
+ ### With Reading Time
67
+
68
+ ```tsx
69
+ <WordCount
70
+ text="Your long article content here..."
71
+ showReadingTime={true}
72
+ />
73
+ // Output: "250 Words • 2 min Reading time"
74
+ ```
75
+
76
+ ### All Features Enabled
77
+
78
+ ```tsx
79
+ <WordCount
80
+ text="Your content here..."
81
+ showCharacterCount={true}
82
+ showReadingTime={true}
83
+ />
84
+ // Output: "250 Words • 1,234 Characters • 2 min Reading time"
85
+ ```
86
+
87
+ ### Custom Labels
88
+
89
+ ```tsx
90
+ <WordCount
91
+ text="Your content here..."
92
+ wordLabel="Terms"
93
+ characterLabel="Chars"
94
+ readingTimeLabel="Est. read time"
95
+ showCharacterCount={true}
96
+ showReadingTime={true}
97
+ />
98
+ // Output: "250 Terms • 1,234 Chars • 2 min Est. read time"
99
+ ```
100
+
101
+ ### Interactive Example
102
+
103
+ ```tsx
104
+ function InteractiveWordCount() {
105
+ const [text, setText] = useState('')
106
+
107
+ return (
108
+ <div>
109
+ <textarea
110
+ value={text}
111
+ onChange={(e) => setText(e.target.value)}
112
+ placeholder="Type your text here..."
113
+ />
114
+ <WordCount
115
+ text={text}
116
+ showCharacterCount={true}
117
+ showReadingTime={true}
118
+ />
119
+ </div>
120
+ )
121
+ }
122
+ ```
123
+
124
+ ## Accessibility Features
125
+
126
+ The WordCount component is built with accessibility in mind:
127
+
128
+ - **ARIA Labels**: Proper `aria-label` attributes for screen readers
129
+ - **Live Regions**: Uses `aria-live="polite"` for dynamic updates
130
+ - **Role Attributes**: Proper `role="status"` for status information
131
+ - **Semantic HTML**: Uses semantic HTML elements for better structure
132
+ - **High Contrast**: Supports high contrast mode
133
+ - **Keyboard Navigation**: Fully keyboard accessible
134
+
135
+ ## Styling
136
+
137
+ The component comes with built-in CSS classes for styling:
138
+
139
+ - `.word-count` - Main container
140
+ - `.word-count__stat` - Individual stat container
141
+ - `.word-count__label` - Label text
142
+ - `.word-count__separator` - Separator between stats
143
+
144
+ ### CSS Custom Properties
145
+
146
+ The component supports CSS custom properties for theming:
147
+
148
+ ```css
149
+ .word-count {
150
+ --text-primary: #111827;
151
+ --text-secondary: #6b7280;
152
+ --text-tertiary: #9ca3af;
153
+ --focus-ring: #3b82f6;
154
+ }
155
+ ```
156
+
157
+ ### Size Modifiers
158
+
159
+ ```css
160
+ .word-count--small { /* Smaller text size */ }
161
+ .word-count--large { /* Larger text size */ }
162
+ .word-count--block { /* Vertical layout */ }
163
+ .word-count--status { /* Status indicator styling */ }
164
+ ```
165
+
166
+ ## Algorithm Details
167
+
168
+ ### Word Counting
169
+
170
+ - Splits text on whitespace using regex `/\s+/`
171
+ - Filters out empty strings
172
+ - Handles multiple spaces, tabs, and line breaks correctly
173
+
174
+ ### Reading Time Calculation
175
+
176
+ - Based on average reading speed of 200 words per minute
177
+ - Uses `Math.ceil()` to round up to nearest minute
178
+ - Only shows for text with > 0 words
179
+
180
+ ### Character Counting
181
+
182
+ - Uses `string.length` property
183
+ - Includes all characters (spaces, punctuation, etc.)
184
+
185
+ ## Browser Support
186
+
187
+ The WordCount component supports all modern browsers:
188
+
189
+ - Chrome 60+
190
+ - Firefox 60+
191
+ - Safari 12+
192
+ - Edge 79+
193
+
194
+ ## Performance
195
+
196
+ - Uses `React.useMemo()` for efficient re-computation
197
+ - Only recalculates when text content changes
198
+ - Minimal DOM manipulation for optimal performance
199
+
200
+ ## TypeScript Support
201
+
202
+ Full TypeScript support with exported types:
203
+
204
+ ```tsx
205
+ import { WordCount, type WordCountProps } from '@fpkit/acss'
206
+
207
+ const props: WordCountProps = {
208
+ text: "Sample text",
209
+ showCharacterCount: true,
210
+ showReadingTime: true
211
+ }
212
+ ```
213
+
214
+ ## Testing
215
+
216
+ The component includes comprehensive unit tests covering:
217
+
218
+ - Basic functionality
219
+ - Edge cases (empty text, whitespace)
220
+ - Accessibility features
221
+ - Custom props
222
+ - Error handling
223
+
224
+ Run tests with:
225
+
226
+ ```bash
227
+ npm test word-count
228
+ ```
229
+
230
+ ## Contributing
231
+
232
+ 1. Fork the repository
233
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
234
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
235
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
236
+ 5. Open a Pull Request
237
+
238
+ ## License
239
+
240
+ This component is part of the FPKit library and follows the same licensing terms.
package/src/hooks.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { usePopover } from './hooks/popover/use-popover'
2
+ export { useBreadcrumbSegments } from './components/breadcrumbs/breadcrumb'
package/src/index.ts CHANGED
@@ -20,7 +20,8 @@ export {
20
20
  export { Field, type FieldProps } from "./components/form/fields";
21
21
  export { Input, type InputProps } from "./components/form/inputs";
22
22
  export { Icon, type IconProps } from "./components/icons/icon";
23
- export { Img, type ImageProps } from "./components/images/img";
23
+ export { Img } from "./components/images/img";
24
+ export type { ImgProps } from "./components/images/img.types";
24
25
  export { Link, type LinkProps } from "./components/link/link";
25
26
  export { List, type ListItemProps } from "./components/list/list";
26
27
  export { Modal, type ModalProps } from "./components/modal/modal";
@@ -37,12 +38,19 @@ export * from "./components/nav/nav";
37
38
 
38
39
  // Typography components
39
40
  export * from "./components/text/text";
40
- export * from "./components/heading/heading";
41
+
42
+ // Title component (primary export)
43
+ export { default as Title, type TitleProps, type HeadingLevel } from "./components/title/title";
44
+
45
+ // Heading component (deprecated - use Title instead)
46
+ /** @deprecated Use Title component instead. Will be removed in v3.0.0 */
47
+ export { default as Heading } from "./components/heading/heading";
41
48
 
42
49
  // Form components
43
50
  export * from "./components/form/textarea";
44
51
 
45
52
  // UI elements
53
+ export { Badge, type BadgeProps } from "./components/badge/badge";
46
54
  export * from "./components/tag/tag";
47
55
  export * from "./components/tables/table-elements";
48
56
  export * from "./components/details/details";
@@ -13,6 +13,7 @@
13
13
  --placeholder-style: italic;
14
14
  --placeholder-fs: smaller;
15
15
  --transition: all 0.25s linear;
16
+ --tran-all: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
16
17
 
17
18
  // * responsive fonts sizes
18
19
  --fs-0: clamp(.9rem, 4vw - 1rem, 1.12rem); // 16px default