@fpkit/acss 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/libs/{chunk-2NRIP6RB.cjs → chunk-2C3YLBWP.cjs} +3 -3
  2. package/libs/{chunk-NWJDAHP6.cjs → chunk-2GJHKWEK.cjs} +3 -3
  3. package/libs/{chunk-FVROL3V5.js → chunk-2JCDEC32.js} +3 -3
  4. package/libs/{chunk-IRLFZ3OL.js → chunk-3XJC4XUG.js} +2 -2
  5. package/libs/{chunk-23ANBDCR.js → chunk-4I5MF54P.js} +3 -3
  6. package/libs/chunk-4I5MF54P.js.map +1 -0
  7. package/libs/chunk-5CJPTDK3.cjs +31 -0
  8. package/libs/chunk-5CJPTDK3.cjs.map +1 -0
  9. package/libs/{chunk-E4OSROCA.cjs → chunk-5QSNJQVH.cjs} +3 -3
  10. package/libs/{chunk-O3JIHC5M.cjs → chunk-6BUJZ4DJ.cjs} +3 -3
  11. package/libs/{chunk-WXBFBWYF.cjs → chunk-AFINOD2L.cjs} +3 -3
  12. package/libs/{chunk-HRRHPLER.js → chunk-AWZLSWDO.js} +2 -2
  13. package/libs/chunk-DDSXKOUB.js +7 -0
  14. package/libs/chunk-DDSXKOUB.js.map +1 -0
  15. package/libs/{chunk-CWRNJA4P.js → chunk-DIJBIOFE.js} +3 -3
  16. package/libs/chunk-EJ6KYBFE.cjs +13 -0
  17. package/libs/chunk-EJ6KYBFE.cjs.map +1 -0
  18. package/libs/{chunk-GUJSMQ3V.cjs → chunk-EKJYOCLY.cjs} +3 -3
  19. package/libs/{chunk-X5RKCLDC.cjs → chunk-F64GE6RG.cjs} +4 -4
  20. package/libs/chunk-FMIM3332.js +8 -0
  21. package/libs/chunk-FMIM3332.js.map +1 -0
  22. package/libs/{chunk-5RAWNUVD.js → chunk-IBUTNPTQ.js} +2 -2
  23. package/libs/chunk-IWP4VJEP.cjs +18 -0
  24. package/libs/chunk-IWP4VJEP.cjs.map +1 -0
  25. package/libs/{chunk-ZFJ4U45S.js → chunk-KDMX3FAW.js} +2 -2
  26. package/libs/{chunk-DYFAUAB7.cjs → chunk-LXODKKA3.cjs} +4 -4
  27. package/libs/chunk-M7JLT62Q.js +9 -0
  28. package/libs/chunk-M7JLT62Q.js.map +1 -0
  29. package/libs/{chunk-IQ76HGVP.js → chunk-MBWI67UT.js} +2 -2
  30. package/libs/{chunk-O5XAJ7BY.cjs → chunk-NCGVF2QS.cjs} +4 -4
  31. package/libs/{chunk-W2UIN7EV.cjs → chunk-NPWHQVYB.cjs} +3 -3
  32. package/libs/{chunk-G55UJ53G.cjs → chunk-NZVSXRTB.cjs} +3 -3
  33. package/libs/chunk-NZVSXRTB.cjs.map +1 -0
  34. package/libs/{chunk-43TK2ICH.js → chunk-PMWL5XZ4.js} +3 -3
  35. package/libs/{chunk-KVKQLRJG.js → chunk-TF3GQKOY.js} +2 -2
  36. package/libs/chunk-TNEJXNZA.cjs +22 -0
  37. package/libs/chunk-TNEJXNZA.cjs.map +1 -0
  38. package/libs/{chunk-IEB64SWY.js → chunk-U5VA34SU.js} +2 -2
  39. package/libs/chunk-UGMP72J2.js +8 -0
  40. package/libs/chunk-UGMP72J2.js.map +1 -0
  41. package/libs/{chunk-MGPWZRBX.cjs → chunk-URBGDUFN.cjs} +6 -6
  42. package/libs/{chunk-QKHPHMG2.js → chunk-ZF6Y7W57.js} +5 -5
  43. package/libs/component-props-50e69975.d.ts +66 -0
  44. package/libs/components/box/box.css +1 -0
  45. package/libs/components/box/box.css.map +1 -0
  46. package/libs/components/box/box.min.css +3 -0
  47. package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
  48. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  49. package/libs/components/button.cjs +4 -4
  50. package/libs/components/button.d.cts +10 -3
  51. package/libs/components/button.d.ts +10 -3
  52. package/libs/components/button.js +2 -2
  53. package/libs/components/card.cjs +7 -7
  54. package/libs/components/card.d.cts +13 -85
  55. package/libs/components/card.d.ts +13 -85
  56. package/libs/components/card.js +2 -2
  57. package/libs/components/cards/card.css +1 -1
  58. package/libs/components/cards/card.css.map +1 -1
  59. package/libs/components/cards/card.min.css +2 -2
  60. package/libs/components/cluster/cluster.css +1 -0
  61. package/libs/components/cluster/cluster.css.map +1 -0
  62. package/libs/components/cluster/cluster.min.css +3 -0
  63. package/libs/components/dialog/dialog.cjs +7 -7
  64. package/libs/components/dialog/dialog.js +5 -5
  65. package/libs/components/form/fields.cjs +4 -4
  66. package/libs/components/form/fields.js +2 -2
  67. package/libs/components/form/textarea.cjs +4 -4
  68. package/libs/components/form/textarea.js +2 -2
  69. package/libs/components/grid/grid.css +1 -0
  70. package/libs/components/grid/grid.css.map +1 -0
  71. package/libs/components/grid/grid.min.css +3 -0
  72. package/libs/components/heading/heading.cjs +3 -3
  73. package/libs/components/heading/heading.js +2 -2
  74. package/libs/components/icons/icon.cjs +4 -4
  75. package/libs/components/icons/icon.d.cts +2 -2
  76. package/libs/components/icons/icon.d.ts +2 -2
  77. package/libs/components/icons/icon.js +2 -2
  78. package/libs/components/link/link.cjs +6 -6
  79. package/libs/components/link/link.js +2 -2
  80. package/libs/components/list/list.cjs +5 -5
  81. package/libs/components/list/list.js +2 -2
  82. package/libs/components/modal.cjs +4 -4
  83. package/libs/components/modal.d.cts +1 -1
  84. package/libs/components/modal.d.ts +1 -1
  85. package/libs/components/modal.js +3 -3
  86. package/libs/components/nav/nav.cjs +7 -7
  87. package/libs/components/nav/nav.js +3 -3
  88. package/libs/components/popover/popover.cjs +4 -4
  89. package/libs/components/popover/popover.d.cts +1 -1
  90. package/libs/components/popover/popover.d.ts +1 -1
  91. package/libs/components/popover/popover.js +1 -1
  92. package/libs/components/stack/stack.css +1 -0
  93. package/libs/components/stack/stack.css.map +1 -0
  94. package/libs/components/stack/stack.min.css +3 -0
  95. package/libs/components/tables/table.cjs +4 -4
  96. package/libs/components/tables/table.d.cts +2 -2
  97. package/libs/components/tables/table.d.ts +2 -2
  98. package/libs/components/tables/table.js +1 -1
  99. package/libs/components/text/text.cjs +5 -5
  100. package/libs/components/text/text.js +2 -2
  101. package/libs/hooks.cjs +4 -4
  102. package/libs/hooks.js +3 -3
  103. package/libs/{icons-287fce3a.d.ts → icons-df8e744f.d.ts} +1 -1
  104. package/libs/icons.cjs +3 -3
  105. package/libs/icons.d.cts +2 -2
  106. package/libs/icons.d.ts +2 -2
  107. package/libs/icons.js +2 -2
  108. package/libs/index.cjs +74 -73
  109. package/libs/index.cjs.map +1 -1
  110. package/libs/index.css +1 -1
  111. package/libs/index.css.map +1 -1
  112. package/libs/index.d.cts +925 -6
  113. package/libs/index.d.ts +925 -6
  114. package/libs/index.js +30 -30
  115. package/libs/index.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/App.tsx +1 -3
  118. package/src/components/alert/STYLES.mdx +790 -0
  119. package/src/components/badge/STYLES.mdx +610 -0
  120. package/src/components/box/README.mdx +401 -0
  121. package/src/components/box/STYLES.mdx +360 -0
  122. package/src/components/box/box.scss +245 -0
  123. package/src/components/box/box.stories.tsx +395 -0
  124. package/src/components/box/box.test.tsx +425 -0
  125. package/src/components/box/box.tsx +170 -0
  126. package/src/components/box/box.types.ts +166 -0
  127. package/src/components/breadcrumbs/STYLES.mdx +99 -0
  128. package/src/components/breadcrumbs/bc-item.tsx +0 -1
  129. package/src/components/buttons/STYLES.mdx +766 -0
  130. package/src/components/cards/STYLES.mdx +835 -0
  131. package/src/components/cards/card.scss +29 -21
  132. package/src/components/cards/card.tsx +13 -3
  133. package/src/components/cards/card.types.ts +13 -0
  134. package/src/components/cluster/README.mdx +595 -0
  135. package/src/components/cluster/STYLES.mdx +626 -0
  136. package/src/components/cluster/cluster.scss +86 -0
  137. package/src/components/cluster/cluster.stories.tsx +385 -0
  138. package/src/components/cluster/cluster.test.tsx +655 -0
  139. package/src/components/cluster/cluster.tsx +94 -0
  140. package/src/components/cluster/cluster.types.ts +75 -0
  141. package/src/components/details/STYLES.mdx +445 -0
  142. package/src/components/dialog/STYLES.mdx +888 -0
  143. package/src/components/flexbox/STYLES.mdx +857 -0
  144. package/src/components/flexbox/flex.stories.tsx +842 -141
  145. package/src/components/flexbox/flex.types.ts +25 -6
  146. package/src/components/form/STYLES.mdx +821 -0
  147. package/src/components/grid/README.mdx +709 -0
  148. package/src/components/grid/STYLES.mdx +785 -0
  149. package/src/components/grid/grid.scss +287 -0
  150. package/src/components/grid/grid.stories.tsx +486 -0
  151. package/src/components/grid/grid.test.tsx +981 -0
  152. package/src/components/grid/grid.tsx +222 -0
  153. package/src/components/grid/grid.types.ts +344 -0
  154. package/src/components/icons/STYLES.mdx +56 -0
  155. package/src/components/icons/components/arrow-right.tsx +0 -5
  156. package/src/components/images/STYLES.mdx +75 -0
  157. package/src/components/kit.tsx +8 -4
  158. package/src/components/layout/STYLES.mdx +556 -0
  159. package/src/components/link/STYLES.mdx +75 -0
  160. package/src/components/list/STYLES.mdx +631 -0
  161. package/src/components/nav/STYLES.mdx +460 -0
  162. package/src/components/popover/popover.tsx +1 -1
  163. package/src/components/progress/STYLES.mdx +64 -0
  164. package/src/components/stack/README.mdx +400 -0
  165. package/src/components/stack/STYLES.mdx +414 -0
  166. package/src/components/stack/stack.scss +109 -0
  167. package/src/components/stack/stack.stories.tsx +559 -0
  168. package/src/components/stack/stack.test.tsx +426 -0
  169. package/src/components/stack/stack.tsx +141 -0
  170. package/src/components/stack/stack.types.ts +133 -0
  171. package/src/components/tables/table-elements.tsx +1 -1
  172. package/src/components/tables/table.tsx +2 -2
  173. package/src/components/tag/STYLES.mdx +105 -0
  174. package/src/components/text-to-speech/STYLES.mdx +80 -0
  175. package/src/components/text-to-speech/TextToSpeech.tsx +0 -4
  176. package/src/components/text-to-speech/useTextToSpeech.tsx +2 -6
  177. package/src/components/ui.tsx +3 -3
  178. package/src/decorators/instructions.tsx +22 -18
  179. package/src/hooks/popover/popover.tsx +1 -1
  180. package/src/index.scss +5 -1
  181. package/src/index.ts +305 -12
  182. package/src/sass/GLOBALS-STYLES.md +631 -0
  183. package/src/sass/_globals.scss +45 -24
  184. package/src/styles/box/box.css +220 -0
  185. package/src/styles/box/box.css.map +1 -0
  186. package/src/styles/cards/card.css +22 -17
  187. package/src/styles/cards/card.css.map +1 -1
  188. package/src/styles/cluster/cluster.css +71 -0
  189. package/src/styles/cluster/cluster.css.map +1 -0
  190. package/src/styles/grid/grid.css +238 -0
  191. package/src/styles/grid/grid.css.map +1 -0
  192. package/src/styles/index.css +667 -49
  193. package/src/styles/index.css.map +1 -1
  194. package/src/styles/stack/stack.css +86 -0
  195. package/src/styles/stack/stack.css.map +1 -0
  196. package/src/types/component-props.ts +42 -13
  197. package/src/types/layout-primitives.ts +48 -0
  198. package/src/types/shared.ts +10 -26
  199. package/libs/chunk-23ANBDCR.js.map +0 -1
  200. package/libs/chunk-5QD3DWFI.js +0 -9
  201. package/libs/chunk-5QD3DWFI.js.map +0 -1
  202. package/libs/chunk-6WTC4JXH.cjs +0 -31
  203. package/libs/chunk-6WTC4JXH.cjs.map +0 -1
  204. package/libs/chunk-ENTCUJ3A.cjs +0 -13
  205. package/libs/chunk-ENTCUJ3A.cjs.map +0 -1
  206. package/libs/chunk-G55UJ53G.cjs.map +0 -1
  207. package/libs/chunk-HHLNOC5T.js +0 -7
  208. package/libs/chunk-HHLNOC5T.js.map +0 -1
  209. package/libs/chunk-KK47SYZI.js +0 -8
  210. package/libs/chunk-KK47SYZI.js.map +0 -1
  211. package/libs/chunk-US2I5GI7.cjs +0 -22
  212. package/libs/chunk-US2I5GI7.cjs.map +0 -1
  213. package/libs/chunk-W5TKWBFC.cjs +0 -18
  214. package/libs/chunk-W5TKWBFC.cjs.map +0 -1
  215. package/libs/chunk-Y2PFDELK.js +0 -8
  216. package/libs/chunk-Y2PFDELK.js.map +0 -1
  217. package/libs/component-props-67d978a2.d.ts +0 -38
  218. /package/libs/{chunk-2NRIP6RB.cjs.map → chunk-2C3YLBWP.cjs.map} +0 -0
  219. /package/libs/{chunk-NWJDAHP6.cjs.map → chunk-2GJHKWEK.cjs.map} +0 -0
  220. /package/libs/{chunk-FVROL3V5.js.map → chunk-2JCDEC32.js.map} +0 -0
  221. /package/libs/{chunk-IRLFZ3OL.js.map → chunk-3XJC4XUG.js.map} +0 -0
  222. /package/libs/{chunk-E4OSROCA.cjs.map → chunk-5QSNJQVH.cjs.map} +0 -0
  223. /package/libs/{chunk-O3JIHC5M.cjs.map → chunk-6BUJZ4DJ.cjs.map} +0 -0
  224. /package/libs/{chunk-WXBFBWYF.cjs.map → chunk-AFINOD2L.cjs.map} +0 -0
  225. /package/libs/{chunk-HRRHPLER.js.map → chunk-AWZLSWDO.js.map} +0 -0
  226. /package/libs/{chunk-CWRNJA4P.js.map → chunk-DIJBIOFE.js.map} +0 -0
  227. /package/libs/{chunk-GUJSMQ3V.cjs.map → chunk-EKJYOCLY.cjs.map} +0 -0
  228. /package/libs/{chunk-X5RKCLDC.cjs.map → chunk-F64GE6RG.cjs.map} +0 -0
  229. /package/libs/{chunk-5RAWNUVD.js.map → chunk-IBUTNPTQ.js.map} +0 -0
  230. /package/libs/{chunk-ZFJ4U45S.js.map → chunk-KDMX3FAW.js.map} +0 -0
  231. /package/libs/{chunk-DYFAUAB7.cjs.map → chunk-LXODKKA3.cjs.map} +0 -0
  232. /package/libs/{chunk-IQ76HGVP.js.map → chunk-MBWI67UT.js.map} +0 -0
  233. /package/libs/{chunk-O5XAJ7BY.cjs.map → chunk-NCGVF2QS.cjs.map} +0 -0
  234. /package/libs/{chunk-W2UIN7EV.cjs.map → chunk-NPWHQVYB.cjs.map} +0 -0
  235. /package/libs/{chunk-43TK2ICH.js.map → chunk-PMWL5XZ4.js.map} +0 -0
  236. /package/libs/{chunk-KVKQLRJG.js.map → chunk-TF3GQKOY.js.map} +0 -0
  237. /package/libs/{chunk-IEB64SWY.js.map → chunk-U5VA34SU.js.map} +0 -0
  238. /package/libs/{chunk-MGPWZRBX.cjs.map → chunk-URBGDUFN.cjs.map} +0 -0
  239. /package/libs/{chunk-QKHPHMG2.js.map → chunk-ZF6Y7W57.js.map} +0 -0
