@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,655 @@
1
+ import React from "react";
2
+ import { describe, it, expect } from "vitest";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { Cluster } from "./cluster";
5
+ import type { ClusterProps } from "./cluster.types";
6
+
7
+ describe("Cluster Component", () => {
8
+ describe("Rendering", () => {
9
+ it("should render with default props", () => {
10
+ render(<Cluster data-testid="cluster">Content</Cluster>);
11
+ const cluster = screen.getByTestId("cluster");
12
+ expect(cluster).toBeInTheDocument();
13
+ expect(cluster.tagName).toBe("DIV");
14
+ expect(cluster).toHaveClass("cluster");
15
+ });
16
+
17
+ it("should render children correctly", () => {
18
+ render(
19
+ <Cluster>
20
+ <span>Tag 1</span>
21
+ <span>Tag 2</span>
22
+ <span>Tag 3</span>
23
+ </Cluster>
24
+ );
25
+ expect(screen.getByText("Tag 1")).toBeInTheDocument();
26
+ expect(screen.getByText("Tag 2")).toBeInTheDocument();
27
+ expect(screen.getByText("Tag 3")).toBeInTheDocument();
28
+ });
29
+
30
+ it("should render with text content", () => {
31
+ render(<Cluster>Plain text content</Cluster>);
32
+ expect(screen.getByText("Plain text content")).toBeInTheDocument();
33
+ });
34
+
35
+ it("should render with multiple children types", () => {
36
+ render(
37
+ <Cluster>
38
+ <span>Span</span>
39
+ <button type="button">Button</button>
40
+ <a href="#test">Link</a>
41
+ </Cluster>
42
+ );
43
+ expect(screen.getByText("Span")).toBeInTheDocument();
44
+ expect(screen.getByText("Button")).toBeInTheDocument();
45
+ expect(screen.getByText("Link")).toBeInTheDocument();
46
+ });
47
+ });
48
+
49
+ describe("Polymorphic Rendering (as prop)", () => {
50
+ it("should render as div by default", () => {
51
+ render(<Cluster data-testid="cluster">Content</Cluster>);
52
+ expect(screen.getByTestId("cluster").tagName).toBe("DIV");
53
+ });
54
+
55
+ it("should render as ul when as='ul'", () => {
56
+ render(
57
+ <Cluster as="ul" data-testid="cluster">
58
+ <li>Item 1</li>
59
+ <li>Item 2</li>
60
+ </Cluster>
61
+ );
62
+ expect(screen.getByTestId("cluster").tagName).toBe("UL");
63
+ });
64
+
65
+ it("should render as ol when as='ol'", () => {
66
+ render(
67
+ <Cluster as="ol" data-testid="cluster">
68
+ <li>Item 1</li>
69
+ <li>Item 2</li>
70
+ </Cluster>
71
+ );
72
+ expect(screen.getByTestId("cluster").tagName).toBe("OL");
73
+ });
74
+
75
+ it("should render as nav when as='nav'", () => {
76
+ render(
77
+ <Cluster as="nav" data-testid="cluster">
78
+ <a href="#1">Link 1</a>
79
+ <a href="#2">Link 2</a>
80
+ </Cluster>
81
+ );
82
+ expect(screen.getByTestId("cluster").tagName).toBe("NAV");
83
+ });
84
+
85
+ it("should render as section when as='section'", () => {
86
+ render(
87
+ <Cluster as="section" data-testid="cluster">
88
+ Content
89
+ </Cluster>
90
+ );
91
+ expect(screen.getByTestId("cluster").tagName).toBe("SECTION");
92
+ });
93
+ });
94
+
95
+ describe("Gap Prop", () => {
96
+ it("should apply gap='0' class", () => {
97
+ render(
98
+ <Cluster gap="0" data-testid="cluster">
99
+ Content
100
+ </Cluster>
101
+ );
102
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-0");
103
+ });
104
+
105
+ it("should apply gap='xs' class", () => {
106
+ render(
107
+ <Cluster gap="xs" data-testid="cluster">
108
+ Content
109
+ </Cluster>
110
+ );
111
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xs");
112
+ });
113
+
114
+ it("should apply gap='sm' class", () => {
115
+ render(
116
+ <Cluster gap="sm" data-testid="cluster">
117
+ Content
118
+ </Cluster>
119
+ );
120
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-sm");
121
+ });
122
+
123
+ it("should apply gap='md' class", () => {
124
+ render(
125
+ <Cluster gap="md" data-testid="cluster">
126
+ Content
127
+ </Cluster>
128
+ );
129
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-md");
130
+ });
131
+
132
+ it("should apply gap='lg' class", () => {
133
+ render(
134
+ <Cluster gap="lg" data-testid="cluster">
135
+ Content
136
+ </Cluster>
137
+ );
138
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-lg");
139
+ });
140
+
141
+ it("should apply gap='xl' class", () => {
142
+ render(
143
+ <Cluster gap="xl" data-testid="cluster">
144
+ Content
145
+ </Cluster>
146
+ );
147
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xl");
148
+ });
149
+
150
+ it("should work without gap prop", () => {
151
+ render(<Cluster data-testid="cluster">Content</Cluster>);
152
+ const cluster = screen.getByTestId("cluster");
153
+ expect(cluster).toHaveClass("cluster");
154
+ expect(cluster).not.toHaveClass("cluster-gap-0");
155
+ expect(cluster).not.toHaveClass("cluster-gap-xs");
156
+ expect(cluster).not.toHaveClass("cluster-gap-sm");
157
+ });
158
+ });
159
+
160
+ describe("Justify Prop (Horizontal Alignment)", () => {
161
+ it("should apply justify='start' class", () => {
162
+ render(
163
+ <Cluster justify="start" data-testid="cluster">
164
+ Content
165
+ </Cluster>
166
+ );
167
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-start");
168
+ });
169
+
170
+ it("should apply justify='center' class", () => {
171
+ render(
172
+ <Cluster justify="center" data-testid="cluster">
173
+ Content
174
+ </Cluster>
175
+ );
176
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-center");
177
+ });
178
+
179
+ it("should apply justify='end' class", () => {
180
+ render(
181
+ <Cluster justify="end" data-testid="cluster">
182
+ Content
183
+ </Cluster>
184
+ );
185
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-end");
186
+ });
187
+
188
+ it("should apply justify='between' class", () => {
189
+ render(
190
+ <Cluster justify="between" data-testid="cluster">
191
+ Content
192
+ </Cluster>
193
+ );
194
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-between");
195
+ });
196
+
197
+ it("should work without justify prop", () => {
198
+ render(<Cluster data-testid="cluster">Content</Cluster>);
199
+ const cluster = screen.getByTestId("cluster");
200
+ expect(cluster).not.toHaveClass("cluster-justify-start");
201
+ expect(cluster).not.toHaveClass("cluster-justify-center");
202
+ });
203
+ });
204
+
205
+ describe("Align Prop (Vertical Alignment)", () => {
206
+ it("should apply align='start' class", () => {
207
+ render(
208
+ <Cluster align="start" data-testid="cluster">
209
+ Content
210
+ </Cluster>
211
+ );
212
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-start");
213
+ });
214
+
215
+ it("should apply align='center' class", () => {
216
+ render(
217
+ <Cluster align="center" data-testid="cluster">
218
+ Content
219
+ </Cluster>
220
+ );
221
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-center");
222
+ });
223
+
224
+ it("should apply align='end' class", () => {
225
+ render(
226
+ <Cluster align="end" data-testid="cluster">
227
+ Content
228
+ </Cluster>
229
+ );
230
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-end");
231
+ });
232
+
233
+ it("should apply align='baseline' class", () => {
234
+ render(
235
+ <Cluster align="baseline" data-testid="cluster">
236
+ Content
237
+ </Cluster>
238
+ );
239
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-baseline");
240
+ });
241
+
242
+ it("should work without align prop", () => {
243
+ render(<Cluster data-testid="cluster">Content</Cluster>);
244
+ const cluster = screen.getByTestId("cluster");
245
+ expect(cluster).not.toHaveClass("cluster-align-start");
246
+ expect(cluster).not.toHaveClass("cluster-align-center");
247
+ });
248
+ });
249
+
250
+ describe("Combined Props", () => {
251
+ it("should apply gap and justify together", () => {
252
+ render(
253
+ <Cluster gap="md" justify="center" data-testid="cluster">
254
+ Content
255
+ </Cluster>
256
+ );
257
+ const cluster = screen.getByTestId("cluster");
258
+ expect(cluster).toHaveClass("cluster");
259
+ expect(cluster).toHaveClass("cluster-gap-md");
260
+ expect(cluster).toHaveClass("cluster-justify-center");
261
+ });
262
+
263
+ it("should apply gap and align together", () => {
264
+ render(
265
+ <Cluster gap="lg" align="baseline" data-testid="cluster">
266
+ Content
267
+ </Cluster>
268
+ );
269
+ const cluster = screen.getByTestId("cluster");
270
+ expect(cluster).toHaveClass("cluster");
271
+ expect(cluster).toHaveClass("cluster-gap-lg");
272
+ expect(cluster).toHaveClass("cluster-align-baseline");
273
+ });
274
+
275
+ it("should apply all props together", () => {
276
+ render(
277
+ <Cluster gap="sm" justify="between" align="center" data-testid="cluster">
278
+ Content
279
+ </Cluster>
280
+ );
281
+ const cluster = screen.getByTestId("cluster");
282
+ expect(cluster).toHaveClass("cluster");
283
+ expect(cluster).toHaveClass("cluster-gap-sm");
284
+ expect(cluster).toHaveClass("cluster-justify-between");
285
+ expect(cluster).toHaveClass("cluster-align-center");
286
+ });
287
+
288
+ it("should combine all props with polymorphic as prop", () => {
289
+ render(
290
+ <Cluster as="nav" gap="md" justify="center" align="baseline" data-testid="cluster">
291
+ <a href="#1">Link</a>
292
+ </Cluster>
293
+ );
294
+ const cluster = screen.getByTestId("cluster");
295
+ expect(cluster.tagName).toBe("NAV");
296
+ expect(cluster).toHaveClass("cluster");
297
+ expect(cluster).toHaveClass("cluster-gap-md");
298
+ expect(cluster).toHaveClass("cluster-justify-center");
299
+ expect(cluster).toHaveClass("cluster-align-baseline");
300
+ });
301
+ });
302
+
303
+ describe("ClassName and Classes Props", () => {
304
+ it("should merge className prop with utility classes", () => {
305
+ render(
306
+ <Cluster className="custom-class" data-testid="cluster">
307
+ Content
308
+ </Cluster>
309
+ );
310
+ const cluster = screen.getByTestId("cluster");
311
+ expect(cluster).toHaveClass("cluster");
312
+ expect(cluster).toHaveClass("custom-class");
313
+ });
314
+
315
+ it("should merge classes prop with utility classes", () => {
316
+ render(
317
+ <Cluster classes="another-class" data-testid="cluster">
318
+ Content
319
+ </Cluster>
320
+ );
321
+ const cluster = screen.getByTestId("cluster");
322
+ expect(cluster).toHaveClass("cluster");
323
+ expect(cluster).toHaveClass("another-class");
324
+ });
325
+
326
+ it("should merge both className and classes", () => {
327
+ render(
328
+ <Cluster className="custom" classes="another" data-testid="cluster">
329
+ Content
330
+ </Cluster>
331
+ );
332
+ const cluster = screen.getByTestId("cluster");
333
+ expect(cluster).toHaveClass("cluster");
334
+ expect(cluster).toHaveClass("custom");
335
+ expect(cluster).toHaveClass("another");
336
+ });
337
+
338
+ it("should merge custom classes with all utility classes", () => {
339
+ render(
340
+ <Cluster
341
+ gap="md"
342
+ justify="center"
343
+ align="baseline"
344
+ className="custom"
345
+ classes="utility"
346
+ data-testid="cluster"
347
+ >
348
+ Content
349
+ </Cluster>
350
+ );
351
+ const cluster = screen.getByTestId("cluster");
352
+ expect(cluster).toHaveClass("cluster");
353
+ expect(cluster).toHaveClass("cluster-gap-md");
354
+ expect(cluster).toHaveClass("cluster-justify-center");
355
+ expect(cluster).toHaveClass("cluster-align-baseline");
356
+ expect(cluster).toHaveClass("custom");
357
+ expect(cluster).toHaveClass("utility");
358
+ });
359
+ });
360
+
361
+ describe("Inline Styles", () => {
362
+ it("should apply inline styles via styles prop", () => {
363
+ render(
364
+ <Cluster styles={{ maxWidth: "500px" }} data-testid="cluster">
365
+ Content
366
+ </Cluster>
367
+ );
368
+ const cluster = screen.getByTestId("cluster");
369
+ expect(cluster).toHaveStyle({ maxWidth: "500px" });
370
+ });
371
+
372
+ it("should apply inline styles via style prop", () => {
373
+ render(
374
+ <Cluster style={{ border: "1px solid red" }} data-testid="cluster">
375
+ Content
376
+ </Cluster>
377
+ );
378
+ const cluster = screen.getByTestId("cluster");
379
+ expect(cluster).toHaveStyle({ border: "1px solid red" });
380
+ });
381
+
382
+ it("should apply CSS custom properties", () => {
383
+ render(
384
+ <Cluster styles={{ "--custom-color": "blue" } as React.CSSProperties} data-testid="cluster">
385
+ Content
386
+ </Cluster>
387
+ );
388
+ const cluster = screen.getByTestId("cluster");
389
+ expect(cluster).toHaveStyle({ "--custom-color": "blue" });
390
+ });
391
+ });
392
+
393
+ describe("Ref Forwarding", () => {
394
+ it("should forward ref to the underlying element", () => {
395
+ const ref = { current: null as HTMLDivElement | null };
396
+ render(
397
+ <Cluster ref={ref} data-testid="cluster">
398
+ Content
399
+ </Cluster>
400
+ );
401
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
402
+ expect(ref.current).toBe(screen.getByTestId("cluster"));
403
+ });
404
+
405
+ it("should forward ref with polymorphic as prop", () => {
406
+ const ref = { current: null as HTMLElement | null };
407
+ render(
408
+ <Cluster as="nav" ref={ref} data-testid="cluster">
409
+ Content
410
+ </Cluster>
411
+ );
412
+ expect(ref.current).toBeInstanceOf(HTMLElement);
413
+ expect(ref.current?.tagName).toBe("NAV");
414
+ });
415
+ });
416
+
417
+ describe("ARIA and Accessibility Attributes", () => {
418
+ it("should accept aria-label", () => {
419
+ render(
420
+ <Cluster aria-label="Tag cloud" data-testid="cluster">
421
+ Content
422
+ </Cluster>
423
+ );
424
+ expect(screen.getByTestId("cluster")).toHaveAttribute("aria-label", "Tag cloud");
425
+ });
426
+
427
+ it("should accept aria-labelledby", () => {
428
+ render(
429
+ <>
430
+ <h2 id="cluster-title">Filter Tags</h2>
431
+ <Cluster aria-labelledby="cluster-title" data-testid="cluster">
432
+ <span>Tag 1</span>
433
+ </Cluster>
434
+ </>
435
+ );
436
+ expect(screen.getByTestId("cluster")).toHaveAttribute("aria-labelledby", "cluster-title");
437
+ });
438
+
439
+ it("should accept aria-describedby", () => {
440
+ render(
441
+ <>
442
+ <p id="cluster-desc">Select tags to filter results</p>
443
+ <Cluster aria-describedby="cluster-desc" data-testid="cluster">
444
+ <span>Tag 1</span>
445
+ </Cluster>
446
+ </>
447
+ );
448
+ expect(screen.getByTestId("cluster")).toHaveAttribute("aria-describedby", "cluster-desc");
449
+ });
450
+
451
+ it("should accept role attribute", () => {
452
+ render(
453
+ <Cluster role="group" data-testid="cluster">
454
+ Content
455
+ </Cluster>
456
+ );
457
+ expect(screen.getByTestId("cluster")).toHaveAttribute("role", "group");
458
+ });
459
+
460
+ it("should accept data attributes", () => {
461
+ render(
462
+ <Cluster data-testid="cluster" data-component="tag-cloud">
463
+ Content
464
+ </Cluster>
465
+ );
466
+ expect(screen.getByTestId("cluster")).toHaveAttribute("data-component", "tag-cloud");
467
+ });
468
+
469
+ it("should accept id attribute", () => {
470
+ render(
471
+ <Cluster id="tags-cluster" data-testid="cluster">
472
+ Content
473
+ </Cluster>
474
+ );
475
+ expect(screen.getByTestId("cluster")).toHaveAttribute("id", "tags-cluster");
476
+ });
477
+ });
478
+
479
+ describe("Edge Cases", () => {
480
+ it("should handle empty children", () => {
481
+ render(<Cluster data-testid="cluster" />);
482
+ expect(screen.getByTestId("cluster")).toBeInTheDocument();
483
+ expect(screen.getByTestId("cluster")).toBeEmptyDOMElement();
484
+ });
485
+
486
+ it("should handle null children", () => {
487
+ render(<Cluster data-testid="cluster">{null}</Cluster>);
488
+ expect(screen.getByTestId("cluster")).toBeInTheDocument();
489
+ });
490
+
491
+ it("should handle undefined children", () => {
492
+ render(<Cluster data-testid="cluster">{undefined}</Cluster>);
493
+ expect(screen.getByTestId("cluster")).toBeInTheDocument();
494
+ });
495
+
496
+ it("should handle false children (conditional rendering)", () => {
497
+ const shouldShow = false;
498
+ render(<Cluster data-testid="cluster">{shouldShow && <span>Hidden</span>}</Cluster>);
499
+ expect(screen.getByTestId("cluster")).toBeInTheDocument();
500
+ expect(screen.queryByText("Hidden")).not.toBeInTheDocument();
501
+ });
502
+
503
+ it("should handle fragments", () => {
504
+ render(
505
+ <Cluster>
506
+ <>
507
+ <span>Item 1</span>
508
+ <span>Item 2</span>
509
+ </>
510
+ </Cluster>
511
+ );
512
+ expect(screen.getByText("Item 1")).toBeInTheDocument();
513
+ expect(screen.getByText("Item 2")).toBeInTheDocument();
514
+ });
515
+
516
+ it("should handle mixed content types", () => {
517
+ render(
518
+ <Cluster>
519
+ Text node
520
+ <span>Element</span>
521
+ {null}
522
+ {undefined}
523
+ {false}
524
+ <button type="button">Button</button>
525
+ </Cluster>
526
+ );
527
+ expect(screen.getByText("Text node")).toBeInTheDocument();
528
+ expect(screen.getByText("Element")).toBeInTheDocument();
529
+ expect(screen.getByText("Button")).toBeInTheDocument();
530
+ });
531
+ });
532
+
533
+ describe("Real-World Usage Patterns", () => {
534
+ it("should render tag cloud correctly", () => {
535
+ const tags = ["React", "TypeScript", "CSS", "Accessibility"];
536
+ render(
537
+ <Cluster gap="sm" justify="center" data-testid="cluster">
538
+ {tags.map((tag) => (
539
+ <span key={tag} className="tag">
540
+ {tag}
541
+ </span>
542
+ ))}
543
+ </Cluster>
544
+ );
545
+ const cluster = screen.getByTestId("cluster");
546
+ expect(cluster).toHaveClass("cluster-gap-sm");
547
+ expect(cluster).toHaveClass("cluster-justify-center");
548
+ tags.forEach((tag) => {
549
+ expect(screen.getByText(tag)).toBeInTheDocument();
550
+ });
551
+ });
552
+
553
+ it("should render button group correctly", () => {
554
+ render(
555
+ <Cluster gap="md" data-testid="cluster">
556
+ <button type="button">Action 1</button>
557
+ <button type="button">Action 2</button>
558
+ <button type="button">Action 3</button>
559
+ </Cluster>
560
+ );
561
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-md");
562
+ expect(screen.getByText("Action 1")).toBeInTheDocument();
563
+ expect(screen.getByText("Action 2")).toBeInTheDocument();
564
+ expect(screen.getByText("Action 3")).toBeInTheDocument();
565
+ });
566
+
567
+ it("should render navigation links with baseline alignment", () => {
568
+ render(
569
+ <Cluster as="nav" gap="lg" align="baseline" justify="center" data-testid="cluster">
570
+ <a href="#home">Home</a>
571
+ <a href="#about">About</a>
572
+ <a href="#contact">Contact</a>
573
+ </Cluster>
574
+ );
575
+ const cluster = screen.getByTestId("cluster");
576
+ expect(cluster.tagName).toBe("NAV");
577
+ expect(cluster).toHaveClass("cluster-gap-lg");
578
+ expect(cluster).toHaveClass("cluster-align-baseline");
579
+ expect(cluster).toHaveClass("cluster-justify-center");
580
+ });
581
+
582
+ it("should render filter pills with semantic list", () => {
583
+ render(
584
+ <Cluster as="ul" gap="sm" styles={{ listStyle: "none" }} data-testid="cluster">
585
+ <li>
586
+ <button type="button">All</button>
587
+ </li>
588
+ <li>
589
+ <button type="button">Active</button>
590
+ </li>
591
+ <li>
592
+ <button type="button">Completed</button>
593
+ </li>
594
+ </Cluster>
595
+ );
596
+ const cluster = screen.getByTestId("cluster");
597
+ expect(cluster.tagName).toBe("UL");
598
+ expect(cluster).toHaveClass("cluster-gap-sm");
599
+ });
600
+
601
+ it("should render badge cluster with zero gap", () => {
602
+ render(
603
+ <Cluster gap="xs" data-testid="cluster">
604
+ <span className="badge">Active</span>
605
+ <span className="badge">New</span>
606
+ <span className="badge">Beta</span>
607
+ </Cluster>
608
+ );
609
+ expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xs");
610
+ expect(screen.getByText("Active")).toBeInTheDocument();
611
+ expect(screen.getByText("New")).toBeInTheDocument();
612
+ expect(screen.getByText("Beta")).toBeInTheDocument();
613
+ });
614
+ });
615
+
616
+ describe("TypeScript Type Safety", () => {
617
+ it("should accept valid ClusterProps", () => {
618
+ const props: ClusterProps = {
619
+ gap: "md",
620
+ justify: "center",
621
+ align: "baseline",
622
+ as: "nav",
623
+ className: "custom",
624
+ children: <span>Content</span>,
625
+ };
626
+ render(<Cluster {...props} data-testid="cluster" />);
627
+ expect(screen.getByTestId("cluster")).toBeInTheDocument();
628
+ });
629
+
630
+ it("should accept all SpacingScale values", () => {
631
+ const spacingScales: ClusterProps["gap"][] = ["0", "xs", "sm", "md", "lg", "xl"];
632
+ spacingScales.forEach((gap, index) => {
633
+ render(
634
+ <Cluster gap={gap} data-testid={`cluster-${index}`}>
635
+ Content
636
+ </Cluster>
637
+ );
638
+ expect(screen.getByTestId(`cluster-${index}`)).toHaveClass(`cluster-gap-${gap}`);
639
+ });
640
+ });
641
+
642
+ it("should accept all ClusterElement values", () => {
643
+ const elements: ClusterProps["as"][] = ["div", "ul", "ol", "nav", "section"];
644
+ elements.forEach((element, index) => {
645
+ if (!element) return;
646
+ render(
647
+ <Cluster as={element} data-testid={`cluster-${index}`}>
648
+ Content
649
+ </Cluster>
650
+ );
651
+ expect(screen.getByTestId(`cluster-${index}`).tagName).toBe(element.toUpperCase());
652
+ });
653
+ });
654
+ });
655
+ });