@fpkit/acss 3.1.1 → 3.2.1

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 (204) 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-L6PRDL6F.cjs → chunk-5CJPTDK3.cjs} +3 -3
  6. package/libs/{chunk-E4OSROCA.cjs → chunk-5QSNJQVH.cjs} +3 -3
  7. package/libs/{chunk-O3JIHC5M.cjs → chunk-6BUJZ4DJ.cjs} +3 -3
  8. package/libs/{chunk-WXBFBWYF.cjs → chunk-AFINOD2L.cjs} +3 -3
  9. package/libs/{chunk-HRRHPLER.js → chunk-AWZLSWDO.js} +2 -2
  10. package/libs/chunk-DDSXKOUB.js +7 -0
  11. package/libs/chunk-DDSXKOUB.js.map +1 -0
  12. package/libs/{chunk-CWRNJA4P.js → chunk-DIJBIOFE.js} +3 -3
  13. package/libs/chunk-EJ6KYBFE.cjs +13 -0
  14. package/libs/chunk-EJ6KYBFE.cjs.map +1 -0
  15. package/libs/{chunk-GUJSMQ3V.cjs → chunk-EKJYOCLY.cjs} +3 -3
  16. package/libs/{chunk-X5RKCLDC.cjs → chunk-F64GE6RG.cjs} +4 -4
  17. package/libs/{chunk-5RAWNUVD.js → chunk-IBUTNPTQ.js} +2 -2
  18. package/libs/{chunk-ZFJ4U45S.js → chunk-KDMX3FAW.js} +2 -2
  19. package/libs/{chunk-DYFAUAB7.cjs → chunk-LXODKKA3.cjs} +4 -4
  20. package/libs/{chunk-MPTMPBFT.js → chunk-M7JLT62Q.js} +2 -2
  21. package/libs/{chunk-IQ76HGVP.js → chunk-MBWI67UT.js} +2 -2
  22. package/libs/{chunk-O5XAJ7BY.cjs → chunk-NCGVF2QS.cjs} +4 -4
  23. package/libs/{chunk-W2UIN7EV.cjs → chunk-NPWHQVYB.cjs} +3 -3
  24. package/libs/chunk-OU52NIKA.js +8 -0
  25. package/libs/chunk-OU52NIKA.js.map +1 -0
  26. package/libs/{chunk-43TK2ICH.js → chunk-PMWL5XZ4.js} +3 -3
  27. package/libs/{chunk-KVKQLRJG.js → chunk-TF3GQKOY.js} +2 -2
  28. package/libs/{chunk-IEB64SWY.js → chunk-U5VA34SU.js} +2 -2
  29. package/libs/{chunk-EE3ZWSBY.cjs → chunk-URBGDUFN.cjs} +6 -6
  30. package/libs/chunk-WWPLBWCQ.cjs +18 -0
  31. package/libs/chunk-WWPLBWCQ.cjs.map +1 -0
  32. package/libs/{chunk-TPIB3RQP.js → chunk-ZF6Y7W57.js} +5 -5
  33. package/libs/component-props-50e69975.d.ts +66 -0
  34. package/libs/components/box/box.css +1 -0
  35. package/libs/components/box/box.css.map +1 -0
  36. package/libs/components/box/box.min.css +3 -0
  37. package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
  38. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  39. package/libs/components/button.cjs +4 -4
  40. package/libs/components/button.d.cts +10 -3
  41. package/libs/components/button.d.ts +10 -3
  42. package/libs/components/button.js +2 -2
  43. package/libs/components/card.cjs +7 -7
  44. package/libs/components/card.d.cts +13 -85
  45. package/libs/components/card.d.ts +13 -85
  46. package/libs/components/card.js +2 -2
  47. package/libs/components/cards/card.css +1 -1
  48. package/libs/components/cards/card.css.map +1 -1
  49. package/libs/components/cards/card.min.css +2 -2
  50. package/libs/components/cluster/cluster.css +1 -0
  51. package/libs/components/cluster/cluster.css.map +1 -0
  52. package/libs/components/cluster/cluster.min.css +3 -0
  53. package/libs/components/dialog/dialog.cjs +7 -7
  54. package/libs/components/dialog/dialog.js +5 -5
  55. package/libs/components/form/fields.cjs +4 -4
  56. package/libs/components/form/fields.js +2 -2
  57. package/libs/components/form/textarea.cjs +4 -4
  58. package/libs/components/form/textarea.js +2 -2
  59. package/libs/components/grid/grid.css +1 -0
  60. package/libs/components/grid/grid.css.map +1 -0
  61. package/libs/components/grid/grid.min.css +3 -0
  62. package/libs/components/heading/heading.cjs +3 -3
  63. package/libs/components/heading/heading.js +2 -2
  64. package/libs/components/icons/icon.cjs +4 -4
  65. package/libs/components/icons/icon.d.cts +2 -2
  66. package/libs/components/icons/icon.d.ts +2 -2
  67. package/libs/components/icons/icon.js +2 -2
  68. package/libs/components/link/link.cjs +6 -6
  69. package/libs/components/link/link.js +2 -2
  70. package/libs/components/list/list.cjs +5 -5
  71. package/libs/components/list/list.js +2 -2
  72. package/libs/components/modal.cjs +4 -4
  73. package/libs/components/modal.d.cts +1 -1
  74. package/libs/components/modal.d.ts +1 -1
  75. package/libs/components/modal.js +3 -3
  76. package/libs/components/nav/nav.cjs +7 -7
  77. package/libs/components/nav/nav.js +3 -3
  78. package/libs/components/stack/stack.css +1 -0
  79. package/libs/components/stack/stack.css.map +1 -0
  80. package/libs/components/stack/stack.min.css +3 -0
  81. package/libs/components/tables/table.d.cts +1 -1
  82. package/libs/components/tables/table.d.ts +1 -1
  83. package/libs/components/text/text.cjs +5 -5
  84. package/libs/components/text/text.js +2 -2
  85. package/libs/hooks.cjs +4 -4
  86. package/libs/hooks.js +3 -3
  87. package/libs/{icons-287fce3a.d.ts → icons-df8e744f.d.ts} +1 -1
  88. package/libs/icons.cjs +3 -3
  89. package/libs/icons.d.cts +2 -2
  90. package/libs/icons.d.ts +2 -2
  91. package/libs/icons.js +2 -2
  92. package/libs/index.cjs +64 -63
  93. package/libs/index.cjs.map +1 -1
  94. package/libs/index.css +1 -1
  95. package/libs/index.css.map +1 -1
  96. package/libs/index.d.cts +923 -4
  97. package/libs/index.d.ts +923 -4
  98. package/libs/index.js +28 -28
  99. package/libs/index.js.map +1 -1
  100. package/package.json +2 -2
  101. package/src/components/alert/STYLES.mdx +790 -0
  102. package/src/components/badge/STYLES.mdx +610 -0
  103. package/src/components/box/README.mdx +401 -0
  104. package/src/components/box/STYLES.mdx +360 -0
  105. package/src/components/box/box.scss +245 -0
  106. package/src/components/box/box.stories.tsx +395 -0
  107. package/src/components/box/box.test.tsx +425 -0
  108. package/src/components/box/box.tsx +170 -0
  109. package/src/components/box/box.types.ts +166 -0
  110. package/src/components/breadcrumbs/STYLES.mdx +99 -0
  111. package/src/components/buttons/STYLES.mdx +766 -0
  112. package/src/components/cards/STYLES.mdx +835 -0
  113. package/src/components/cards/card.scss +30 -21
  114. package/src/components/cards/card.stories.tsx +120 -80
  115. package/src/components/cards/card.tsx +14 -4
  116. package/src/components/cards/card.types.ts +13 -0
  117. package/src/components/cluster/README.mdx +595 -0
  118. package/src/components/cluster/STYLES.mdx +626 -0
  119. package/src/components/cluster/cluster.scss +86 -0
  120. package/src/components/cluster/cluster.stories.tsx +385 -0
  121. package/src/components/cluster/cluster.test.tsx +655 -0
  122. package/src/components/cluster/cluster.tsx +94 -0
  123. package/src/components/cluster/cluster.types.ts +75 -0
  124. package/src/components/details/STYLES.mdx +445 -0
  125. package/src/components/dialog/STYLES.mdx +888 -0
  126. package/src/components/flexbox/STYLES.mdx +1 -1
  127. package/src/components/form/STYLES.mdx +821 -0
  128. package/src/components/grid/README.mdx +709 -0
  129. package/src/components/grid/STYLES.mdx +785 -0
  130. package/src/components/grid/grid.scss +287 -0
  131. package/src/components/grid/grid.stories.tsx +486 -0
  132. package/src/components/grid/grid.test.tsx +981 -0
  133. package/src/components/grid/grid.tsx +222 -0
  134. package/src/components/grid/grid.types.ts +344 -0
  135. package/src/components/icons/STYLES.mdx +56 -0
  136. package/src/components/images/STYLES.mdx +75 -0
  137. package/src/components/layout/STYLES.mdx +556 -0
  138. package/src/components/link/STYLES.mdx +75 -0
  139. package/src/components/list/STYLES.mdx +631 -0
  140. package/src/components/nav/STYLES.mdx +460 -0
  141. package/src/components/progress/STYLES.mdx +64 -0
  142. package/src/components/stack/README.mdx +400 -0
  143. package/src/components/stack/STYLES.mdx +414 -0
  144. package/src/components/stack/stack.scss +109 -0
  145. package/src/components/stack/stack.stories.tsx +559 -0
  146. package/src/components/stack/stack.test.tsx +426 -0
  147. package/src/components/stack/stack.tsx +141 -0
  148. package/src/components/stack/stack.types.ts +133 -0
  149. package/src/components/tag/STYLES.mdx +105 -0
  150. package/src/components/text-to-speech/STYLES.mdx +80 -0
  151. package/src/components/ui.tsx +3 -3
  152. package/src/index.scss +7 -2
  153. package/src/index.ts +305 -12
  154. package/src/sass/GLOBALS-STYLES.md +631 -0
  155. package/src/sass/_globals.scss +45 -24
  156. package/src/sass/_styles.scss +2 -2
  157. package/src/styles/box/box.css +220 -0
  158. package/src/styles/box/box.css.map +1 -0
  159. package/src/styles/cards/card.css +23 -17
  160. package/src/styles/cards/card.css.map +1 -1
  161. package/src/styles/cluster/cluster.css +71 -0
  162. package/src/styles/cluster/cluster.css.map +1 -0
  163. package/src/styles/grid/grid.css +238 -0
  164. package/src/styles/grid/grid.css.map +1 -0
  165. package/src/styles/index.css +668 -49
  166. package/src/styles/index.css.map +1 -1
  167. package/src/styles/stack/stack.css +86 -0
  168. package/src/styles/stack/stack.css.map +1 -0
  169. package/src/types/component-props.ts +42 -14
  170. package/src/types/layout-primitives.ts +48 -0
  171. package/src/types/shared.ts +10 -26
  172. package/libs/chunk-ENTCUJ3A.cjs +0 -13
  173. package/libs/chunk-ENTCUJ3A.cjs.map +0 -1
  174. package/libs/chunk-HHLNOC5T.js +0 -7
  175. package/libs/chunk-HHLNOC5T.js.map +0 -1
  176. package/libs/chunk-KK47SYZI.js +0 -8
  177. package/libs/chunk-KK47SYZI.js.map +0 -1
  178. package/libs/chunk-W5TKWBFC.cjs +0 -18
  179. package/libs/chunk-W5TKWBFC.cjs.map +0 -1
  180. package/libs/component-props-67d978a2.d.ts +0 -38
  181. /package/libs/{chunk-2NRIP6RB.cjs.map → chunk-2C3YLBWP.cjs.map} +0 -0
  182. /package/libs/{chunk-NWJDAHP6.cjs.map → chunk-2GJHKWEK.cjs.map} +0 -0
  183. /package/libs/{chunk-FVROL3V5.js.map → chunk-2JCDEC32.js.map} +0 -0
  184. /package/libs/{chunk-IRLFZ3OL.js.map → chunk-3XJC4XUG.js.map} +0 -0
  185. /package/libs/{chunk-L6PRDL6F.cjs.map → chunk-5CJPTDK3.cjs.map} +0 -0
  186. /package/libs/{chunk-E4OSROCA.cjs.map → chunk-5QSNJQVH.cjs.map} +0 -0
  187. /package/libs/{chunk-O3JIHC5M.cjs.map → chunk-6BUJZ4DJ.cjs.map} +0 -0
  188. /package/libs/{chunk-WXBFBWYF.cjs.map → chunk-AFINOD2L.cjs.map} +0 -0
  189. /package/libs/{chunk-HRRHPLER.js.map → chunk-AWZLSWDO.js.map} +0 -0
  190. /package/libs/{chunk-CWRNJA4P.js.map → chunk-DIJBIOFE.js.map} +0 -0
  191. /package/libs/{chunk-GUJSMQ3V.cjs.map → chunk-EKJYOCLY.cjs.map} +0 -0
  192. /package/libs/{chunk-X5RKCLDC.cjs.map → chunk-F64GE6RG.cjs.map} +0 -0
  193. /package/libs/{chunk-5RAWNUVD.js.map → chunk-IBUTNPTQ.js.map} +0 -0
  194. /package/libs/{chunk-ZFJ4U45S.js.map → chunk-KDMX3FAW.js.map} +0 -0
  195. /package/libs/{chunk-DYFAUAB7.cjs.map → chunk-LXODKKA3.cjs.map} +0 -0
  196. /package/libs/{chunk-MPTMPBFT.js.map → chunk-M7JLT62Q.js.map} +0 -0
  197. /package/libs/{chunk-IQ76HGVP.js.map → chunk-MBWI67UT.js.map} +0 -0
  198. /package/libs/{chunk-O5XAJ7BY.cjs.map → chunk-NCGVF2QS.cjs.map} +0 -0
  199. /package/libs/{chunk-W2UIN7EV.cjs.map → chunk-NPWHQVYB.cjs.map} +0 -0
  200. /package/libs/{chunk-43TK2ICH.js.map → chunk-PMWL5XZ4.js.map} +0 -0
  201. /package/libs/{chunk-KVKQLRJG.js.map → chunk-TF3GQKOY.js.map} +0 -0
  202. /package/libs/{chunk-IEB64SWY.js.map → chunk-U5VA34SU.js.map} +0 -0
  203. /package/libs/{chunk-EE3ZWSBY.cjs.map → chunk-URBGDUFN.cjs.map} +0 -0
  204. /package/libs/{chunk-TPIB3RQP.js.map → chunk-ZF6Y7W57.js.map} +0 -0