@@ -0,0 +1,426 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { Stack } from "./stack";
4
+
5
+ describe("Stack", () => {
6
+ // ============================================================================
7
+ // Rendering Tests
8
+ // ============================================================================
9
+
10
+ it("renders with default props", () => {
11
+ render(<Stack>Content</Stack>);
12
+ expect(screen.getByText("Content")).toBeInTheDocument();
13
+ });
14
+
15
+ it("renders children correctly", () => {
16
+ render(
17
+ <Stack>
18
+ <span>Child 1</span>
19
+ <span>Child 2</span>
20
+ </Stack>
21
+ );
22
+ expect(screen.getByText("Child 1")).toBeInTheDocument();
23
+ expect(screen.getByText("Child 2")).toBeInTheDocument();
24
+ });
25
+
26
+ it("renders as div by default", () => {
27
+ render(<Stack data-testid="stack">Content</Stack>);
28
+ const stack = screen.getByTestId("stack");
29
+ expect(stack.tagName).toBe("DIV");
30
+ });
31
+
32
+ it("always has base stack class", () => {
33
+ render(<Stack data-testid="stack">Content</Stack>);
34
+ const stack = screen.getByTestId("stack");
35
+ expect(stack).toHaveClass("stack");
36
+ });
37
+
38
+ // ============================================================================
39
+ // Polymorphic Rendering Tests
40
+ // ============================================================================
41
+
42
+ it("renders as different elements via as prop", () => {
43
+ render(<Stack as="section" data-testid="stack">Content</Stack>);
44
+ const stack = screen.getByTestId("stack");
45
+ expect(stack.tagName).toBe("SECTION");
46
+ });
47
+
48
+ it("renders as article", () => {
49
+ render(<Stack as="article" data-testid="stack">Content</Stack>);
50
+ const stack = screen.getByTestId("stack");
51
+ expect(stack.tagName).toBe("ARTICLE");
52
+ });
53
+
54
+ it("renders as nav", () => {
55
+ render(<Stack as="nav" data-testid="stack">Content</Stack>);
56
+ const stack = screen.getByTestId("stack");
57
+ expect(stack.tagName).toBe("NAV");
58
+ });
59
+
60
+ it("renders as ul", () => {
61
+ render(<Stack as="ul" data-testid="stack">Content</Stack>);
62
+ const stack = screen.getByTestId("stack");
63
+ expect(stack.tagName).toBe("UL");
64
+ });
65
+
66
+ it("renders as ol", () => {
67
+ render(<Stack as="ol" data-testid="stack">Content</Stack>);
68
+ const stack = screen.getByTestId("stack");
69
+ expect(stack.tagName).toBe("OL");
70
+ });
71
+
72
+ // ============================================================================
73
+ // Direction Class Tests
74
+ // ============================================================================
75
+
76
+ it("applies vertical direction class by default", () => {
77
+ render(<Stack data-testid="stack">Content</Stack>);
78
+ const stack = screen.getByTestId("stack");
79
+ expect(stack).toHaveClass("stack-vertical");
80
+ });
81
+
82
+ it("applies vertical direction class explicitly", () => {
83
+ render(<Stack direction="vertical" data-testid="stack">Content</Stack>);
84
+ const stack = screen.getByTestId("stack");
85
+ expect(stack).toHaveClass("stack-vertical");
86
+ });
87
+
88
+ it("applies horizontal direction class", () => {
89
+ render(<Stack direction="horizontal" data-testid="stack">Content</Stack>);
90
+ const stack = screen.getByTestId("stack");
91
+ expect(stack).toHaveClass("stack-horizontal");
92
+ });
93
+
94
+ // ============================================================================
95
+ // Gap Class Tests
96
+ // ============================================================================
97
+
98
+ it("applies gap-0 class", () => {
99
+ render(<Stack gap="0" data-testid="stack">Content</Stack>);
100
+ const stack = screen.getByTestId("stack");
101
+ expect(stack).toHaveClass("stack-gap-0");
102
+ });
103
+
104
+ it("applies gap-xs class", () => {
105
+ render(<Stack gap="xs" data-testid="stack">Content</Stack>);
106
+ const stack = screen.getByTestId("stack");
107
+ expect(stack).toHaveClass("stack-gap-xs");
108
+ });
109
+
110
+ it("applies gap-sm class", () => {
111
+ render(<Stack gap="sm" data-testid="stack">Content</Stack>);
112
+ const stack = screen.getByTestId("stack");
113
+ expect(stack).toHaveClass("stack-gap-sm");
114
+ });
115
+
116
+ it("applies gap-md class", () => {
117
+ render(<Stack gap="md" data-testid="stack">Content</Stack>);
118
+ const stack = screen.getByTestId("stack");
119
+ expect(stack).toHaveClass("stack-gap-md");
120
+ });
121
+
122
+ it("applies gap-lg class", () => {
123
+ render(<Stack gap="lg" data-testid="stack">Content</Stack>);
124
+ const stack = screen.getByTestId("stack");
125
+ expect(stack).toHaveClass("stack-gap-lg");
126
+ });
127
+
128
+ it("applies gap-xl class", () => {
129
+ render(<Stack gap="xl" data-testid="stack">Content</Stack>);
130
+ const stack = screen.getByTestId("stack");
131
+ expect(stack).toHaveClass("stack-gap-xl");
132
+ });
133
+
134
+ // ============================================================================
135
+ // Align Class Tests (Cross-Axis)
136
+ // ============================================================================
137
+
138
+ it("applies align-start class", () => {
139
+ render(<Stack align="start" data-testid="stack">Content</Stack>);
140
+ const stack = screen.getByTestId("stack");
141
+ expect(stack).toHaveClass("stack-align-start");
142
+ });
143
+
144
+ it("applies align-center class", () => {
145
+ render(<Stack align="center" data-testid="stack">Content</Stack>);
146
+ const stack = screen.getByTestId("stack");
147
+ expect(stack).toHaveClass("stack-align-center");
148
+ });
149
+
150
+ it("applies align-end class", () => {
151
+ render(<Stack align="end" data-testid="stack">Content</Stack>);
152
+ const stack = screen.getByTestId("stack");
153
+ expect(stack).toHaveClass("stack-align-end");
154
+ });
155
+
156
+ it("applies align-stretch class", () => {
157
+ render(<Stack align="stretch" data-testid="stack">Content</Stack>);
158
+ const stack = screen.getByTestId("stack");
159
+ expect(stack).toHaveClass("stack-align-stretch");
160
+ });
161
+
162
+ // ============================================================================
163
+ // Justify Class Tests (Main-Axis)
164
+ // ============================================================================
165
+
166
+ it("applies justify-start class", () => {
167
+ render(<Stack justify="start" data-testid="stack">Content</Stack>);
168
+ const stack = screen.getByTestId("stack");
169
+ expect(stack).toHaveClass("stack-justify-start");
170
+ });
171
+
172
+ it("applies justify-center class", () => {
173
+ render(<Stack justify="center" data-testid="stack">Content</Stack>);
174
+ const stack = screen.getByTestId("stack");
175
+ expect(stack).toHaveClass("stack-justify-center");
176
+ });
177
+
178
+ it("applies justify-end class", () => {
179
+ render(<Stack justify="end" data-testid="stack">Content</Stack>);
180
+ const stack = screen.getByTestId("stack");
181
+ expect(stack).toHaveClass("stack-justify-end");
182
+ });
183
+
184
+ it("applies justify-between class", () => {
185
+ render(<Stack justify="between" data-testid="stack">Content</Stack>);
186
+ const stack = screen.getByTestId("stack");
187
+ expect(stack).toHaveClass("stack-justify-between");
188
+ });
189
+
190
+ // ============================================================================
191
+ // Wrap Class Tests
192
+ // ============================================================================
193
+
194
+ it("applies wrap class", () => {
195
+ render(<Stack wrap="wrap" data-testid="stack">Content</Stack>);
196
+ const stack = screen.getByTestId("stack");
197
+ expect(stack).toHaveClass("stack-wrap");
198
+ });
199
+
200
+ it("applies nowrap class", () => {
201
+ render(<Stack wrap="nowrap" data-testid="stack">Content</Stack>);
202
+ const stack = screen.getByTestId("stack");
203
+ expect(stack).toHaveClass("stack-nowrap");
204
+ });
205
+
206
+ // ============================================================================
207
+ // Multiple Props Tests
208
+ // ============================================================================
209
+
210
+ it("applies multiple utility classes", () => {
211
+ render(
212
+ <Stack
213
+ direction="horizontal"
214
+ gap="lg"
215
+ align="center"
216
+ justify="between"
217
+ wrap="wrap"
218
+ data-testid="stack"
219
+ >
220
+ Content
221
+ </Stack>
222
+ );
223
+ const stack = screen.getByTestId("stack");
224
+ expect(stack).toHaveClass("stack");
225
+ expect(stack).toHaveClass("stack-horizontal");
226
+ expect(stack).toHaveClass("stack-gap-lg");
227
+ expect(stack).toHaveClass("stack-align-center");
228
+ expect(stack).toHaveClass("stack-justify-between");
229
+ expect(stack).toHaveClass("stack-wrap");
230
+ });
231
+
232
+ it("combines vertical direction with other props", () => {
233
+ render(
234
+ <Stack
235
+ direction="vertical"
236
+ gap="md"
237
+ align="start"
238
+ justify="center"
239
+ data-testid="stack"
240
+ >
241
+ Content
242
+ </Stack>
243
+ );
244
+ const stack = screen.getByTestId("stack");
245
+ expect(stack).toHaveClass("stack-vertical");
246
+ expect(stack).toHaveClass("stack-gap-md");
247
+ expect(stack).toHaveClass("stack-align-start");
248
+ expect(stack).toHaveClass("stack-justify-center");
249
+ });
250
+
251
+ // ============================================================================
252
+ // Custom Class Tests
253
+ // ============================================================================
254
+
255
+ it("merges custom className with utility classes", () => {
256
+ render(
257
+ <Stack gap="md" className="custom-class" data-testid="stack">
258
+ Content
259
+ </Stack>
260
+ );
261
+ const stack = screen.getByTestId("stack");
262
+ expect(stack).toHaveClass("stack");
263
+ expect(stack).toHaveClass("stack-vertical");
264
+ expect(stack).toHaveClass("stack-gap-md");
265
+ expect(stack).toHaveClass("custom-class");
266
+ });
267
+
268
+ it("merges custom classes prop with utility classes", () => {
269
+ render(
270
+ <Stack gap="md" classes="legacy-class" data-testid="stack">
271
+ Content
272
+ </Stack>
273
+ );
274
+ const stack = screen.getByTestId("stack");
275
+ expect(stack).toHaveClass("stack");
276
+ expect(stack).toHaveClass("stack-gap-md");
277
+ expect(stack).toHaveClass("legacy-class");
278
+ });
279
+
280
+ it("merges both className and classes with utility classes", () => {
281
+ render(
282
+ <Stack
283
+ gap="md"
284
+ className="custom-class"
285
+ classes="legacy-class"
286
+ data-testid="stack"
287
+ >
288
+ Content
289
+ </Stack>
290
+ );
291
+ const stack = screen.getByTestId("stack");
292
+ expect(stack).toHaveClass("stack");
293
+ expect(stack).toHaveClass("stack-gap-md");
294
+ expect(stack).toHaveClass("custom-class");
295
+ expect(stack).toHaveClass("legacy-class");
296
+ });
297
+
298
+ // ============================================================================
299
+ // Inline Styles Tests
300
+ // ============================================================================
301
+
302
+ it("applies custom styles", () => {
303
+ render(
304
+ <Stack
305
+ gap="md"
306
+ styles={{ backgroundColor: "red", color: "white" }}
307
+ data-testid="stack"
308
+ >
309
+ Content
310
+ </Stack>
311
+ );
312
+ const stack = screen.getByTestId("stack");
313
+ expect(stack).toHaveStyle({ backgroundColor: "rgb(255, 0, 0)" });
314
+ expect(stack).toHaveStyle({ color: "rgb(255, 255, 255)" });
315
+ });
316
+
317
+ it("accepts CSS custom properties in styles", () => {
318
+ render(
319
+ <Stack
320
+ gap="md"
321
+ styles={{ "--spacing-md": "2rem" } as React.CSSProperties}
322
+ data-testid="stack"
323
+ >
324
+ Content
325
+ </Stack>
326
+ );
327
+ const stack = screen.getByTestId("stack");
328
+ expect(stack).toHaveAttribute("style");
329
+ });
330
+
331
+ // ============================================================================
332
+ // Ref Forwarding Tests
333
+ // ============================================================================
334
+
335
+ it("forwards ref correctly", () => {
336
+ const ref = React.createRef<HTMLDivElement>();
337
+ render(
338
+ <Stack ref={ref} data-testid="stack">
339
+ Content
340
+ </Stack>
341
+ );
342
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
343
+ });
344
+
345
+ it("forwards ref with polymorphic as prop", () => {
346
+ const ref = React.createRef<HTMLElement>();
347
+ render(
348
+ <Stack as="section" ref={ref} data-testid="stack">
349
+ Content
350
+ </Stack>
351
+ );
352
+ expect(ref.current).toBeInstanceOf(HTMLElement);
353
+ expect(ref.current?.tagName).toBe("SECTION");
354
+ });
355
+
356
+ // ============================================================================
357
+ // Accessibility Tests
358
+ // ============================================================================
359
+
360
+ it("forwards ARIA attributes", () => {
361
+ render(
362
+ <Stack aria-label="Test Stack" role="region" data-testid="stack">
363
+ Content
364
+ </Stack>
365
+ );
366
+ const stack = screen.getByTestId("stack");
367
+ expect(stack).toHaveAttribute("aria-label", "Test Stack");
368
+ expect(stack).toHaveAttribute("role", "region");
369
+ });
370
+
371
+ it("forwards data attributes", () => {
372
+ render(
373
+ <Stack data-testid="stack" data-custom="value">
374
+ Content
375
+ </Stack>
376
+ );
377
+ const stack = screen.getByTestId("stack");
378
+ expect(stack).toHaveAttribute("data-custom", "value");
379
+ });
380
+
381
+ // ============================================================================
382
+ // Edge Cases
383
+ // ============================================================================
384
+
385
+ it("handles empty children", () => {
386
+ render(<Stack data-testid="stack" />);
387
+ const stack = screen.getByTestId("stack");
388
+ expect(stack).toBeInTheDocument();
389
+ expect(stack).toBeEmptyDOMElement();
390
+ });
391
+
392
+ it("handles no optional props gracefully", () => {
393
+ render(<Stack data-testid="stack">Content</Stack>);
394
+ const stack = screen.getByTestId("stack");
395
+ expect(stack).toBeInTheDocument();
396
+ expect(stack).toHaveClass("stack");
397
+ expect(stack).toHaveClass("stack-vertical");
398
+ expect(stack.tagName).toBe("DIV");
399
+ });
400
+
401
+ it("handles only direction prop", () => {
402
+ render(<Stack direction="horizontal" data-testid="stack">Content</Stack>);
403
+ const stack = screen.getByTestId("stack");
404
+ expect(stack).toHaveClass("stack");
405
+ expect(stack).toHaveClass("stack-horizontal");
406
+ // Should not have gap, align, justify, or wrap classes when not provided
407
+ expect(stack.className.split(" ").length).toBe(2); // only "stack" and "stack-horizontal"
408
+ });
409
+
410
+ // ============================================================================
411
+ // Layout Behavior Tests
412
+ // ============================================================================
413
+
414
+ it("creates vertical flex container by default", () => {
415
+ render(<Stack data-testid="stack">Content</Stack>);
416
+ const stack = screen.getByTestId("stack");
417
+ // Base stack class ensures display: flex is applied
418
+ expect(stack).toHaveClass("stack");
419
+ });
420
+
421
+ it("creates horizontal flex container when direction is horizontal", () => {
422
+ render(<Stack direction="horizontal" data-testid="stack">Content</Stack>);
423
+ const stack = screen.getByTestId("stack");
424
+ expect(stack).toHaveClass("stack-horizontal");
425
+ });
426
+ });
@@ -0,0 +1,141 @@
1
+ import React from "react";
2
+ import UI from "../ui";
3
+ import type { StackProps } from "./stack.types";
4
+
5
+ // Re-export types for convenience
6
+ export type { StackProps } from "./stack.types";
7
+
8
+ /**
9
+ * Stack - A simplified layout primitive for vertical or horizontal spacing between children.
10
+ *
11
+ * The Stack component provides an easy-to-use flexbox-based layout for creating vertical or
12
+ * horizontal arrangements with consistent gap spacing. It's designed to be simpler than the
13
+ * full Flex component, focusing on the most common stacking patterns.
14
+ *
15
+ * ## Key Features
16
+ * - **Simple API**: Fewer props than Flex for common use cases
17
+ * - **Fluid Spacing**: Responsive gap using CSS clamp()
18
+ * - **Flexbox-Based**: Reliable cross-browser layout
19
+ * - **Polymorphic**: Render as any semantic HTML element
20
+ * - **Type-Safe**: Full TypeScript support
21
+ *
22
+ * ## Accessibility
23
+ * - Uses semantic HTML elements when appropriate via `as` prop
24
+ * - Supports all ARIA attributes via spread props
25
+ * - Proper focus order maintained (visual order matches DOM order)
26
+ *
27
+ * ## Use Cases
28
+ * - Vertical spacing between content sections
29
+ * - Horizontal button groups
30
+ * - Navigation menus
31
+ * - Form layouts
32
+ * - Centered content (vertical/horizontal)
33
+ *
34
+ * @example
35
+ * // Vertical stack with medium gap (default)
36
+ * <Stack gap="md">
37
+ * <h1>Title</h1>
38
+ * <p>Paragraph 1</p>
39
+ * <p>Paragraph 2</p>
40
+ * </Stack>
41
+ *
42
+ * @example
43
+ * // Horizontal button group
44
+ * <Stack direction="horizontal" gap="sm">
45
+ * <Button>Cancel</Button>
46
+ * <Button variant="primary">Submit</Button>
47
+ * </Stack>
48
+ *
49
+ * @example
50
+ * // Centered vertical stack
51
+ * <Stack
52
+ * gap="lg"
53
+ * align="center"
54
+ * justify="center"
55
+ * style={{ minHeight: '100vh' }}
56
+ * >
57
+ * <Logo />
58
+ * <h1>Welcome</h1>
59
+ * <Button>Get Started</Button>
60
+ * </Stack>
61
+ *
62
+ * @example
63
+ * // Navigation with horizontal layout
64
+ * <Stack
65
+ * as="nav"
66
+ * direction="horizontal"
67
+ * gap="md"
68
+ * justify="between"
69
+ * align="center"
70
+ * >
71
+ * <Logo />
72
+ * <Stack direction="horizontal" gap="sm">
73
+ * <a href="/about">About</a>
74
+ * <a href="/contact">Contact</a>
75
+ * </Stack>
76
+ * </Stack>
77
+ *
78
+ * @see {@link StackProps} for complete props documentation
79
+ */
80
+ export const Stack = React.forwardRef<HTMLElement, StackProps>(
81
+ (
82
+ {
83
+ gap,
84
+ direction = "vertical",
85
+ align,
86
+ justify,
87
+ wrap,
88
+ as = "div",
89
+ className,
90
+ classes,
91
+ children,
92
+ ...props
93
+ },
94
+ ref
95
+ ) => {
96
+ // Build utility classes array based on props
97
+ const utilityClasses: string[] = ["stack"];
98
+
99
+ // Direction utilities (default is vertical/column)
100
+ if (direction === "horizontal") {
101
+ utilityClasses.push("stack-horizontal");
102
+ } else {
103
+ utilityClasses.push("stack-vertical");
104
+ }
105
+
106
+ // Gap utilities
107
+ if (gap) {
108
+ utilityClasses.push(`stack-gap-${gap}`);
109
+ }
110
+
111
+ // Align utilities (cross-axis)
112
+ if (align) {
113
+ utilityClasses.push(`stack-align-${align}`);
114
+ }
115
+
116
+ // Justify utilities (main-axis)
117
+ if (justify) {
118
+ utilityClasses.push(`stack-justify-${justify}`);
119
+ }
120
+
121
+ // Wrap utilities
122
+ if (wrap) {
123
+ utilityClasses.push(`stack-${wrap}`);
124
+ }
125
+
126
+ // Merge all classes: utility classes, className prop, and classes prop
127
+ const allClasses = [...utilityClasses, className, classes]
128
+ .filter(Boolean)
129
+ .join(" ");
130
+
131
+ return (
132
+ <UI as={as} ref={ref} classes={allClasses} {...props}>
133
+ {children}
134
+ </UI>
135
+ );
136
+ }
137
+ );
138
+
139
+ Stack.displayName = "Stack";
140
+
141
+ export default Stack;
@@ -0,0 +1,133 @@
1
+ import type { ComponentProps } from "../../types/component-props";
2
+ import type { SpacingScale, StackElement } from "../../types/layout-primitives";
3
+ import type React from "react";
4
+
5
+ /**
6
+ * Props for the Stack component - a simplified layout primitive for vertical or horizontal spacing.
7
+ *
8
+ * Stack provides an easy-to-use flexbox-based layout for creating vertical or horizontal arrangements
9
+ * with consistent spacing between children. It's simpler than the full Flex component, ideal for
10
+ * common stacking patterns.
11
+ *
12
+ * ## Design Principles
13
+ * - **Simplified API**: Fewer props than Flex for common use cases
14
+ * - **Fluid Spacing**: Uses unified spacing scale with responsive values
15
+ * - **Flexbox-Based**: Built on CSS flexbox for reliable layouts
16
+ * - **Semantic HTML**: Defaults to `div` but supports semantic elements
17
+ *
18
+ * @example
19
+ * // Vertical stack (default)
20
+ * <Stack gap="md">
21
+ * <h1>Title</h1>
22
+ * <p>Paragraph 1</p>
23
+ * <p>Paragraph 2</p>
24
+ * </Stack>
25
+ *
26
+ * @example
27
+ * // Horizontal stack for buttons
28
+ * <Stack direction="horizontal" gap="sm">
29
+ * <Button>Cancel</Button>
30
+ * <Button variant="primary">Submit</Button>
31
+ * </Stack>
32
+ *
33
+ * @example
34
+ * // Centered vertical stack
35
+ * <Stack
36
+ * gap="lg"
37
+ * align="center"
38
+ * justify="center"
39
+ * style={{ minHeight: '100vh' }}
40
+ * >
41
+ * <Logo />
42
+ * <h1>Welcome</h1>
43
+ * </Stack>
44
+ */
45
+ export interface StackProps
46
+ extends Partial<ComponentProps>,
47
+ Omit<React.HTMLAttributes<HTMLElement>, "className"> {
48
+ /**
49
+ * Gap between children.
50
+ * Uses unified spacing scale: '0' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
51
+ * Maps to CSS custom properties (--spacing-*)
52
+ * @default 'md'
53
+ * @example
54
+ * <Stack gap="lg">Content</Stack>
55
+ */
56
+ gap?: SpacingScale;
57
+
58
+ /**
59
+ * Layout direction.
60
+ * - 'vertical': Stack children vertically (column)
61
+ * - 'horizontal': Stack children horizontally (row)
62
+ * @default 'vertical'
63
+ * @example
64
+ * <Stack direction="horizontal" gap="sm">
65
+ * <Button>Cancel</Button>
66
+ * <Button>Submit</Button>
67
+ * </Stack>
68
+ */
69
+ direction?: "vertical" | "horizontal";
70
+
71
+ /**
72
+ * Alignment on cross axis.
73
+ * - 'start': Align items to start (left in horizontal, top in vertical)
74
+ * - 'center': Center items
75
+ * - 'end': Align items to end (right in horizontal, bottom in vertical)
76
+ * - 'stretch': Stretch items to fill cross axis
77
+ * @default 'stretch'
78
+ * @example
79
+ * <Stack align="center">Centered items</Stack>
80
+ */
81
+ align?: "start" | "center" | "end" | "stretch";
82
+
83
+ /**
84
+ * Alignment on main axis.
85
+ * - 'start': Items at start
86
+ * - 'center': Items centered
87
+ * - 'end': Items at end
88
+ * - 'between': Space between items
89
+ * @example
90
+ * <Stack justify="between">
91
+ * <Header />
92
+ * <Footer />
93
+ * </Stack>
94
+ */
95
+ justify?: "start" | "center" | "end" | "between";
96
+
97
+ /**
98
+ * Allow items to wrap to next line/column.
99
+ * - 'wrap': Items wrap when they exceed container
100
+ * - 'nowrap': Items stay on single line/column (default)
101
+ * @default 'nowrap'
102
+ * @example
103
+ * <Stack direction="horizontal" wrap="wrap">
104
+ * {items.map(item => <Item key={item.id} />)}
105
+ * </Stack>
106
+ */
107
+ wrap?: "wrap" | "nowrap";
108
+
109
+ /**
110
+ * Polymorphic element type to render.
111
+ * Defaults to 'div' but supports semantic HTML elements.
112
+ * @default 'div'
113
+ * @example
114
+ * <Stack as="nav" direction="horizontal" gap="md">
115
+ * <a href="/">Home</a>
116
+ * <a href="/about">About</a>
117
+ * </Stack>
118
+ */
119
+ as?: StackElement;
120
+
121
+ /**
122
+ * Additional CSS classes to apply.
123
+ * Merged with generated utility classes.
124
+ * @example
125
+ * <Stack className="custom-stack" gap="md">Content</Stack>
126
+ */
127
+ className?: string;
128
+
129
+ /**
130
+ * Children elements to render inside the Stack.
131
+ */
132
+ children?: React.ReactNode;
133
+ }