@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
@@ -0,0 +1,416 @@
1
+ # UI Component
2
+
3
+ > A polymorphic React component that can render as any HTML element with full TypeScript type safety.
4
+
5
+ ## Overview
6
+
7
+ The **UI component** is a foundational primitive used throughout the fpkit library to create flexible, type-safe components. It implements the polymorphic component pattern, enabling a single component to render as different HTML elements while maintaining complete TypeScript type safety for element-specific props.
8
+
9
+ This component serves as the building block for 25+ components across fpkit, including Button, Badge, Tag, Heading, Text, and more.
10
+
11
+ ## Features
12
+
13
+ - **Polymorphic Rendering** - Render as any valid HTML element using the `as` prop
14
+ - **Full Type Safety** - TypeScript infers correct props based on the chosen element
15
+ - **Style Merging** - Intelligent merging of `defaultStyles` and `styles` props
16
+ - **Ref Forwarding** - Properly typed ref support for all element types
17
+ - **Prop Spreading** - All native element props are forwarded with type checking
18
+ - **Zero Runtime Overhead** - Thin wrapper with minimal performance cost
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ |------|------|---------|-------------|
24
+ | `as` | `React.ElementType` | `'div'` | The HTML element type to render (e.g., 'button', 'span', 'a') |
25
+ | `styles` | `React.CSSProperties` | `undefined` | Inline styles to apply. Overrides `defaultStyles`. |
26
+ | `classes` | `string` | `undefined` | CSS class names to apply to the element |
27
+ | `defaultStyles` | `React.CSSProperties` | `undefined` | Base styles that can be overridden by `styles` prop |
28
+ | `children` | `React.ReactNode` | `undefined` | Child elements to render inside the component |
29
+ | `ref` | `React.Ref<Element>` | `undefined` | Forwarded ref with proper typing for the element type |
30
+ | `renderStyles` | `boolean` | - | **Deprecated**: Reserved for future use. Currently has no effect. |
31
+ | `...props` | Element-specific | - | All native element props are forwarded (onClick, href, disabled, etc.) |
32
+
33
+ ## Usage Examples
34
+
35
+ ### Basic Usage
36
+
37
+ By default, the UI component renders as a `div`:
38
+
39
+ ```tsx
40
+ import UI from '@fpkit/acss';
41
+
42
+ function Example() {
43
+ return <UI>Hello World</UI>;
44
+ }
45
+ // Renders: <div>Hello World</div>
46
+ ```
47
+
48
+ ### Polymorphic Rendering
49
+
50
+ Use the `as` prop to render as different HTML elements:
51
+
52
+ ```tsx
53
+ // Render as a button
54
+ <UI as="button" onClick={handleClick}>
55
+ Click me
56
+ </UI>
57
+
58
+ // Render as a link with href (TypeScript knows href is valid!)
59
+ <UI as="a" href="/home" target="_blank">
60
+ Go Home
61
+ </UI>
62
+
63
+ // Render as a semantic section
64
+ <UI as="section" aria-label="Main Content">
65
+ <h2>Section Title</h2>
66
+ <p>Section content...</p>
67
+ </UI>
68
+
69
+ // Render as a span
70
+ <UI as="span" style={{ fontWeight: 'bold' }}>
71
+ Bold Text
72
+ </UI>
73
+ ```
74
+
75
+ ### Style Customization
76
+
77
+ The UI component supports both `defaultStyles` (base styles) and `styles` (override styles):
78
+
79
+ ```tsx
80
+ // defaultStyles provide a base layer
81
+ <UI
82
+ defaultStyles={{
83
+ padding: '1rem',
84
+ backgroundColor: 'lightblue',
85
+ color: 'blue'
86
+ }}
87
+ >
88
+ Blue text with padding
89
+ </UI>
90
+
91
+ // styles override defaultStyles
92
+ <UI
93
+ defaultStyles={{
94
+ padding: '1rem',
95
+ color: 'blue'
96
+ }}
97
+ styles={{
98
+ color: 'red' // This overrides the blue color
99
+ }}
100
+ >
101
+ Red text with padding (color overridden)
102
+ </UI>
103
+
104
+ // Using CSS custom properties
105
+ <UI
106
+ styles={{
107
+ '--button-bg': '#007bff',
108
+ '--button-color': 'white',
109
+ padding: '0.5rem 1rem',
110
+ backgroundColor: 'var(--button-bg)',
111
+ color: 'var(--button-color)'
112
+ } as React.CSSProperties}
113
+ >
114
+ Themed Button
115
+ </UI>
116
+ ```
117
+
118
+ ### Ref Forwarding
119
+
120
+ The UI component properly forwards refs with correct typing:
121
+
122
+ ```tsx
123
+ import { useRef, useEffect } from 'react';
124
+
125
+ function FocusExample() {
126
+ const buttonRef = useRef<HTMLButtonElement>(null);
127
+
128
+ useEffect(() => {
129
+ // TypeScript knows this is an HTMLButtonElement
130
+ buttonRef.current?.focus();
131
+ }, []);
132
+
133
+ return (
134
+ <UI as="button" ref={buttonRef}>
135
+ Auto-focused Button
136
+ </UI>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ### Type-Safe Props
142
+
143
+ TypeScript automatically infers the correct props based on the `as` prop:
144
+
145
+ ```tsx
146
+ // Button-specific props are available
147
+ <UI as="button" type="submit" disabled>
148
+ Submit
149
+ </UI>
150
+
151
+ // Anchor-specific props are available
152
+ <UI as="a" href="/link" target="_blank" rel="noopener">
153
+ External Link
154
+ </UI>
155
+
156
+ // Form-specific props are available
157
+ <UI as="form" onSubmit={handleSubmit} method="POST">
158
+ <input type="text" />
159
+ </UI>
160
+
161
+ // TypeScript will error on invalid prop combinations!
162
+ // ❌ This will cause a TypeScript error:
163
+ // <UI as="button" href="/link">Invalid</UI>
164
+ ```
165
+
166
+ ### Building Higher-Level Components
167
+
168
+ The UI component is designed to be a primitive for building other components:
169
+
170
+ ```tsx
171
+ // Example: Building a Button component
172
+ interface ButtonProps {
173
+ variant?: 'primary' | 'secondary';
174
+ children: React.ReactNode;
175
+ }
176
+
177
+ const Button = ({ variant = 'primary', ...props }: ButtonProps) => {
178
+ const variantStyles = {
179
+ primary: {
180
+ backgroundColor: '#007bff',
181
+ color: 'white',
182
+ border: 'none'
183
+ },
184
+ secondary: {
185
+ backgroundColor: 'transparent',
186
+ color: '#007bff',
187
+ border: '1px solid #007bff'
188
+ }
189
+ };
190
+
191
+ return (
192
+ <UI
193
+ as="button"
194
+ defaultStyles={{
195
+ padding: '0.5rem 1rem',
196
+ borderRadius: '0.25rem',
197
+ cursor: 'pointer',
198
+ ...variantStyles[variant]
199
+ }}
200
+ {...props}
201
+ />
202
+ );
203
+ };
204
+
205
+ // Usage
206
+ <Button variant="primary" onClick={handleClick}>
207
+ Primary Button
208
+ </Button>
209
+ ```
210
+
211
+ ## Technical Details
212
+
213
+ ### Type System Architecture
214
+
215
+ The UI component uses a sophisticated type system to achieve polymorphic behavior:
216
+
217
+ ```
218
+ FPComponent (function signature)
219
+
220
+ FPProps<C> (merged props with ref)
221
+
222
+ PolymorphicComponentPropWithRef<C, Props>
223
+
224
+ PolymorphicComponentProp<C, Props> (props without ref)
225
+
226
+ PropsWithChildren<Props & AsProp<C>> & Omit<ComponentPropsWithoutRef<C>, ...>
227
+ ```
228
+
229
+ **How it works:**
230
+
231
+ 1. **Generic Parameter**: The component accepts a generic `C extends React.ElementType` representing the element type
232
+ 2. **Prop Inference**: TypeScript uses `React.ComponentPropsWithoutRef<C>` to extract native props for that element
233
+ 3. **Prop Merging**: Custom props (`styles`, `classes`, etc.) are merged with native props
234
+ 4. **Conflict Resolution**: The `PropsToOmit` type removes conflicting keys to prevent type errors
235
+ 5. **Ref Typing**: The `PolymorphicRef` type extracts the correct ref type for the element
236
+
237
+ ### Style Merging Behavior
238
+
239
+ The component merges styles using JavaScript object spread:
240
+
241
+ ```typescript
242
+ const styleObj: React.CSSProperties = { ...defaultStyles, ...styles };
243
+ ```
244
+
245
+ **Precedence:**
246
+ - `defaultStyles` is applied first (base layer)
247
+ - `styles` is applied second (override layer)
248
+ - Properties in `styles` always override matching properties in `defaultStyles`
249
+
250
+ **Example:**
251
+ ```tsx
252
+ <UI
253
+ defaultStyles={{ padding: '1rem', color: 'blue', fontSize: '16px' }}
254
+ styles={{ color: 'red' }}
255
+ />
256
+ // Result: { padding: '1rem', color: 'red', fontSize: '16px' }
257
+ ```
258
+
259
+ ## Common Patterns
260
+
261
+ ### How fpkit Components Use UI
262
+
263
+ Many fpkit components are built on the UI primitive:
264
+
265
+ **Button Component:**
266
+ ```tsx
267
+ <UI
268
+ as="button"
269
+ defaultStyles={{ padding: '0.5rem 1rem', borderRadius: '0.25rem' }}
270
+ {...props}
271
+ />
272
+ ```
273
+
274
+ **Badge Component:**
275
+ ```tsx
276
+ <UI
277
+ as="span"
278
+ defaultStyles={{
279
+ display: 'inline-block',
280
+ padding: '0.25rem 0.5rem',
281
+ fontSize: '0.75rem',
282
+ borderRadius: '0.25rem'
283
+ }}
284
+ {...props}
285
+ />
286
+ ```
287
+
288
+ **Tag Component:**
289
+ ```tsx
290
+ <UI
291
+ as="span"
292
+ defaultStyles={{
293
+ display: 'inline-flex',
294
+ alignItems: 'center',
295
+ gap: '0.25rem'
296
+ }}
297
+ {...props}
298
+ />
299
+ ```
300
+
301
+ ## FP vs UI
302
+
303
+ The fpkit codebase contains two similar polymorphic components:
304
+
305
+ | Feature | UI Component | FP Component |
306
+ |---------|-------------|--------------|
307
+ | File | `ui.tsx` | `fp.tsx` |
308
+ | Usage | Used by 25+ components | Standalone usage |
309
+ | `defaultStyles` | ✅ Supported | ✅ Supported |
310
+ | `renderStyles` | ⚠️ Defined but not implemented | ⚠️ Defined but not implemented |
311
+ | Tests | 📝 Being added | ✅ Has tests |
312
+ | Default Element | `div` | `div` |
313
+
314
+ **When to use UI:**
315
+ - When building components within the fpkit library
316
+ - When you need the established patterns used by Button, Badge, etc.
317
+ - When `defaultStyles` merging is important
318
+
319
+ **When to use FP:**
320
+ - For standalone polymorphic rendering
321
+ - When you want a tested primitive
322
+ - For quick prototyping
323
+
324
+ > **Note**: These components may be consolidated in a future version. Check the documentation for updates.
325
+
326
+ ## Accessibility Notes
327
+
328
+ The UI component is a low-level primitive that doesn't enforce accessibility features. It's your responsibility to:
329
+
330
+ - **Choose semantic HTML elements** - Use `as="button"` for clickable actions, `as="a"` for navigation, etc.
331
+ - **Avoid generic divs for interactive elements** - Don't use `<UI as="div" onClick={...}>` for buttons
332
+ - **Use proper ARIA attributes** - Add `aria-label`, `role`, etc. when needed
333
+ - **Ensure keyboard accessibility** - Native elements provide this for free (button, a, etc.)
334
+
335
+ **Good examples:**
336
+ ```tsx
337
+ // ✅ Semantic button
338
+ <UI as="button" onClick={handleClick}>Click me</UI>
339
+
340
+ // ✅ Semantic navigation link
341
+ <UI as="nav" aria-label="Main navigation">...</UI>
342
+
343
+ // ✅ Semantic article with heading
344
+ <UI as="article">
345
+ <UI as="h2">Article Title</UI>
346
+ </UI>
347
+ ```
348
+
349
+ **Bad examples:**
350
+ ```tsx
351
+ // ❌ Div used as button (not keyboard accessible)
352
+ <UI as="div" onClick={handleClick}>Click me</UI>
353
+
354
+ // ❌ Span used as link (not accessible)
355
+ <UI as="span" onClick={navigate}>Go to page</UI>
356
+ ```
357
+
358
+ ## Additional Notes
359
+
360
+ ### Performance Considerations
361
+
362
+ The UI component has minimal performance overhead:
363
+ - Single object spread for style merging
364
+ - No hooks or state
365
+ - Thin wrapper around native elements
366
+ - Ref forwarding with no additional rendering
367
+
368
+ ### TypeScript IntelliSense
369
+
370
+ One of the biggest benefits of the UI component is enhanced developer experience:
371
+
372
+ - **Autocomplete** - Your IDE will suggest valid props based on the `as` prop
373
+ - **Type Errors** - Invalid prop combinations are caught at compile time
374
+ - **Documentation** - Hover over props to see JSDoc comments
375
+ - **Refactoring** - Change the `as` prop and see prop types update instantly
376
+
377
+ ### Gotchas and Best Practices
378
+
379
+ **Style Object Creation:**
380
+ ```tsx
381
+ // ❌ Bad: Creates new object on every render
382
+ <UI styles={{ padding: '1rem' }}>Text</UI>
383
+
384
+ // ✅ Good: Memoize style objects
385
+ const styles = useMemo(() => ({ padding: '1rem' }), []);
386
+ <UI styles={styles}>Text</UI>
387
+
388
+ // ✅ Also good: Define outside component if static
389
+ const STATIC_STYLES = { padding: '1rem' };
390
+ <UI styles={STATIC_STYLES}>Text</UI>
391
+ ```
392
+
393
+ **Ref Type Safety:**
394
+ ```tsx
395
+ // ✅ Good: Ref type matches element
396
+ const buttonRef = useRef<HTMLButtonElement>(null);
397
+ <UI as="button" ref={buttonRef}>Button</UI>
398
+
399
+ // ❌ Bad: Ref type mismatch
400
+ const divRef = useRef<HTMLDivElement>(null);
401
+ <UI as="button" ref={divRef}>Button</UI> // Type error!
402
+ ```
403
+
404
+ ## Related Components
405
+
406
+ - **FP Component** - Alternative polymorphic primitive
407
+ - **Button** - Interactive button component built with UI
408
+ - **Badge** - Small label component built with UI
409
+ - **Tag** - Dismissible tag component built with UI
410
+ - **Heading** - Semantic heading component built with UI
411
+
412
+ ## See Also
413
+
414
+ - [React Polymorphic Components](https://www.benmvp.com/blog/polymorphic-react-components-typescript/)
415
+ - [TypeScript Advanced Types](https://www.typescriptlang.org/docs/handbook/2/types-from-types.html)
416
+ - [React forwardRef](https://react.dev/reference/react/forwardRef)