@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,894 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import React, { useRef, useEffect } from "react";
3
+ import UI from "./ui";
4
+
5
+ /**
6
+ * The UI component is a polymorphic React primitive that can render as any HTML element
7
+ * while maintaining full TypeScript type safety. It serves as the foundation for 25+
8
+ * components across the fpkit library.
9
+ *
10
+ * ## Key Features
11
+ * - Polymorphic rendering with the `as` prop
12
+ * - Full TypeScript type safety for element-specific props
13
+ * - Style merging with `defaultStyles` and `styles`
14
+ * - Proper ref forwarding with typed refs
15
+ * - Zero runtime overhead
16
+ */
17
+ const meta = {
18
+ title: "FP.UI",
19
+ component: UI,
20
+ tags: ["autodocs", "primitive"],
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component:
25
+ "A foundational polymorphic component that can render as any HTML element with complete type safety.",
26
+ },
27
+ },
28
+ },
29
+ argTypes: {
30
+ as: {
31
+ control: "select",
32
+ options: [
33
+ "div",
34
+ "span",
35
+ "button",
36
+ "a",
37
+ "section",
38
+ "article",
39
+ "nav",
40
+ "main",
41
+ "header",
42
+ "footer",
43
+ ],
44
+ description: "The HTML element type to render",
45
+ table: {
46
+ type: { summary: "React.ElementType" },
47
+ defaultValue: { summary: "div" },
48
+ },
49
+ },
50
+ styles: {
51
+ control: "object",
52
+ description: "Inline styles to apply (overrides defaultStyles)",
53
+ table: {
54
+ type: { summary: "React.CSSProperties" },
55
+ },
56
+ },
57
+ classes: {
58
+ control: "text",
59
+ description: "CSS class names to apply",
60
+ table: {
61
+ type: { summary: "string" },
62
+ },
63
+ },
64
+ children: {
65
+ control: "text",
66
+ description: "Content to render inside the component",
67
+ },
68
+ },
69
+ } satisfies Meta<typeof UI>;
70
+
71
+ export default meta;
72
+ type Story = StoryObj<typeof meta>;
73
+
74
+ /**
75
+ * Default story showing the UI component rendering as a div with basic styling.
76
+ */
77
+ export const Default: Story = {
78
+ args: {
79
+ children: "Default UI Component (renders as div)",
80
+ styles: {
81
+ padding: "1rem",
82
+ backgroundColor: "#f0f0f0",
83
+ borderRadius: "0.25rem",
84
+ },
85
+ },
86
+ };
87
+
88
+ /**
89
+ * Demonstrates the UI component rendering as a button with button-specific props.
90
+ */
91
+ export const AsButton: Story = {
92
+ args: {
93
+ as: "button",
94
+ children: "Click Me",
95
+ styles: {
96
+ padding: "0.75rem 1.5rem",
97
+ backgroundColor: "#007bff",
98
+ color: "white",
99
+ border: "none",
100
+ borderRadius: "0.25rem",
101
+ cursor: "pointer",
102
+ fontSize: "1rem",
103
+ },
104
+ },
105
+ };
106
+
107
+ /**
108
+ * Demonstrates the UI component rendering as a span element.
109
+ */
110
+ export const AsSpan: Story = {
111
+ args: {
112
+ as: "span",
113
+ children: "Inline Span Element",
114
+ styles: {
115
+ fontWeight: "bold",
116
+ color: "#28a745",
117
+ padding: "0.25rem 0.5rem",
118
+ backgroundColor: "#d4edda",
119
+ borderRadius: "0.25rem",
120
+ },
121
+ },
122
+ };
123
+
124
+ /**
125
+ * Demonstrates the UI component rendering as an anchor link with href.
126
+ */
127
+ export const AsAnchor: Story = {
128
+ args: {
129
+ as: "a",
130
+ href: "https://example.com",
131
+ target: "_blank",
132
+ rel: "noopener noreferrer",
133
+ children: "External Link",
134
+ styles: {
135
+ color: "#007bff",
136
+ textDecoration: "underline",
137
+ padding: "0.5rem",
138
+ display: "inline-block",
139
+ },
140
+ },
141
+ };
142
+
143
+ /**
144
+ * Demonstrates the UI component rendering as a semantic section element.
145
+ */
146
+ export const AsSection: Story = {
147
+ args: {
148
+ as: "section",
149
+ children: (
150
+ <>
151
+ <h2 style={{ marginTop: 0 }}>Section Title</h2>
152
+ <p>
153
+ This demonstrates the UI component rendering as a semantic section
154
+ element.
155
+ </p>
156
+ </>
157
+ ),
158
+ styles: {
159
+ padding: "1.5rem",
160
+ backgroundColor: "#fff3cd",
161
+ border: "1px solid #ffc107",
162
+ borderRadius: "0.25rem",
163
+ },
164
+ },
165
+ };
166
+
167
+ /**
168
+ * Shows how the `styles` prop applies inline styles.
169
+ */
170
+ export const WithStyles: Story = {
171
+ args: {
172
+ children: "Styled with inline CSS",
173
+ styles: {
174
+ padding: "1rem 2rem",
175
+ backgroundColor: "#6f42c1",
176
+ color: "white",
177
+ borderRadius: "0.5rem",
178
+ fontWeight: "bold",
179
+ textAlign: "center",
180
+ },
181
+ },
182
+ };
183
+
184
+ /**
185
+ * Shows how the `classes` prop applies CSS class names.
186
+ */
187
+ export const WithClasses: Story = {
188
+ args: {
189
+ children: "Element with CSS classes",
190
+ classes: "custom-class another-class",
191
+ styles: {
192
+ padding: "1rem",
193
+ border: "2px dashed #17a2b8",
194
+ },
195
+ },
196
+ };
197
+
198
+ /**
199
+ * Demonstrates how `styles` overrides `defaultStyles`.
200
+ */
201
+ export const StyleMerging: Story = {
202
+ args: {
203
+ children: "Style Merging Example",
204
+ defaultStyles: {
205
+ padding: "1rem",
206
+ backgroundColor: "lightblue",
207
+ color: "blue",
208
+ fontSize: "1rem",
209
+ borderRadius: "0.25rem",
210
+ },
211
+ styles: {
212
+ color: "red", // This overrides the blue color
213
+ fontWeight: "bold", // This is added
214
+ },
215
+ },
216
+ parameters: {
217
+ docs: {
218
+ description: {
219
+ story:
220
+ "The `defaultStyles` provide base styling (blue text, light blue background), while `styles` overrides specific properties (text becomes red and bold).",
221
+ },
222
+ },
223
+ },
224
+ };
225
+
226
+ /**
227
+ * Demonstrates using CSS custom properties for theming.
228
+ */
229
+ export const CSSCustomProperties: Story = {
230
+ args: {
231
+ children: "Themed with CSS Variables",
232
+ styles: {
233
+ "--primary-color": "#28a745",
234
+ "--secondary-color": "#ffffff",
235
+ padding: "1rem 1.5rem",
236
+ backgroundColor: "var(--primary-color)",
237
+ color: "var(--secondary-color)",
238
+ borderRadius: "0.25rem",
239
+ } as React.CSSProperties,
240
+ },
241
+ parameters: {
242
+ docs: {
243
+ description: {
244
+ story:
245
+ "CSS custom properties (variables) can be set dynamically through the styles prop for theming.",
246
+ },
247
+ },
248
+ },
249
+ };
250
+
251
+ /**
252
+ * Demonstrates ref forwarding with proper typing.
253
+ */
254
+ export const RefForwarding: Story = {
255
+ render: () => {
256
+ const RefExample = () => {
257
+ const buttonRef = useRef<HTMLButtonElement>(null);
258
+
259
+ useEffect(() => {
260
+ // Focus the button on mount
261
+ if (buttonRef.current) {
262
+ buttonRef.current.focus();
263
+ }
264
+ }, []);
265
+
266
+ return (
267
+ <UI
268
+ as="button"
269
+ ref={buttonRef}
270
+ styles={{
271
+ padding: "0.75rem 1.5rem",
272
+ backgroundColor: "#dc3545",
273
+ color: "white",
274
+ border: "none",
275
+ borderRadius: "0.25rem",
276
+ cursor: "pointer",
277
+ }}
278
+ >
279
+ Auto-focused Button
280
+ </UI>
281
+ );
282
+ };
283
+
284
+ return <RefExample />;
285
+ },
286
+ parameters: {
287
+ docs: {
288
+ description: {
289
+ story:
290
+ "The UI component forwards refs with proper typing. This button is automatically focused when mounted.",
291
+ },
292
+ },
293
+ },
294
+ };
295
+
296
+ /**
297
+ * Example of building a Button component using UI as a primitive.
298
+ */
299
+ export const ButtonPattern: Story = {
300
+ render: () => {
301
+ interface ButtonProps {
302
+ variant?: "primary" | "secondary" | "danger";
303
+ children: React.ReactNode;
304
+ onClick?: () => void;
305
+ }
306
+
307
+ const Button = ({
308
+ variant = "primary",
309
+ children,
310
+ ...props
311
+ }: ButtonProps) => {
312
+ const variantStyles = {
313
+ primary: {
314
+ backgroundColor: "#007bff",
315
+ color: "white",
316
+ },
317
+ secondary: {
318
+ backgroundColor: "#6c757d",
319
+ color: "white",
320
+ },
321
+ danger: {
322
+ backgroundColor: "#dc3545",
323
+ color: "white",
324
+ },
325
+ };
326
+
327
+ return (
328
+ <UI
329
+ as="button"
330
+ defaultStyles={{
331
+ padding: "0.5rem 1rem",
332
+ border: "none",
333
+ borderRadius: "0.25rem",
334
+ cursor: "pointer",
335
+ fontSize: "1rem",
336
+ ...variantStyles[variant],
337
+ }}
338
+ {...props}
339
+ >
340
+ {children}
341
+ </UI>
342
+ );
343
+ };
344
+
345
+ return (
346
+ <div style={{ display: "flex", gap: "1rem" }}>
347
+ <Button variant="primary">Primary</Button>
348
+ <Button variant="secondary">Secondary</Button>
349
+ <Button variant="danger">Danger</Button>
350
+ </div>
351
+ );
352
+ },
353
+ parameters: {
354
+ docs: {
355
+ description: {
356
+ story:
357
+ "This shows how to build a Button component with variants using UI as the primitive.",
358
+ },
359
+ },
360
+ },
361
+ };
362
+
363
+ /**
364
+ * Example of building a Badge component using UI as a primitive.
365
+ */
366
+ export const BadgePattern: Story = {
367
+ render: () => {
368
+ interface BadgeProps {
369
+ variant?: "info" | "success" | "warning" | "error";
370
+ children: React.ReactNode;
371
+ }
372
+
373
+ const Badge = ({ variant = "info", children, ...props }: BadgeProps) => {
374
+ const variantStyles = {
375
+ info: {
376
+ backgroundColor: "#d1ecf1",
377
+ color: "#0c5460",
378
+ },
379
+ success: {
380
+ backgroundColor: "#d4edda",
381
+ color: "#155724",
382
+ },
383
+ warning: {
384
+ backgroundColor: "#fff3cd",
385
+ color: "#856404",
386
+ },
387
+ error: {
388
+ backgroundColor: "#f8d7da",
389
+ color: "#721c24",
390
+ },
391
+ };
392
+
393
+ return (
394
+ <UI
395
+ as="span"
396
+ defaultStyles={{
397
+ display: "inline-block",
398
+ padding: "0.25rem 0.5rem",
399
+ fontSize: "0.75rem",
400
+ fontWeight: "bold",
401
+ borderRadius: "0.25rem",
402
+ ...variantStyles[variant],
403
+ }}
404
+ {...props}
405
+ >
406
+ {children}
407
+ </UI>
408
+ );
409
+ };
410
+
411
+ return (
412
+ <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
413
+ <Badge variant="info">Info</Badge>
414
+ <Badge variant="success">Success</Badge>
415
+ <Badge variant="warning">Warning</Badge>
416
+ <Badge variant="error">Error</Badge>
417
+ </div>
418
+ );
419
+ },
420
+ parameters: {
421
+ docs: {
422
+ description: {
423
+ story:
424
+ "This shows how to build a Badge component with variants using UI as the primitive.",
425
+ },
426
+ },
427
+ },
428
+ };
429
+
430
+ /**
431
+ * Demonstrates TypeScript type safety - element-specific props are correctly typed.
432
+ */
433
+ export const TypeSafeProps: Story = {
434
+ render: () => {
435
+ return (
436
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
437
+ {/* Button with disabled prop (only valid for buttons) */}
438
+ <UI
439
+ as="button"
440
+ disabled
441
+ styles={{
442
+ padding: "0.5rem 1rem",
443
+ backgroundColor: "#6c757d",
444
+ color: "white",
445
+ border: "none",
446
+ borderRadius: "0.25rem",
447
+ cursor: "not-allowed",
448
+ opacity: 0.6,
449
+ }}
450
+ >
451
+ Disabled Button
452
+ </UI>
453
+
454
+ {/* Anchor with href and target (only valid for anchors) */}
455
+ <UI
456
+ as="a"
457
+ href="https://github.com"
458
+ target="_blank"
459
+ rel="noopener noreferrer"
460
+ styles={{
461
+ color: "#007bff",
462
+ textDecoration: "none",
463
+ padding: "0.5rem",
464
+ }}
465
+ >
466
+ GitHub Link
467
+ </UI>
468
+
469
+ {/* Form with onSubmit (only valid for forms) */}
470
+ <UI
471
+ as="form"
472
+ onSubmit={(e: React.FormEvent) => {
473
+ e.preventDefault();
474
+ alert("Form submitted!");
475
+ }}
476
+ styles={{
477
+ padding: "1rem",
478
+ border: "1px solid #dee2e6",
479
+ borderRadius: "0.25rem",
480
+ }}
481
+ >
482
+ <button type="submit">Submit Form</button>
483
+ </UI>
484
+ </div>
485
+ );
486
+ },
487
+ parameters: {
488
+ docs: {
489
+ description: {
490
+ story:
491
+ "TypeScript ensures that only valid props for each element type are accepted. Try changing the `as` prop to see IntelliSense update!",
492
+ },
493
+ },
494
+ },
495
+ };
496
+
497
+ /**
498
+ * Demonstrates accessible interactive elements with proper ARIA attributes.
499
+ * All examples pass WCAG 2.1 AA accessibility checks.
500
+ */
501
+ export const AccessibleInteractiveElements: Story = {
502
+ render: function AccessibleInteractiveElementsStory() {
503
+ const [isExpanded, setIsExpanded] = React.useState(false);
504
+
505
+ return (
506
+ <div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
507
+ {/* Accessible button with aria-label for icon-only button */}
508
+ <div>
509
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
510
+ Icon Button with aria-label
511
+ </h4>
512
+ <UI
513
+ as="button"
514
+ aria-label="Close dialog"
515
+ onClick={() => alert("Dialog closed")}
516
+ styles={{
517
+ padding: "0.5rem",
518
+ backgroundColor: "#dc3545",
519
+ color: "white",
520
+ border: "none",
521
+ borderRadius: "0.25rem",
522
+ cursor: "pointer",
523
+ fontSize: "1.25rem",
524
+ lineHeight: 1,
525
+ }}
526
+ >
527
+ ×
528
+ </UI>
529
+ </div>
530
+
531
+ {/* Accessible link with descriptive text */}
532
+ <div>
533
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
534
+ Accessible Link
535
+ </h4>
536
+ <UI
537
+ as="a"
538
+ href="/products"
539
+ styles={{
540
+ color: "#007bff",
541
+ textDecoration: "underline",
542
+ padding: "0.5rem",
543
+ display: "inline-block",
544
+ }}
545
+ >
546
+ View all products
547
+ </UI>
548
+ </div>
549
+
550
+ {/* Toggle button with aria-expanded */}
551
+ <div>
552
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
553
+ Expandable Section
554
+ </h4>
555
+ <UI
556
+ as="button"
557
+ aria-expanded={isExpanded}
558
+ aria-controls="expandable-content"
559
+ onClick={() => setIsExpanded(!isExpanded)}
560
+ styles={{
561
+ padding: "0.75rem 1rem",
562
+ backgroundColor: "#007bff",
563
+ color: "white",
564
+ border: "none",
565
+ borderRadius: "0.25rem",
566
+ cursor: "pointer",
567
+ width: "100%",
568
+ textAlign: "left",
569
+ }}
570
+ >
571
+ {isExpanded ? "▼" : "▶"} Toggle Content
572
+ </UI>
573
+ {isExpanded && (
574
+ <UI
575
+ id="expandable-content"
576
+ styles={{
577
+ padding: "1rem",
578
+ backgroundColor: "#f8f9fa",
579
+ marginTop: "0.5rem",
580
+ borderRadius: "0.25rem",
581
+ }}
582
+ >
583
+ This content is now visible and announced to screen readers.
584
+ </UI>
585
+ )}
586
+ </div>
587
+
588
+ {/* Custom interactive element with proper role and keyboard support */}
589
+ <div>
590
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
591
+ Custom Interactive (div with role="button")
592
+ </h4>
593
+ <UI
594
+ as="div"
595
+ role="button"
596
+ tabIndex={0}
597
+ aria-label="Custom toggle"
598
+ onClick={() => alert("Clicked!")}
599
+ onKeyDown={(e: React.KeyboardEvent) => {
600
+ if (e.key === "Enter" || e.key === " ") {
601
+ e.preventDefault();
602
+ alert("Activated via keyboard!");
603
+ }
604
+ }}
605
+ styles={{
606
+ padding: "0.75rem 1rem",
607
+ backgroundColor: "#28a745",
608
+ color: "white",
609
+ borderRadius: "0.25rem",
610
+ cursor: "pointer",
611
+ userSelect: "none",
612
+ display: "inline-block",
613
+ }}
614
+ >
615
+ Press Enter or Space
616
+ </UI>
617
+ </div>
618
+ </div>
619
+ );
620
+ },
621
+ parameters: {
622
+ docs: {
623
+ description: {
624
+ story:
625
+ "Examples of accessible interactive elements using proper ARIA attributes, semantic HTML, and keyboard support. Run the Storybook a11y addon to verify these pass accessibility checks.",
626
+ },
627
+ },
628
+ },
629
+ tags: ["a11y"],
630
+ };
631
+
632
+ /**
633
+ * Demonstrates accessibility patterns including focus management and ARIA attributes.
634
+ */
635
+ export const AccessibilityPatterns: Story = {
636
+ render: function AccessibilityPatternsStory() {
637
+ const buttonRef = useRef<HTMLButtonElement>(null);
638
+ const [count, setCount] = React.useState(0);
639
+
640
+ useEffect(() => {
641
+ // Auto-focus on mount for keyboard navigation
642
+ buttonRef.current?.focus();
643
+ }, []);
644
+
645
+ return (
646
+ <div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
647
+ {/* Focus management example */}
648
+ <div>
649
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
650
+ Auto-focused Button (Focus Management)
651
+ </h4>
652
+ <UI
653
+ as="button"
654
+ ref={buttonRef}
655
+ onClick={() => alert("Focused button clicked")}
656
+ styles={{
657
+ padding: "0.75rem 1.5rem",
658
+ backgroundColor: "#007bff",
659
+ color: "white",
660
+ border: "none",
661
+ borderRadius: "0.25rem",
662
+ cursor: "pointer",
663
+ outline: "2px solid transparent",
664
+ outlineOffset: "2px",
665
+ }}
666
+ // Custom focus indicator with WCAG 2.4.7 compliant contrast
667
+ onFocus={(e: React.FocusEvent<HTMLButtonElement>) => {
668
+ e.currentTarget.style.outline = "2px solid #0056b3";
669
+ }}
670
+ onBlur={(e: React.FocusEvent<HTMLButtonElement>) => {
671
+ e.currentTarget.style.outline = "2px solid transparent";
672
+ }}
673
+ >
674
+ This button auto-focused on mount
675
+ </UI>
676
+ </div>
677
+
678
+ {/* ARIA live region for dynamic content */}
679
+ <div>
680
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
681
+ ARIA Live Region (Dynamic Updates)
682
+ </h4>
683
+ <UI
684
+ as="button"
685
+ onClick={() => setCount((c) => c + 1)}
686
+ aria-describedby="counter-description"
687
+ styles={{
688
+ padding: "0.75rem 1.5rem",
689
+ backgroundColor: "#28a745",
690
+ color: "white",
691
+ border: "none",
692
+ borderRadius: "0.25rem",
693
+ cursor: "pointer",
694
+ }}
695
+ >
696
+ Increment Counter
697
+ </UI>
698
+ <UI
699
+ id="counter-description"
700
+ role="status"
701
+ aria-live="polite"
702
+ aria-atomic="true"
703
+ styles={{
704
+ marginTop: "0.5rem",
705
+ padding: "0.75rem",
706
+ backgroundColor: "#d4edda",
707
+ borderRadius: "0.25rem",
708
+ }}
709
+ >
710
+ Current count: {count}
711
+ </UI>
712
+ </div>
713
+
714
+ {/* Semantic vs generic elements */}
715
+ <div>
716
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
717
+ Semantic HTML Choice
718
+ </h4>
719
+ <div style={{ display: "flex", gap: "1rem" }}>
720
+ <UI
721
+ as="button"
722
+ type="button"
723
+ styles={{
724
+ padding: "0.5rem 1rem",
725
+ backgroundColor: "#007bff",
726
+ color: "white",
727
+ border: "none",
728
+ borderRadius: "0.25rem",
729
+ cursor: "pointer",
730
+ }}
731
+ >
732
+ ✅ Semantic &lt;button&gt;
733
+ </UI>
734
+ <UI
735
+ as="nav"
736
+ aria-label="Secondary navigation"
737
+ styles={{
738
+ padding: "0.5rem 1rem",
739
+ backgroundColor: "#6f42c1",
740
+ color: "white",
741
+ borderRadius: "0.25rem",
742
+ }}
743
+ >
744
+ ✅ Semantic &lt;nav&gt;
745
+ </UI>
746
+ </div>
747
+ </div>
748
+ </div>
749
+ );
750
+ },
751
+ parameters: {
752
+ docs: {
753
+ description: {
754
+ story:
755
+ "Demonstrates accessibility patterns including focus management, ARIA live regions for dynamic content, custom focus indicators, and semantic HTML usage.",
756
+ },
757
+ },
758
+ },
759
+ tags: ["a11y"],
760
+ };
761
+
762
+ /**
763
+ * ⚠️ Shows common accessibility mistakes to avoid.
764
+ * These examples intentionally violate accessibility guidelines to demonstrate what NOT to do.
765
+ */
766
+ export const CommonAccessibilityMistakes: Story = {
767
+ render: () => {
768
+ return (
769
+ <div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
770
+ <div
771
+ style={{
772
+ padding: "1rem",
773
+ backgroundColor: "#fff3cd",
774
+ borderLeft: "4px solid #ffc107",
775
+ marginBottom: "1rem",
776
+ }}
777
+ >
778
+ <strong>⚠️ Warning:</strong> These examples show common accessibility
779
+ violations. Do NOT copy these patterns. They are shown for educational
780
+ purposes only.
781
+ </div>
782
+
783
+ {/* Missing accessible name */}
784
+ <div>
785
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
786
+ ❌ BAD: Icon button without accessible name
787
+ </h4>
788
+ <UI
789
+ as="button"
790
+ onClick={() => {}}
791
+ styles={{
792
+ padding: "0.5rem",
793
+ backgroundColor: "#dc3545",
794
+ color: "white",
795
+ border: "none",
796
+ borderRadius: "0.25rem",
797
+ cursor: "pointer",
798
+ fontSize: "1.25rem",
799
+ }}
800
+ >
801
+ ×
802
+ </UI>
803
+ <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
804
+ <strong>Problem:</strong> Screen readers cannot identify this button's
805
+ purpose. <strong>Fix:</strong> Add <code>aria-label="Close"</code>
806
+ </p>
807
+ </div>
808
+
809
+ {/* Non-semantic clickable div */}
810
+ <div>
811
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
812
+ ❌ BAD: Clickable div without keyboard support
813
+ </h4>
814
+ <UI
815
+ as="div"
816
+ onClick={() => alert("This is not keyboard accessible!")}
817
+ styles={{
818
+ padding: "0.75rem 1rem",
819
+ backgroundColor: "#6c757d",
820
+ color: "white",
821
+ borderRadius: "0.25rem",
822
+ cursor: "pointer",
823
+ display: "inline-block",
824
+ }}
825
+ >
826
+ Click me (but you can't use keyboard!)
827
+ </UI>
828
+ <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
829
+ <strong>Problem:</strong> Not keyboard accessible or announced to screen
830
+ readers. <strong>Fix:</strong> Use <code>as="button"</code> or add{" "}
831
+ <code>role="button"</code>, <code>tabIndex=0</code>, and keyboard handlers.
832
+ </p>
833
+ </div>
834
+
835
+ {/* Poor contrast focus indicator */}
836
+ <div>
837
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
838
+ ❌ BAD: Insufficient focus indicator contrast
839
+ </h4>
840
+ <UI
841
+ as="button"
842
+ styles={{
843
+ padding: "0.75rem 1.5rem",
844
+ backgroundColor: "#007bff",
845
+ color: "white",
846
+ border: "none",
847
+ borderRadius: "0.25rem",
848
+ cursor: "pointer",
849
+ outline: "1px solid #4da3ff",
850
+ }}
851
+ >
852
+ Low contrast focus
853
+ </UI>
854
+ <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
855
+ <strong>Problem:</strong> Focus indicator contrast ratio is less than 3:1
856
+ (WCAG 2.4.7). <strong>Fix:</strong> Use a contrasting color like dark blue
857
+ on light blue background.
858
+ </p>
859
+ </div>
860
+
861
+ {/* Vague link text */}
862
+ <div>
863
+ <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
864
+ ❌ BAD: Non-descriptive link text
865
+ </h4>
866
+ <UI
867
+ as="a"
868
+ href="#"
869
+ styles={{
870
+ color: "#007bff",
871
+ textDecoration: "underline",
872
+ }}
873
+ >
874
+ Click here
875
+ </UI>
876
+ <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
877
+ <strong>Problem:</strong> "Click here" doesn't describe the link's
878
+ destination. <strong>Fix:</strong> Use descriptive text like "View product
879
+ documentation".
880
+ </p>
881
+ </div>
882
+ </div>
883
+ );
884
+ },
885
+ parameters: {
886
+ docs: {
887
+ description: {
888
+ story:
889
+ "⚠️ Educational examples showing common accessibility violations. These patterns should be avoided. Each example includes an explanation of the problem and how to fix it. Run the Storybook a11y addon to see these violations detected automatically.",
890
+ },
891
+ },
892
+ },
893
+ tags: ["a11y"],
894
+ };