@@ -0,0 +1,425 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { Box } from "./box";
4
+
5
+ describe("Box", () => {
6
+ // ============================================================================
7
+ // Rendering Tests
8
+ // ============================================================================
9
+
10
+ it("renders with default props", () => {
11
+ render(<Box>Content</Box>);
12
+ expect(screen.getByText("Content")).toBeInTheDocument();
13
+ });
14
+
15
+ it("renders children correctly", () => {
16
+ render(
17
+ <Box>
18
+ <span>Child 1</span>
19
+ <span>Child 2</span>
20
+ </Box>
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(<Box data-testid="box">Content</Box>);
28
+ const box = screen.getByTestId("box");
29
+ expect(box.tagName).toBe("DIV");
30
+ });
31
+
32
+ // ============================================================================
33
+ // Polymorphic Rendering Tests
34
+ // ============================================================================
35
+
36
+ it("renders as different elements via as prop", () => {
37
+ render(<Box as="section" data-testid="box">Content</Box>);
38
+ const box = screen.getByTestId("box");
39
+ expect(box.tagName).toBe("SECTION");
40
+ });
41
+
42
+ it("renders as article", () => {
43
+ render(<Box as="article" data-testid="box">Content</Box>);
44
+ const box = screen.getByTestId("box");
45
+ expect(box.tagName).toBe("ARTICLE");
46
+ });
47
+
48
+ it("renders as main", () => {
49
+ render(<Box as="main" data-testid="box">Content</Box>);
50
+ const box = screen.getByTestId("box");
51
+ expect(box.tagName).toBe("MAIN");
52
+ });
53
+
54
+ it("renders as header", () => {
55
+ render(<Box as="header" data-testid="box">Content</Box>);
56
+ const box = screen.getByTestId("box");
57
+ expect(box.tagName).toBe("HEADER");
58
+ });
59
+
60
+ it("renders as footer", () => {
61
+ render(<Box as="footer" data-testid="box">Content</Box>);
62
+ const box = screen.getByTestId("box");
63
+ expect(box.tagName).toBe("FOOTER");
64
+ });
65
+
66
+ // ============================================================================
67
+ // Padding Class Tests
68
+ // ============================================================================
69
+
70
+ it("applies padding class", () => {
71
+ render(<Box padding="md" data-testid="box">Content</Box>);
72
+ const box = screen.getByTestId("box");
73
+ expect(box).toHaveClass("box-padding-md");
74
+ });
75
+
76
+ it("applies paddingInline class", () => {
77
+ render(<Box paddingInline="lg" data-testid="box">Content</Box>);
78
+ const box = screen.getByTestId("box");
79
+ expect(box).toHaveClass("box-padding-inline-lg");
80
+ });
81
+
82
+ it("applies paddingBlock class", () => {
83
+ render(<Box paddingBlock="sm" data-testid="box">Content</Box>);
84
+ const box = screen.getByTestId("box");
85
+ expect(box).toHaveClass("box-padding-block-sm");
86
+ });
87
+
88
+ it("applies multiple padding classes", () => {
89
+ render(
90
+ <Box padding="md" paddingInline="lg" paddingBlock="sm" data-testid="box">
91
+ Content
92
+ </Box>
93
+ );
94
+ const box = screen.getByTestId("box");
95
+ expect(box).toHaveClass("box-padding-md");
96
+ expect(box).toHaveClass("box-padding-inline-lg");
97
+ expect(box).toHaveClass("box-padding-block-sm");
98
+ });
99
+
100
+ it("applies padding-0 class", () => {
101
+ render(<Box padding="0" data-testid="box">Content</Box>);
102
+ const box = screen.getByTestId("box");
103
+ expect(box).toHaveClass("box-padding-0");
104
+ });
105
+
106
+ // ============================================================================
107
+ // Margin Class Tests
108
+ // ============================================================================
109
+
110
+ it("applies margin class", () => {
111
+ render(<Box margin="lg" data-testid="box">Content</Box>);
112
+ const box = screen.getByTestId("box");
113
+ expect(box).toHaveClass("box-margin-lg");
114
+ });
115
+
116
+ it("applies marginInline class", () => {
117
+ render(<Box marginInline="xl" data-testid="box">Content</Box>);
118
+ const box = screen.getByTestId("box");
119
+ expect(box).toHaveClass("box-margin-inline-xl");
120
+ });
121
+
122
+ it("applies marginBlock class", () => {
123
+ render(<Box marginBlock="md" data-testid="box">Content</Box>);
124
+ const box = screen.getByTestId("box");
125
+ expect(box).toHaveClass("box-margin-block-md");
126
+ });
127
+
128
+ it("applies margin-0 class", () => {
129
+ render(<Box margin="0" data-testid="box">Content</Box>);
130
+ const box = screen.getByTestId("box");
131
+ expect(box).toHaveClass("box-margin-0");
132
+ });
133
+
134
+ // ============================================================================
135
+ // Width Class Tests
136
+ // ============================================================================
137
+
138
+ it("applies width auto class", () => {
139
+ render(<Box width="auto" data-testid="box">Content</Box>);
140
+ const box = screen.getByTestId("box");
141
+ expect(box).toHaveClass("box-width-auto");
142
+ });
143
+
144
+ it("applies width full class", () => {
145
+ render(<Box width="full" data-testid="box">Content</Box>);
146
+ const box = screen.getByTestId("box");
147
+ expect(box).toHaveClass("box-width-full");
148
+ });
149
+
150
+ it("applies width fit class", () => {
151
+ render(<Box width="fit" data-testid="box">Content</Box>);
152
+ const box = screen.getByTestId("box");
153
+ expect(box).toHaveClass("box-width-fit");
154
+ });
155
+
156
+ it("applies width max class", () => {
157
+ render(<Box width="max" data-testid="box">Content</Box>);
158
+ const box = screen.getByTestId("box");
159
+ expect(box).toHaveClass("box-width-max");
160
+ });
161
+
162
+ // ============================================================================
163
+ // Max-Width Class Tests
164
+ // ============================================================================
165
+
166
+ it("applies maxWidth xs class", () => {
167
+ render(<Box maxWidth="xs" data-testid="box">Content</Box>);
168
+ const box = screen.getByTestId("box");
169
+ expect(box).toHaveClass("box-max-width-xs");
170
+ });
171
+
172
+ it("applies maxWidth sm class", () => {
173
+ render(<Box maxWidth="sm" data-testid="box">Content</Box>);
174
+ const box = screen.getByTestId("box");
175
+ expect(box).toHaveClass("box-max-width-sm");
176
+ });
177
+
178
+ it("applies maxWidth md class", () => {
179
+ render(<Box maxWidth="md" data-testid="box">Content</Box>);
180
+ const box = screen.getByTestId("box");
181
+ expect(box).toHaveClass("box-max-width-md");
182
+ });
183
+
184
+ it("applies maxWidth lg class", () => {
185
+ render(<Box maxWidth="lg" data-testid="box">Content</Box>);
186
+ const box = screen.getByTestId("box");
187
+ expect(box).toHaveClass("box-max-width-lg");
188
+ });
189
+
190
+ it("applies maxWidth xl class", () => {
191
+ render(<Box maxWidth="xl" data-testid="box">Content</Box>);
192
+ const box = screen.getByTestId("box");
193
+ expect(box).toHaveClass("box-max-width-xl");
194
+ });
195
+
196
+ it("applies maxWidth container class", () => {
197
+ render(<Box maxWidth="container" data-testid="box">Content</Box>);
198
+ const box = screen.getByTestId("box");
199
+ expect(box).toHaveClass("box-max-width-container");
200
+ });
201
+
202
+ // ============================================================================
203
+ // Border Radius Class Tests
204
+ // ============================================================================
205
+
206
+ it("applies radius xs class", () => {
207
+ render(<Box radius="xs" data-testid="box">Content</Box>);
208
+ const box = screen.getByTestId("box");
209
+ expect(box).toHaveClass("box-radius-xs");
210
+ });
211
+
212
+ it("applies radius sm class", () => {
213
+ render(<Box radius="sm" data-testid="box">Content</Box>);
214
+ const box = screen.getByTestId("box");
215
+ expect(box).toHaveClass("box-radius-sm");
216
+ });
217
+
218
+ it("applies radius md class", () => {
219
+ render(<Box radius="md" data-testid="box">Content</Box>);
220
+ const box = screen.getByTestId("box");
221
+ expect(box).toHaveClass("box-radius-md");
222
+ });
223
+
224
+ it("applies radius lg class", () => {
225
+ render(<Box radius="lg" data-testid="box">Content</Box>);
226
+ const box = screen.getByTestId("box");
227
+ expect(box).toHaveClass("box-radius-lg");
228
+ });
229
+
230
+ it("applies radius xl class", () => {
231
+ render(<Box radius="xl" data-testid="box">Content</Box>);
232
+ const box = screen.getByTestId("box");
233
+ expect(box).toHaveClass("box-radius-xl");
234
+ });
235
+
236
+ it("applies radius full class", () => {
237
+ render(<Box radius="full" data-testid="box">Content</Box>);
238
+ const box = screen.getByTestId("box");
239
+ expect(box).toHaveClass("box-radius-full");
240
+ });
241
+
242
+ it("applies radius 0 class", () => {
243
+ render(<Box radius="0" data-testid="box">Content</Box>);
244
+ const box = screen.getByTestId("box");
245
+ expect(box).toHaveClass("box-radius-0");
246
+ });
247
+
248
+ // ============================================================================
249
+ // Multiple Props Tests
250
+ // ============================================================================
251
+
252
+ it("applies multiple utility classes", () => {
253
+ render(
254
+ <Box
255
+ padding="md"
256
+ margin="lg"
257
+ width="full"
258
+ maxWidth="container"
259
+ radius="md"
260
+ data-testid="box"
261
+ >
262
+ Content
263
+ </Box>
264
+ );
265
+ const box = screen.getByTestId("box");
266
+ expect(box).toHaveClass("box-padding-md");
267
+ expect(box).toHaveClass("box-margin-lg");
268
+ expect(box).toHaveClass("box-width-full");
269
+ expect(box).toHaveClass("box-max-width-container");
270
+ expect(box).toHaveClass("box-radius-md");
271
+ });
272
+
273
+ // ============================================================================
274
+ // Custom Class Tests
275
+ // ============================================================================
276
+
277
+ it("merges custom className with utility classes", () => {
278
+ render(
279
+ <Box padding="md" className="custom-class" data-testid="box">
280
+ Content
281
+ </Box>
282
+ );
283
+ const box = screen.getByTestId("box");
284
+ expect(box).toHaveClass("box-padding-md");
285
+ expect(box).toHaveClass("custom-class");
286
+ });
287
+
288
+ it("merges custom classes with utility classes", () => {
289
+ render(
290
+ <Box padding="md" classes="legacy-class" data-testid="box">
291
+ Content
292
+ </Box>
293
+ );
294
+ const box = screen.getByTestId("box");
295
+ expect(box).toHaveClass("box-padding-md");
296
+ expect(box).toHaveClass("legacy-class");
297
+ });
298
+
299
+ it("merges both className and classes with utility classes", () => {
300
+ render(
301
+ <Box
302
+ padding="md"
303
+ className="custom-class"
304
+ classes="legacy-class"
305
+ data-testid="box"
306
+ >
307
+ Content
308
+ </Box>
309
+ );
310
+ const box = screen.getByTestId("box");
311
+ expect(box).toHaveClass("box-padding-md");
312
+ expect(box).toHaveClass("custom-class");
313
+ expect(box).toHaveClass("legacy-class");
314
+ });
315
+
316
+ // ============================================================================
317
+ // Inline Styles Tests
318
+ // ============================================================================
319
+
320
+ it("applies custom styles", () => {
321
+ render(
322
+ <Box
323
+ padding="md"
324
+ styles={{ backgroundColor: "red", color: "white" }}
325
+ data-testid="box"
326
+ >
327
+ Content
328
+ </Box>
329
+ );
330
+ const box = screen.getByTestId("box");
331
+ expect(box).toHaveStyle({ backgroundColor: "rgb(255, 0, 0)" });
332
+ expect(box).toHaveStyle({ color: "rgb(255, 255, 255)" });
333
+ });
334
+
335
+ it("accepts CSS custom properties in styles", () => {
336
+ render(
337
+ <Box
338
+ padding="md"
339
+ styles={{ "--spacing-md": "2rem" } as React.CSSProperties}
340
+ data-testid="box"
341
+ >
342
+ Content
343
+ </Box>
344
+ );
345
+ const box = screen.getByTestId("box");
346
+ expect(box).toHaveAttribute("style");
347
+ });
348
+
349
+ // ============================================================================
350
+ // Ref Forwarding Tests
351
+ // ============================================================================
352
+
353
+ it("forwards ref correctly", () => {
354
+ const ref = React.createRef<HTMLDivElement>();
355
+ render(
356
+ <Box ref={ref} data-testid="box">
357
+ Content
358
+ </Box>
359
+ );
360
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
361
+ });
362
+
363
+ it("forwards ref with polymorphic as prop", () => {
364
+ const ref = React.createRef<HTMLElement>();
365
+ render(
366
+ <Box as="section" ref={ref} data-testid="box">
367
+ Content
368
+ </Box>
369
+ );
370
+ expect(ref.current).toBeInstanceOf(HTMLElement);
371
+ expect(ref.current?.tagName).toBe("SECTION");
372
+ });
373
+
374
+ // ============================================================================
375
+ // Accessibility Tests
376
+ // ============================================================================
377
+
378
+ it("forwards ARIA attributes", () => {
379
+ render(
380
+ <Box aria-label="Test Box" role="region" data-testid="box">
381
+ Content
382
+ </Box>
383
+ );
384
+ const box = screen.getByTestId("box");
385
+ expect(box).toHaveAttribute("aria-label", "Test Box");
386
+ expect(box).toHaveAttribute("role", "region");
387
+ });
388
+
389
+ it("forwards data attributes", () => {
390
+ render(
391
+ <Box data-testid="box" data-custom="value">
392
+ Content
393
+ </Box>
394
+ );
395
+ const box = screen.getByTestId("box");
396
+ expect(box).toHaveAttribute("data-custom", "value");
397
+ });
398
+
399
+ // ============================================================================
400
+ // Edge Cases
401
+ // ============================================================================
402
+
403
+ it("handles empty children", () => {
404
+ render(<Box data-testid="box" />);
405
+ const box = screen.getByTestId("box");
406
+ expect(box).toBeInTheDocument();
407
+ expect(box).toBeEmptyDOMElement();
408
+ });
409
+
410
+ it("handles no props gracefully", () => {
411
+ render(<Box data-testid="box">Content</Box>);
412
+ const box = screen.getByTestId("box");
413
+ expect(box).toBeInTheDocument();
414
+ expect(box.tagName).toBe("DIV");
415
+ });
416
+
417
+ it("does not apply classes when no spacing props provided", () => {
418
+ render(<Box data-testid="box">Content</Box>);
419
+ const box = screen.getByTestId("box");
420
+ // Should not have any box-* utility classes
421
+ const classList = Array.from(box.classList);
422
+ const hasBoxUtilityClass = classList.some((cls) => cls.startsWith("box-"));
423
+ expect(hasBoxUtilityClass).toBe(false);
424
+ });
425
+ });
@@ -0,0 +1,170 @@
1
+ import React from "react";
2
+ import UI from "../ui";
3
+ import type { BoxProps } from "./box.types";
4
+
5
+ // Re-export types for convenience
6
+ export type { BoxProps } from "./box.types";
7
+
8
+ /**
9
+ * Box - A fundamental container primitive for spacing and sizing control.
10
+ *
11
+ * The Box component is the foundational layout primitive in fpkit, providing a flexible,
12
+ * semantic container with comprehensive control over spacing (padding/margin), sizing,
13
+ * and visual appearance. It uses utility classes generated from props, ensuring consistent
14
+ * styling across the application.
15
+ *
16
+ * ## Key Features
17
+ * - **Unified Spacing Scale**: Fluid responsive spacing using CSS clamp()
18
+ * - **Logical Properties**: `padding-inline`/`padding-block` for i18n support
19
+ * - **Polymorphic Rendering**: Render as any semantic HTML element via `as` prop
20
+ * - **CSS Custom Properties**: All values customizable for theming
21
+ * - **Type-Safe**: Full TypeScript support with IntelliSense
22
+ *
23
+ * ## Accessibility
24
+ * - Uses semantic HTML elements by default
25
+ * - Supports ARIA attributes via spread props
26
+ * - Encourages semantic elements via `as` prop
27
+ * - All props forwarded to underlying element
28
+ *
29
+ * ## Use Cases
30
+ * - Container with padding/margin
31
+ * - Centered layouts with max-width
32
+ * - Card-like components
33
+ * - Spacing between sections
34
+ * - Semantic landmarks
35
+ *
36
+ * @example
37
+ * // Basic container with padding
38
+ * <Box padding="md">
39
+ * <h1>Content</h1>
40
+ * </Box>
41
+ *
42
+ * @example
43
+ * // Centered container with max width
44
+ * <Box
45
+ * padding="lg"
46
+ * maxWidth="container"
47
+ * style={{ marginInline: 'auto' }}
48
+ * >
49
+ * <article>Centered content</article>
50
+ * </Box>
51
+ *
52
+ * @example
53
+ * // Card-like box with radius
54
+ * <Box
55
+ * padding="lg"
56
+ * radius="md"
57
+ * as="article"
58
+ * styles={{
59
+ * backgroundColor: '#fff',
60
+ * boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
61
+ * }}
62
+ * >
63
+ * <h2>Card Title</h2>
64
+ * <p>Card content</p>
65
+ * </Box>
66
+ *
67
+ * @example
68
+ * // Asymmetric spacing with logical properties
69
+ * <Box
70
+ * paddingInline="xl"
71
+ * paddingBlock="md"
72
+ * as="section"
73
+ * >
74
+ * <p>Wide horizontal padding, narrow vertical</p>
75
+ * </Box>
76
+ *
77
+ * @example
78
+ * // Semantic section with spacing
79
+ * <Box as="section" padding="xl" margin="lg">
80
+ * <h2>Section Title</h2>
81
+ * <p>Section content...</p>
82
+ * </Box>
83
+ *
84
+ * @see {@link BoxProps} for complete props documentation
85
+ */
86
+ export const Box = React.forwardRef<HTMLElement, BoxProps>(
87
+ (
88
+ {
89
+ padding,
90
+ paddingInline,
91
+ paddingBlock,
92
+ margin,
93
+ marginInline,
94
+ marginBlock,
95
+ width,
96
+ maxWidth,
97
+ radius,
98
+ as = "div",
99
+ className,
100
+ classes,
101
+ children,
102
+ ...props
103
+ },
104
+ ref
105
+ ) => {
106
+ // Build utility classes array based on props
107
+ const utilityClasses: string[] = [];
108
+
109
+ // Padding utilities
110
+ if (padding) {
111
+ utilityClasses.push(`box-padding-${padding}`);
112
+ }
113
+ if (paddingInline) {
114
+ utilityClasses.push(`box-padding-inline-${paddingInline}`);
115
+ }
116
+ if (paddingBlock) {
117
+ utilityClasses.push(`box-padding-block-${paddingBlock}`);
118
+ }
119
+
120
+ // Margin utilities
121
+ if (margin) {
122
+ utilityClasses.push(`box-margin-${margin}`);
123
+ }
124
+ if (marginInline) {
125
+ utilityClasses.push(`box-margin-inline-${marginInline}`);
126
+ }
127
+ if (marginBlock) {
128
+ utilityClasses.push(`box-margin-block-${marginBlock}`);
129
+ }
130
+
131
+ // Width utilities
132
+ if (width) {
133
+ utilityClasses.push(`box-width-${width}`);
134
+ }
135
+
136
+ // Max-width utilities
137
+ if (maxWidth) {
138
+ utilityClasses.push(`box-max-width-${maxWidth}`);
139
+ }
140
+
141
+ // Border radius utilities
142
+ if (radius) {
143
+ utilityClasses.push(`box-radius-${radius}`);
144
+ }
145
+
146
+ // Merge all classes: utility classes, className prop, and classes prop
147
+ const allClasses = [
148
+ ...utilityClasses,
149
+ className,
150
+ classes,
151
+ ]
152
+ .filter(Boolean)
153
+ .join(" ");
154
+
155
+ return (
156
+ <UI
157
+ as={as}
158
+ ref={ref}
159
+ classes={allClasses || undefined}
160
+ {...props}
161
+ >
162
+ {children}
163
+ </UI>
164
+ );
165
+ }
166
+ );
167
+
168
+ Box.displayName = "Box";
169
+
170
+ export default Box;