@boostdev/design-system-components 2.7.1 → 2.11.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 (535) hide show
  1. package/AGENTS.md +54 -46
  2. package/README.md +34 -20
  3. package/dist/client.cjs +1317 -779
  4. package/dist/client.css +1029 -812
  5. package/dist/client.d.cts +88 -26
  6. package/dist/client.d.ts +88 -26
  7. package/dist/client.js +1278 -732
  8. package/dist/index.cjs +1317 -779
  9. package/dist/index.css +1029 -812
  10. package/dist/index.d.cts +88 -26
  11. package/dist/index.d.ts +88 -26
  12. package/dist/index.js +1278 -732
  13. package/dist/web-components/chunk-2OLVQUBU.js +193 -0
  14. package/dist/web-components/chunk-3AY2YKCM.js +223 -0
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-CZBIVR4V.js → chunk-47AT5KZD.js} +88 -16
  17. package/dist/web-components/chunk-4A2YYTYN.js +93 -0
  18. package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
  19. package/dist/web-components/chunk-66M6ETWD.js +77 -0
  20. package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
  21. package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
  22. package/dist/web-components/{chunk-5R6IFELV.js → chunk-AIBOHNDU.js} +11 -1
  23. package/dist/web-components/{chunk-ITHLKR7Q.js → chunk-AP4RDUL2.js} +16 -5
  24. package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
  25. package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
  26. package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
  27. package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
  28. package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
  29. package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
  30. package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
  31. package/dist/web-components/chunk-FEDABOR5.js +248 -0
  32. package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
  33. package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
  34. package/dist/web-components/chunk-ITD2MV5K.js +392 -0
  35. package/dist/web-components/{chunk-CM3KYZ6Z.js → chunk-JH6LQKLK.js} +78 -6
  36. package/dist/web-components/chunk-JMCXAAFK.js +120 -0
  37. package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
  38. package/dist/web-components/chunk-JV5QT2OU.js +146 -0
  39. package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
  40. package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
  41. package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
  42. package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
  43. package/dist/web-components/chunk-M3ECULRH.js +174 -0
  44. package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
  45. package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
  46. package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
  47. package/dist/web-components/chunk-OY74D4X6.js +302 -0
  48. package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
  49. package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
  50. package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
  51. package/dist/web-components/chunk-QZAIRJ75.js +111 -0
  52. package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
  53. package/dist/web-components/chunk-RVF7W653.js +101 -0
  54. package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
  55. package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
  56. package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
  57. package/dist/web-components/chunk-TMWXRHGF.js +192 -0
  58. package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
  59. package/dist/web-components/chunk-UO7ROSHD.js +424 -0
  60. package/dist/web-components/chunk-VANAKB6D.js +152 -0
  61. package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
  62. package/dist/web-components/chunk-W6H5ID2U.js +161 -0
  63. package/dist/web-components/{chunk-57TRNCWQ.js → chunk-WFVKU4GX.js} +22 -11
  64. package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
  65. package/dist/web-components/chunk-XI7KRAH7.js +54 -0
  66. package/dist/web-components/{chunk-F5E3IT7G.js → chunk-XIZWAWMJ.js} +50 -11
  67. package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
  68. package/dist/web-components/globals.js +1 -1
  69. package/dist/web-components/index.d.ts +77 -707
  70. package/dist/web-components/index.js +339 -2066
  71. package/dist/web-components/interaction/bds-accordion-item.js +1 -1
  72. package/dist/web-components/interaction/bds-accordion.js +1 -1
  73. package/dist/web-components/interaction/bds-button.d.ts +10 -2
  74. package/dist/web-components/interaction/bds-button.js +1 -1
  75. package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
  76. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  77. package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
  78. package/dist/web-components/interaction/bds-dialog.js +1 -1
  79. package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
  80. package/dist/web-components/interaction/bds-drawer.js +1 -1
  81. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
  82. package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
  83. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
  84. package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
  85. package/dist/web-components/interaction/bds-popover.d.ts +14 -3
  86. package/dist/web-components/interaction/bds-popover.js +1 -1
  87. package/dist/web-components/interaction/bds-skip-link.js +1 -1
  88. package/dist/web-components/interaction/bds-tab-panel.js +1 -1
  89. package/dist/web-components/interaction/bds-tab.js +1 -1
  90. package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
  91. package/dist/web-components/interaction/bds-tabs.js +1 -1
  92. package/dist/web-components/interaction/bds-toast-provider.js +1 -1
  93. package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
  94. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  95. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
  96. package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
  97. package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
  98. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  99. package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
  100. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  101. package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
  102. package/dist/web-components/interaction/form/bds-field-group.js +7 -0
  103. package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
  104. package/dist/web-components/interaction/form/bds-file-input.js +1 -1
  105. package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
  106. package/dist/web-components/interaction/form/bds-form-input.js +7 -0
  107. package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
  108. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  109. package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
  110. package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
  111. package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
  112. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  113. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
  114. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  115. package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
  116. package/dist/web-components/interaction/form/bds-select.js +1 -1
  117. package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
  118. package/dist/web-components/interaction/form/bds-slider.js +1 -1
  119. package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
  120. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  121. package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
  122. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  123. package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
  124. package/dist/web-components/layout/bds-grid-item.js +7 -0
  125. package/dist/web-components/layout/bds-grid.d.ts +108 -0
  126. package/dist/web-components/layout/bds-grid.js +7 -0
  127. package/dist/web-components/ui/bds-alert.d.ts +16 -2
  128. package/dist/web-components/ui/bds-alert.js +1 -1
  129. package/dist/web-components/ui/bds-avatar.d.ts +2 -1
  130. package/dist/web-components/ui/bds-avatar.js +1 -1
  131. package/dist/web-components/ui/bds-badge.js +1 -1
  132. package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
  133. package/dist/web-components/ui/bds-breadcrumb.js +1 -1
  134. package/dist/web-components/ui/bds-button-container.d.ts +52 -0
  135. package/dist/web-components/ui/bds-button-container.js +7 -0
  136. package/dist/web-components/ui/bds-button-group.d.ts +21 -0
  137. package/dist/web-components/ui/bds-button-group.js +8 -0
  138. package/dist/web-components/ui/bds-calendar.d.ts +121 -0
  139. package/dist/web-components/ui/bds-calendar.js +7 -0
  140. package/dist/web-components/ui/bds-card.d.ts +12 -3
  141. package/dist/web-components/ui/bds-card.js +1 -1
  142. package/dist/web-components/ui/bds-carousel.d.ts +98 -0
  143. package/dist/web-components/ui/bds-carousel.js +7 -0
  144. package/dist/web-components/ui/bds-description-list.js +1 -1
  145. package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
  146. package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
  147. package/dist/web-components/ui/bds-link.js +1 -1
  148. package/dist/web-components/ui/bds-loading.js +1 -1
  149. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  150. package/dist/web-components/ui/bds-pagination.d.ts +47 -0
  151. package/dist/web-components/ui/bds-pagination.js +7 -0
  152. package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
  153. package/dist/web-components/ui/bds-progress-circle.js +1 -1
  154. package/dist/web-components/ui/bds-progress.d.ts +3 -1
  155. package/dist/web-components/ui/bds-progress.js +1 -1
  156. package/dist/web-components/ui/bds-rating.js +1 -1
  157. package/dist/web-components/ui/bds-section-header.d.ts +18 -4
  158. package/dist/web-components/ui/bds-section-header.js +1 -1
  159. package/dist/web-components/ui/bds-separator.js +1 -1
  160. package/dist/web-components/ui/bds-skeleton.js +1 -1
  161. package/dist/web-components/ui/bds-table.d.ts +52 -0
  162. package/dist/web-components/ui/bds-table.js +7 -0
  163. package/dist/web-components/ui/bds-typography.d.ts +1 -0
  164. package/dist/web-components/ui/bds-typography.js +1 -1
  165. package/package.json +60 -3
  166. package/src/components/interaction/Button/Button.module.css +23 -14
  167. package/src/components/interaction/Button/Button.spec.tsx +20 -0
  168. package/src/components/interaction/Button/Button.tsx +2 -2
  169. package/src/components/interaction/Command/Command.mdx +5 -2
  170. package/src/components/interaction/Command/Command.module.css +23 -7
  171. package/src/components/interaction/Command/Command.spec.tsx +35 -0
  172. package/src/components/interaction/Command/Command.tsx +15 -4
  173. package/src/components/interaction/Command/index.ts +1 -1
  174. package/src/components/interaction/Dialog/Dialog.mdx +4 -4
  175. package/src/components/interaction/Dialog/Dialog.module.css +20 -0
  176. package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
  177. package/src/components/interaction/Dialog/Dialog.tsx +8 -3
  178. package/src/components/interaction/Dialog/index.ts +1 -0
  179. package/src/components/interaction/Drawer/Drawer.mdx +5 -2
  180. package/src/components/interaction/Drawer/Drawer.module.css +34 -3
  181. package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
  182. package/src/components/interaction/Drawer/Drawer.tsx +30 -12
  183. package/src/components/interaction/Drawer/index.ts +1 -0
  184. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
  185. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
  186. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
  187. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  188. package/src/components/interaction/Popover/Popover.mdx +6 -2
  189. package/src/components/interaction/Popover/Popover.module.css +26 -18
  190. package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
  191. package/src/components/interaction/Popover/Popover.tsx +19 -5
  192. package/src/components/interaction/Popover/index.ts +1 -0
  193. package/src/components/interaction/Rating/Rating.mdx +1 -1
  194. package/src/components/interaction/Rating/Rating.module.css +2 -2
  195. package/src/components/interaction/Rating/Rating.tsx +1 -1
  196. package/src/components/interaction/Rating/index.ts +1 -0
  197. package/src/components/interaction/Toast/Toast.mdx +3 -0
  198. package/src/components/interaction/Toast/Toast.module.css +35 -5
  199. package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
  200. package/src/components/interaction/Toast/Toast.tsx +15 -9
  201. package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
  202. package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
  203. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
  204. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  205. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
  206. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
  207. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
  208. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
  209. package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
  210. package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
  211. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
  212. package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
  213. package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
  214. package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
  215. package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
  216. package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
  217. package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
  218. package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
  219. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
  220. package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
  221. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
  222. package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
  223. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
  224. package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
  225. package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
  226. package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
  227. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
  228. package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
  229. package/src/components/interaction/form/Radio/Radio.mdx +8 -1
  230. package/src/components/interaction/form/Radio/Radio.module.css +20 -7
  231. package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
  232. package/src/components/interaction/form/Radio/Radio.tsx +10 -4
  233. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
  234. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
  235. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
  236. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
  237. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
  238. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
  239. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
  240. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
  241. package/src/components/interaction/form/Select/Select.mdx +10 -4
  242. package/src/components/interaction/form/Select/Select.module.css +4 -4
  243. package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
  244. package/src/components/interaction/form/Select/Select.tsx +21 -9
  245. package/src/components/interaction/form/Slider/Slider.mdx +4 -1
  246. package/src/components/interaction/form/Slider/Slider.module.css +32 -8
  247. package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
  248. package/src/components/interaction/form/Slider/Slider.tsx +9 -7
  249. package/src/components/interaction/form/Switch/Switch.mdx +2 -2
  250. package/src/components/interaction/form/Switch/Switch.module.css +12 -13
  251. package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
  252. package/src/components/interaction/form/Switch/Switch.tsx +2 -1
  253. package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
  254. package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
  255. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
  256. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
  257. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  258. package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
  259. package/src/components/interaction/form/atoms/Message.tsx +1 -1
  260. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
  261. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
  262. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
  263. package/src/components/layout/Card/Card.mdx +6 -1
  264. package/src/components/layout/Card/Card.module.css +26 -5
  265. package/src/components/layout/Card/Card.spec.tsx +7 -0
  266. package/src/components/layout/Card/Card.tsx +4 -3
  267. package/src/components/layout/Grid/Grid.spec.tsx +44 -0
  268. package/src/components/layout/Grid/Grid.tsx +5 -5
  269. package/src/components/layout/Grid/GridItem.tsx +6 -6
  270. package/src/components/layout/Grid/masonry.ts +31 -5
  271. package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
  272. package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
  273. package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
  274. package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
  275. package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
  276. package/src/components/ui/Accordion/Accordion.mdx +4 -1
  277. package/src/components/ui/Accordion/Accordion.module.css +2 -1
  278. package/src/components/ui/Alert/Alert.mdx +21 -4
  279. package/src/components/ui/Alert/Alert.module.css +9 -4
  280. package/src/components/ui/Alert/Alert.spec.tsx +20 -0
  281. package/src/components/ui/Avatar/Avatar.mdx +8 -3
  282. package/src/components/ui/Avatar/Avatar.module.css +13 -8
  283. package/src/components/ui/Badge/Badge.mdx +3 -1
  284. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
  285. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
  286. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
  287. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
  288. package/src/components/ui/Calendar/Calendar.mdx +6 -0
  289. package/src/components/ui/Calendar/Calendar.module.css +68 -19
  290. package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
  291. package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
  292. package/src/components/ui/Calendar/Calendar.tsx +143 -154
  293. package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
  294. package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
  295. package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
  296. package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
  297. package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
  298. package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
  299. package/src/components/ui/Calendar/calendar-core.ts +164 -0
  300. package/src/components/ui/Calendar/index.ts +1 -0
  301. package/src/components/ui/Carousel/Carousel.mdx +8 -3
  302. package/src/components/ui/Carousel/Carousel.module.css +7 -5
  303. package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
  304. package/src/components/ui/Carousel/Carousel.tsx +19 -7
  305. package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
  306. package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
  307. package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
  308. package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
  309. package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
  310. package/src/components/ui/Link/Link.mdx +4 -2
  311. package/src/components/ui/Loading/Loading.mdx +6 -3
  312. package/src/components/ui/Loading/Loading.module.css +7 -7
  313. package/src/components/ui/Loading/Loading.spec.tsx +12 -0
  314. package/src/components/ui/Loading/Loading.tsx +8 -3
  315. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
  316. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
  317. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
  318. package/src/components/ui/Pagination/Pagination.mdx +29 -7
  319. package/src/components/ui/Pagination/Pagination.module.css +15 -14
  320. package/src/components/ui/Progress/Progress.mdx +6 -2
  321. package/src/components/ui/Progress/Progress.module.css +1 -1
  322. package/src/components/ui/Progress/Progress.spec.tsx +17 -0
  323. package/src/components/ui/Progress/Progress.tsx +2 -2
  324. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
  325. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
  326. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
  327. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
  328. package/src/components/ui/Separator/Separator.mdx +7 -0
  329. package/src/components/ui/Separator/Separator.module.css +1 -0
  330. package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
  331. package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
  332. package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
  333. package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
  334. package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
  335. package/src/components/ui/Table/Table.mdx +14 -0
  336. package/src/components/ui/Table/Table.module.css +13 -8
  337. package/src/components/ui/Table/Table.spec.tsx +56 -0
  338. package/src/components/ui/Table/Table.tsx +44 -6
  339. package/src/components/ui/Tabs/Tabs.mdx +16 -0
  340. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  341. package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
  342. package/src/components/ui/Tabs/Tabs.tsx +5 -1
  343. package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
  344. package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
  345. package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
  346. package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
  347. package/src/components/ui/Typography/Typography.mdx +7 -0
  348. package/src/components/ui/Typography/Typography.module.css +5 -5
  349. package/src/index.ts +7 -3
  350. package/src/stories/DesignSystem/Borders.mdx +14 -9
  351. package/src/stories/DesignSystem/Colors.mdx +43 -41
  352. package/src/stories/DesignSystem/Overview.mdx +4 -3
  353. package/src/stories/Introduction.mdx +5 -5
  354. package/src/web-components/globals.ts +7 -0
  355. package/src/web-components/index.ts +2 -0
  356. package/src/web-components/interaction/BdsAccordion.mdx +10 -1
  357. package/src/web-components/interaction/BdsButton.mdx +15 -4
  358. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  359. package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
  360. package/src/web-components/interaction/BdsDialog.mdx +12 -7
  361. package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
  362. package/src/web-components/interaction/BdsDrawer.mdx +9 -6
  363. package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
  364. package/src/web-components/interaction/BdsPopover.mdx +7 -3
  365. package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
  366. package/src/web-components/interaction/BdsTabs.mdx +18 -1
  367. package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
  368. package/src/web-components/interaction/BdsTooltip.mdx +4 -4
  369. package/src/web-components/interaction/bds-accordion-item.ts +10 -0
  370. package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
  371. package/src/web-components/interaction/bds-accordion.ts +28 -2
  372. package/src/web-components/interaction/bds-button.spec.ts +81 -0
  373. package/src/web-components/interaction/bds-button.ts +60 -13
  374. package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
  375. package/src/web-components/interaction/bds-collapsible.ts +49 -7
  376. package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
  377. package/src/web-components/interaction/bds-dialog.ts +68 -11
  378. package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
  379. package/src/web-components/interaction/bds-drawer.ts +54 -21
  380. package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
  381. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
  382. package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
  383. package/src/web-components/interaction/bds-popover.spec.ts +80 -6
  384. package/src/web-components/interaction/bds-popover.ts +83 -32
  385. package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
  386. package/src/web-components/interaction/bds-skip-link.ts +29 -8
  387. package/src/web-components/interaction/bds-tab-panel.ts +10 -0
  388. package/src/web-components/interaction/bds-tab.spec.ts +25 -0
  389. package/src/web-components/interaction/bds-tab.ts +20 -5
  390. package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
  391. package/src/web-components/interaction/bds-tabs.ts +42 -1
  392. package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
  393. package/src/web-components/interaction/bds-toast-provider.ts +50 -21
  394. package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
  395. package/src/web-components/interaction/bds-tooltip.ts +100 -5
  396. package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
  397. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
  398. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
  399. package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
  400. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
  401. package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
  402. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  403. package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
  404. package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
  405. package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
  406. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
  407. package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
  408. package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
  409. package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
  410. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
  411. package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
  412. package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
  413. package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
  414. package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
  415. package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
  416. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
  417. package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
  418. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
  419. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
  420. package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
  421. package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
  422. package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
  423. package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
  424. package/src/web-components/interaction/form/bds-combobox.ts +102 -17
  425. package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
  426. package/src/web-components/interaction/form/bds-field-group.ts +167 -23
  427. package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
  428. package/src/web-components/interaction/form/bds-file-input.ts +71 -34
  429. package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
  430. package/src/web-components/interaction/form/bds-form-input.ts +27 -16
  431. package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
  432. package/src/web-components/interaction/form/bds-number-input.ts +118 -29
  433. package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
  434. package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
  435. package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
  436. package/src/web-components/interaction/form/bds-radio.ts +136 -23
  437. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
  438. package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
  439. package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
  440. package/src/web-components/interaction/form/bds-select.ts +96 -8
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +181 -33
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +142 -41
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +109 -8
  447. package/src/web-components/layout/BdsGrid.mdx +6 -2
  448. package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
  449. package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
  450. package/src/web-components/layout/bds-grid-item.ts +17 -3
  451. package/src/web-components/layout/bds-grid.spec.ts +98 -0
  452. package/src/web-components/layout/bds-grid.ts +139 -19
  453. package/src/web-components/ui/BdsAlert.mdx +6 -5
  454. package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
  455. package/src/web-components/ui/BdsAvatar.mdx +4 -2
  456. package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
  457. package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
  458. package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
  459. package/src/web-components/ui/BdsCalendar.mdx +29 -4
  460. package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
  461. package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
  462. package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
  463. package/src/web-components/ui/BdsCard.mdx +7 -3
  464. package/src/web-components/ui/BdsCard.stories.tsx +17 -1
  465. package/src/web-components/ui/BdsCarousel.mdx +40 -12
  466. package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
  467. package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
  468. package/src/web-components/ui/BdsLink.mdx +14 -2
  469. package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
  470. package/src/web-components/ui/BdsPagination.mdx +40 -1
  471. package/src/web-components/ui/BdsProgress.mdx +8 -2
  472. package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
  473. package/src/web-components/ui/BdsRating.mdx +7 -0
  474. package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
  475. package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
  476. package/src/web-components/ui/BdsTable.mdx +23 -2
  477. package/src/web-components/ui/BdsTable.stories.tsx +8 -2
  478. package/src/web-components/ui/BdsTypography.mdx +17 -1
  479. package/src/web-components/ui/bds-alert.spec.ts +17 -2
  480. package/src/web-components/ui/bds-alert.ts +31 -5
  481. package/src/web-components/ui/bds-avatar.spec.ts +29 -0
  482. package/src/web-components/ui/bds-avatar.ts +30 -12
  483. package/src/web-components/ui/bds-badge.spec.ts +5 -0
  484. package/src/web-components/ui/bds-badge.ts +11 -1
  485. package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
  486. package/src/web-components/ui/bds-breadcrumb.ts +26 -4
  487. package/src/web-components/ui/bds-button-container.spec.ts +39 -0
  488. package/src/web-components/ui/bds-button-container.ts +123 -0
  489. package/src/web-components/ui/bds-button-group.spec.ts +14 -20
  490. package/src/web-components/ui/bds-button-group.ts +13 -64
  491. package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
  492. package/src/web-components/ui/bds-calendar-range.ts +307 -0
  493. package/src/web-components/ui/bds-calendar.spec.ts +176 -0
  494. package/src/web-components/ui/bds-calendar.ts +186 -344
  495. package/src/web-components/ui/bds-card.spec.ts +32 -0
  496. package/src/web-components/ui/bds-card.ts +34 -9
  497. package/src/web-components/ui/bds-carousel.spec.ts +147 -0
  498. package/src/web-components/ui/bds-carousel.ts +113 -36
  499. package/src/web-components/ui/bds-description-list.spec.ts +15 -0
  500. package/src/web-components/ui/bds-description-list.ts +37 -3
  501. package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
  502. package/src/web-components/ui/bds-icon-wrapper.ts +16 -15
  503. package/src/web-components/ui/bds-link.spec.ts +17 -0
  504. package/src/web-components/ui/bds-link.ts +26 -11
  505. package/src/web-components/ui/bds-loading.ts +11 -1
  506. package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
  507. package/src/web-components/ui/bds-notification-banner.ts +18 -1
  508. package/src/web-components/ui/bds-pagination.spec.ts +44 -0
  509. package/src/web-components/ui/bds-pagination.ts +39 -19
  510. package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
  511. package/src/web-components/ui/bds-progress-circle.ts +17 -5
  512. package/src/web-components/ui/bds-progress.spec.ts +21 -0
  513. package/src/web-components/ui/bds-progress.ts +19 -6
  514. package/src/web-components/ui/bds-rating.spec.ts +6 -0
  515. package/src/web-components/ui/bds-rating.ts +12 -2
  516. package/src/web-components/ui/bds-section-header.spec.ts +39 -8
  517. package/src/web-components/ui/bds-section-header.ts +47 -13
  518. package/src/web-components/ui/bds-separator.ts +10 -0
  519. package/src/web-components/ui/bds-skeleton.ts +10 -0
  520. package/src/web-components/ui/bds-table.spec.ts +63 -0
  521. package/src/web-components/ui/bds-table.ts +88 -28
  522. package/src/web-components/ui/bds-typography.spec.ts +47 -0
  523. package/src/web-components/ui/bds-typography.ts +28 -10
  524. package/src/web-components/ui/calendar-styles.ts +94 -0
  525. package/dist/web-components/chunk-5TFWOPLH.js +0 -49
  526. package/dist/web-components/chunk-7FMR53BW.js +0 -127
  527. package/dist/web-components/chunk-AGKJTMJI.js +0 -56
  528. package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
  529. package/dist/web-components/chunk-IYGF6CD5.js +0 -118
  530. package/dist/web-components/chunk-QBB6ZX4R.js +0 -107
  531. package/dist/web-components/chunk-Y3POAKGS.js +0 -133
  532. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  533. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  534. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  535. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -6,12 +6,6 @@
6
6
  padding: 0;
7
7
  min-inline-size: 0;
8
8
 
9
- /* Establish an inline-size containment context so descendant rules can
10
- use @container against the FieldGroup's own width — independent of the
11
- viewport. Without this, the @container rule below would never match. */
12
- container-type: inline-size;
13
- container-name: field-group;
14
-
15
9
  /* Field count is injected by the React component as an inline style:
16
10
  `style={{ '--fieldGroup_field-count': N }}`. Read with a `1` fallback
17
11
  so the calc below stays well-formed if the property is ever missing
@@ -43,7 +37,8 @@
43
37
  var(--fieldGroup_legend-border-block-end, 1px solid var(--bds-color_bg--subtle));
44
38
  color:
45
39
  var(--fieldGroup_legend-color, var(--bds-color_on-bg));
46
- font-family: var(--bds-font_family--body);
40
+ font-family:
41
+ var(--fieldGroup_legend-font-family, var(--bds-font_family--body));
47
42
  font-size:
48
43
  var(--fieldGroup_legend-font-size, var(--bds-font_size--heading-3));
49
44
  font-weight:
@@ -52,6 +47,13 @@
52
47
  var(--fieldGroup_legend-line-height, var(--bds-font_line-height--heading));
53
48
  }
54
49
 
50
+ /* Disabled group: <fieldset disabled> natively disables the contained
51
+ fields (each dims via its own :disabled rule); dim the legend too so the
52
+ whole group reads as disabled. */
53
+ .fieldGroup:disabled .legend {
54
+ opacity: 0.5;
55
+ }
56
+
55
57
  .fields {
56
58
  display: flex;
57
59
  flex-direction: column;
@@ -60,9 +62,9 @@
60
62
 
61
63
  /* Horizontal variant: row layout with flex-wrap. Each child claims at least
62
64
  --fieldGroup_min-field-width and grows to fill remaining space. The
63
- `flex-wrap` already handles partial wrapping; the @container rule below
64
- forces a clean single-column stack when the whole row would no longer
65
- fit cleanly. */
65
+ `flex-wrap` already handles partial wrapping; the [data-collapsed] rule
66
+ below forces a clean single-column stack when the whole row would no
67
+ longer fit cleanly. */
66
68
  .fieldGroup.--variant_horizontal .fields {
67
69
  flex-flow: row wrap;
68
70
 
@@ -78,19 +80,51 @@
78
80
  min-inline-size: 0;
79
81
  }
80
82
 
81
- /* When the FieldGroup itself is too narrow to fit a complete horizontal
82
- row of all its fields at minimum width + gaps, collapse to a clean
83
- single column. The threshold is derived automatically from the field
84
- count + min-field-width + gap (see --fieldGroup_collapse-threshold
85
- above), so the same FieldGroup adapts whether it has 2 or 6 fields,
86
- in any container no media-query wiring at the call site. */
87
- @container field-group (max-inline-size: var(--fieldGroup_collapse-threshold)) {
88
- .fieldGroup.--variant_horizontal .fields {
89
- flex-direction: column;
90
- }
83
+ /* `@container` query *conditions* cannot reference custom properties
84
+ `var()` inside a container-query feature value is invalid CSS, so the
85
+ per-instance --fieldGroup_collapse-threshold above can't drive an
86
+ `@container` rule directly. `.probe` is an invisible element whose own
87
+ inline-size resolves that same threshold through a normal property
88
+ value (where `var()` is valid). FieldGroup.tsx compares the probe's
89
+ resolved pixel width against the fieldset's own width via
90
+ ResizeObserver and toggles `[data-collapsed]` — see the effect there
91
+ for the full mechanism.
92
+
93
+ `.probeContainer` is a zero-size, overflow-clipped box: `.probe`'s
94
+ inline-size is intentionally larger than the fieldset whenever the
95
+ layout should collapse, so it must be fully contained here — never
96
+ `position: absolute` directly against the page — or it would visually
97
+ poke out of the FieldGroup and could grow the document's scrollable
98
+ area. Clipping only affects paint/scroll, not `.probe`'s own resolved
99
+ box size, so ResizeObserver still reports its true width. */
100
+ .probeContainer {
101
+ position: relative;
102
+ inline-size: 0;
103
+ block-size: 0;
104
+ overflow: hidden;
105
+ }
106
+
107
+ .probe {
108
+ position: absolute;
109
+ inset-block-start: 0;
110
+ inset-inline-start: 0;
111
+ block-size: 0;
112
+ inline-size: var(--fieldGroup_collapse-threshold);
113
+ visibility: hidden;
114
+ }
115
+
116
+ /* When JS determines the FieldGroup itself is too narrow to fit a
117
+ complete horizontal row of all its fields at minimum width + gaps,
118
+ collapse to a clean single column. The threshold is derived
119
+ automatically from the field count + min-field-width + gap (see
120
+ --fieldGroup_collapse-threshold above), so the same FieldGroup adapts
121
+ whether it has 2 or 6 fields, in any container — no media-query wiring
122
+ at the call site. */
123
+ .fieldGroup.--variant_horizontal[data-collapsed='true'] .fields {
124
+ flex-direction: column;
125
+ }
91
126
 
92
- .fieldGroup.--variant_horizontal .fields > * {
93
- flex: 1 1 auto;
94
- }
127
+ .fieldGroup.--variant_horizontal[data-collapsed='true'] .fields > * {
128
+ flex: 1 1 auto;
95
129
  }
96
130
  }
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
- import { render, screen } from '@testing-library/react';
3
+ import { act, render, screen } from '@testing-library/react';
4
4
  import { FieldGroup } from './FieldGroup';
5
5
 
6
6
  const cssSource = readFileSync(
@@ -93,9 +93,9 @@ describe('FieldGroup', () => {
93
93
  </FieldGroup>,
94
94
  );
95
95
  let fieldset = screen.getByRole('group') as HTMLFieldSetElement;
96
- // The CSS @container rule resolves `--fieldGroup_collapse-threshold` from
97
- // this value, so a wrong count silently makes the auto-collapse fire at
98
- // the wrong width.
96
+ // `--fieldGroup_collapse-threshold` (and, through it, the auto-collapse
97
+ // probe) is derived from this value, so a wrong count silently makes the
98
+ // auto-collapse fire at the wrong width.
99
99
  expect(fieldset.style.getPropertyValue('--fieldGroup_field-count')).toBe('3');
100
100
 
101
101
  rerender(
@@ -108,10 +108,28 @@ describe('FieldGroup', () => {
108
108
  expect(fieldset.style.getPropertyValue('--fieldGroup_field-count')).toBe('2');
109
109
  });
110
110
 
111
+ it('does not count null/false conditional children toward the field count', () => {
112
+ // Regression: `Children.count` counts every slot, including `null`/`false`
113
+ // conditional children — inflating --fieldGroup_field-count (and so the
114
+ // derived collapse threshold) for a group that only actually renders 2
115
+ // fields. `Children.toArray` drops the non-rendered slots.
116
+ render(
117
+ <FieldGroup>
118
+ <input data-testid="email" />
119
+ {false}
120
+ {null}
121
+ <input data-testid="phone" />
122
+ </FieldGroup>,
123
+ );
124
+ const fieldset = screen.getByRole('group') as HTMLFieldSetElement;
125
+ expect(fieldset.style.getPropertyValue('--fieldGroup_field-count')).toBe('2');
126
+ });
127
+
111
128
  it('renders cleanly with zero children and sets the field count to 0', () => {
112
- // Threshold calc with N=0 produces a negative max-inline-size; container
113
- // queries with a negative length never match, so the layout stays in its
114
- // declared variant. No fields, no row, no crash.
129
+ // No fields, no row, no crash — the collapse threshold with N=0 is
130
+ // degenerate (a non-positive inline-size), but that's a layout concern
131
+ // handled by the auto-collapse mechanism, not something that should
132
+ // throw at render time.
115
133
  const { container } = render(<FieldGroup legend="Empty section" />);
116
134
  const fieldset = container.querySelector('fieldset')!;
117
135
  expect(fieldset.style.getPropertyValue('--fieldGroup_field-count')).toBe('0');
@@ -127,6 +145,7 @@ describe('FieldGroup', () => {
127
145
  '--fieldGroup_gap',
128
146
  '--fieldGroup_min-field-width',
129
147
  '--fieldGroup_legend-color',
148
+ '--fieldGroup_legend-font-family',
130
149
  '--fieldGroup_legend-font-size',
131
150
  '--fieldGroup_legend-font-weight',
132
151
  '--fieldGroup_legend-line-height',
@@ -194,3 +213,106 @@ describe('FieldGroup', () => {
194
213
  expect(fieldset.disabled).toBe(true);
195
214
  });
196
215
  });
216
+
217
+ describe('FieldGroup — auto-collapse', () => {
218
+ // Regression: the CSS @container condition previously read
219
+ // `var(--fieldGroup_collapse-threshold)` directly, which is invalid inside
220
+ // an @container feature value — the documented single-column collapse
221
+ // never fired in any browser. FieldGroup now measures a hidden probe
222
+ // element (whose inline-size resolves that same threshold) against the
223
+ // fieldset's own width via ResizeObserver and toggles `data-collapsed`.
224
+ // jsdom has no ResizeObserver — install a mock that records each
225
+ // constructed observer and the elements it was told to watch, so tests can
226
+ // drive its callback directly.
227
+ class MockResizeObserver {
228
+ callback: ResizeObserverCallback;
229
+ observed: Element[] = [];
230
+
231
+ constructor(callback: ResizeObserverCallback) {
232
+ this.callback = callback;
233
+ instances.push(this);
234
+ }
235
+
236
+ observe(el: Element) {
237
+ this.observed.push(el);
238
+ }
239
+
240
+ unobserve() {}
241
+
242
+ disconnect() {}
243
+ }
244
+
245
+ let instances: MockResizeObserver[] = [];
246
+ const originalResizeObserver = globalThis.ResizeObserver;
247
+
248
+ const fireResize = (
249
+ instance: MockResizeObserver,
250
+ fieldsetWidth: number,
251
+ probeWidth: number,
252
+ ) => {
253
+ const [fieldsetEl, probeEl] = instance.observed;
254
+ act(() => {
255
+ instance.callback(
256
+ [
257
+ { target: fieldsetEl, contentRect: { width: fieldsetWidth } },
258
+ { target: probeEl, contentRect: { width: probeWidth } },
259
+ ] as unknown as ResizeObserverEntry[],
260
+ instance as unknown as ResizeObserver,
261
+ );
262
+ });
263
+ };
264
+
265
+ beforeEach(() => {
266
+ instances = [];
267
+ (globalThis as unknown as { ResizeObserver: typeof ResizeObserver }).ResizeObserver =
268
+ MockResizeObserver as unknown as typeof ResizeObserver;
269
+ });
270
+
271
+ afterEach(() => {
272
+ if (originalResizeObserver) {
273
+ (globalThis as unknown as { ResizeObserver: typeof ResizeObserver }).ResizeObserver =
274
+ originalResizeObserver;
275
+ } else {
276
+ delete (globalThis as unknown as Record<string, unknown>).ResizeObserver;
277
+ }
278
+ });
279
+
280
+ it('sets data-collapsed when the fieldset is narrower than the derived threshold', () => {
281
+ const { container } = render(
282
+ <FieldGroup>
283
+ <input />
284
+ <input />
285
+ </FieldGroup>,
286
+ );
287
+ const fieldset = container.querySelector('fieldset')!;
288
+ expect(instances).toHaveLength(1);
289
+
290
+ fireResize(instances[0], 200, 400);
291
+ expect(fieldset.getAttribute('data-collapsed')).toBe('true');
292
+ });
293
+
294
+ it('clears data-collapsed once the fieldset is wide enough for the row', () => {
295
+ const { container } = render(
296
+ <FieldGroup>
297
+ <input />
298
+ <input />
299
+ </FieldGroup>,
300
+ );
301
+ const fieldset = container.querySelector('fieldset')!;
302
+
303
+ fireResize(instances[0], 200, 400);
304
+ expect(fieldset.getAttribute('data-collapsed')).toBe('true');
305
+
306
+ fireResize(instances[0], 600, 400);
307
+ expect(fieldset.hasAttribute('data-collapsed')).toBe(false);
308
+ });
309
+
310
+ it('never observes or collapses the vertical variant', () => {
311
+ render(
312
+ <FieldGroup variant="vertical">
313
+ <input />
314
+ </FieldGroup>,
315
+ );
316
+ expect(instances).toHaveLength(0);
317
+ });
318
+ });
@@ -1,4 +1,12 @@
1
- import { Children, CSSProperties, FieldsetHTMLAttributes, ReactNode } from 'react';
1
+ import {
2
+ Children,
3
+ CSSProperties,
4
+ FieldsetHTMLAttributes,
5
+ ReactNode,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
2
10
  import { cn } from '@boostdev/design-system-foundation';
3
11
  import css from './FieldGroup.module.css';
4
12
  import type { WithClassName } from '../../../../types';
@@ -7,9 +15,8 @@ import type { WithClassName } from '../../../../types';
7
15
  * Layout variants for FieldGroup.
8
16
  *
9
17
  * - `horizontal` (default) — fields flow into a row, each claiming at least
10
- * `--fieldGroup_min-field-width`. When the container's inline-size is too
11
- * narrow to fit the row, a CSS container query collapses the layout to a
12
- * single column.
18
+ * `--fieldGroup_min-field-width`. When the FieldGroup's own inline-size is
19
+ * too narrow to fit the row, the layout collapses to a single column.
13
20
  * - `vertical` — fields always stack one per row.
14
21
  */
15
22
  export type FieldGroupVariant = 'horizontal' | 'vertical';
@@ -26,10 +33,13 @@ export interface FieldGroupProps
26
33
  legend?: ReactNode;
27
34
  }
28
35
 
36
+ const getInlineSize = (entry: ResizeObserverEntry): number =>
37
+ entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
38
+
29
39
  /**
30
40
  * `FieldGroup` semantically groups related form fields under a single
31
- * `<fieldset>` and lays them out either horizontally (with container-query
32
- * fallback to vertical) or vertically.
41
+ * `<fieldset>` and lays them out either horizontally (collapsing to a
42
+ * single column when the FieldGroup itself is too narrow) or vertically.
33
43
  */
34
44
  export function FieldGroup({
35
45
  variant = 'horizontal',
@@ -39,26 +49,76 @@ export function FieldGroup({
39
49
  style,
40
50
  ...rest
41
51
  }: Readonly<FieldGroupProps>) {
42
- // Surface the field count to CSS so the @container collapse threshold
43
- // scales with the actual content. The CSS uses
52
+ // Surface the field count to CSS so the auto-derived collapse threshold
53
+ // scales with the actual content:
44
54
  // N * --fieldGroup_min-field-width + (N - 1) * --fieldGroup_gap
45
- // which is exactly the inline-size required to lay all fields out at
46
- // their minimum width with the configured gap. When the FieldGroup's own
47
- // inline-size drops below that, the layout collapses to a single column.
48
- const fieldCount = Children.count(children);
55
+ // is exactly the inline-size required to lay all fields out at their
56
+ // minimum width with the configured gap. `Children.toArray` (not
57
+ // `Children.count`, which also counts `null`/`false` conditional
58
+ // children) so only actually-rendered children inflate the threshold.
59
+ const fieldCount = Children.toArray(children).length;
49
60
  const styleWithCount = {
50
61
  ...style,
51
62
  '--fieldGroup_field-count': fieldCount,
52
63
  } as CSSProperties;
53
64
 
65
+ const fieldsetRef = useRef<HTMLFieldSetElement>(null);
66
+ const probeRef = useRef<HTMLDivElement>(null);
67
+ const [isCollapsed, setIsCollapsed] = useState(false);
68
+
69
+ // `@container` query conditions cannot reference custom properties, so the
70
+ // per-instance collapse threshold (which the CSS still computes via
71
+ // `--fieldGroup_collapse-threshold`, see FieldGroup.module.css) can't drive
72
+ // a CSS `@container` rule directly. Instead, `.probe` is an invisible
73
+ // element whose own inline-size resolves that threshold through the
74
+ // browser's normal layout/cascade; comparing its measured pixel width
75
+ // against the fieldset's own width — via ResizeObserver, so it stays
76
+ // correct across resizes and field-count changes — tells us when to
77
+ // collapse. `[data-collapsed="true"]` on the fieldset drives the CSS.
78
+ useEffect(() => {
79
+ const fieldsetEl = fieldsetRef.current;
80
+ const probeEl = probeRef.current;
81
+ if (
82
+ variant !== 'horizontal' ||
83
+ !fieldsetEl ||
84
+ !probeEl ||
85
+ typeof ResizeObserver === 'undefined'
86
+ ) {
87
+ setIsCollapsed(false);
88
+ return;
89
+ }
90
+
91
+ let fieldsetSize = 0;
92
+ let thresholdSize = 0;
93
+
94
+ const observer = new ResizeObserver(entries => {
95
+ for (const entry of entries) {
96
+ const size = getInlineSize(entry);
97
+ if (entry.target === fieldsetEl) fieldsetSize = size;
98
+ else if (entry.target === probeEl) thresholdSize = size;
99
+ }
100
+ setIsCollapsed(fieldsetSize < thresholdSize);
101
+ });
102
+
103
+ observer.observe(fieldsetEl);
104
+ observer.observe(probeEl);
105
+
106
+ return () => observer.disconnect();
107
+ }, [variant]);
108
+
54
109
  return (
55
110
  <fieldset
56
111
  {...rest}
112
+ ref={fieldsetRef}
57
113
  style={styleWithCount}
58
114
  className={cn(css.fieldGroup, css[`--variant_${variant}`], className)}
115
+ data-collapsed={variant === 'horizontal' && isCollapsed ? 'true' : undefined}
59
116
  >
60
117
  {legend !== undefined && <legend className={css.legend}>{legend}</legend>}
61
118
  <div className={css.fields}>{children}</div>
119
+ <div className={css.probeContainer}>
120
+ <div ref={probeRef} className={css.probe} />
121
+ </div>
62
122
  </fieldset>
63
123
  );
64
124
  }
@@ -40,9 +40,17 @@ File upload input with styled trigger button, hint, and error support.
40
40
  </thead>
41
41
  <tbody>
42
42
  <tr><td>`--fileInput_color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
43
- <tr><td>`--fileInput_border-color`</td><td>`currentcolor`</td><td>Drop zone border colour</td></tr>
43
+ <tr><td>`--fileInput_border`</td><td>`2px dashed currentcolor`</td><td>Drop zone border (resting state)</td></tr>
44
+ <tr><td>`--fileInput_radius`</td><td>`var(--bds-border_radius--s)`</td><td>Drop zone corner radius</td></tr>
45
+ <tr><td>`--fileInput_border-color`</td><td>`currentcolor`</td><td>Drop zone border colour when focused (focus-within)</td></tr>
44
46
  <tr><td>`--fileInput_border-color--hover`</td><td>`var(--bds-color_interactive)`</td><td>Drop zone border colour on hover</td></tr>
45
47
  <tr><td>`--fileInput_bg--hover`</td><td>`rgb(from var(--bds-color_interactive) r g b / 4%)`</td><td>Drop zone background on hover</td></tr>
48
+ <tr><td>`--fileInput_bg--dragging`</td><td>`rgb(from currentcolor r g b / 8%)`</td><td>Drop zone background while a file is dragged over it</td></tr>
49
+ <tr><td>`--fileInput_gap`</td><td>`var(--bds-space_xs)`</td><td>Gap between the icon and prompt text inside the drop zone</td></tr>
50
+ <tr><td>`--fileInput_padding`</td><td>`var(--bds-space_l) var(--bds-space_m)`</td><td>Padding of the drop zone</td></tr>
51
+ <tr><td>`--fileInput_label-margin`</td><td>`var(--bds-space_xs)`</td><td>Space below the field label</td></tr>
52
+ <tr><td>`--fileInput_font-size`</td><td>`var(--bds-font_size--body)`</td><td>Font size of the prompt text</td></tr>
53
+ <tr><td>`--fileInput_font-size--hint`</td><td>`var(--bds-font_size--body--s)`</td><td>Font size of the accept-type hint</td></tr>
46
54
  </tbody>
47
55
  </table>
48
56
 
@@ -9,7 +9,7 @@
9
9
  align-items: center;
10
10
  font-family: var(--bds-font_family--body);
11
11
  color: var(--bds-color_on-bg);
12
- margin-block-end: var(--bds-space_xs);
12
+ margin-block-end: var(--fileInput_label-margin, var(--bds-space_xs));
13
13
  }
14
14
 
15
15
  .dropZone {
@@ -17,10 +17,10 @@
17
17
  flex-direction: column;
18
18
  align-items: center;
19
19
  justify-content: center;
20
- gap: var(--bds-space_xs);
21
- padding: var(--bds-space_l) var(--bds-space_m);
22
- border: 2px dashed currentcolor;
23
- border-radius: var(--bds-border_radius--s);
20
+ gap: var(--fileInput_gap, var(--bds-space_xs));
21
+ padding: var(--fileInput_padding, var(--bds-space_l) var(--bds-space_m));
22
+ border: var(--fileInput_border, 2px dashed currentcolor);
23
+ border-radius: var(--fileInput_radius, var(--bds-border_radius--s));
24
24
  color: var(--fileInput_color, var(--bds-color_on-bg));
25
25
  cursor: pointer;
26
26
  text-align: center;
@@ -35,7 +35,7 @@
35
35
 
36
36
  .isDragging {
37
37
  border-color: currentcolor;
38
- background-color: rgb(from currentcolor r g b / 8%);
38
+ background-color: var(--fileInput_bg--dragging, rgb(from currentcolor r g b / 8%));
39
39
  }
40
40
 
41
41
  .hasError {
@@ -54,7 +54,7 @@
54
54
  }
55
55
 
56
56
  .prompt {
57
- font-size: var(--bds-font_size--body);
57
+ font-size: var(--fileInput_font-size, var(--bds-font_size--body));
58
58
  color: var(--bds-color_on-bg);
59
59
  }
60
60
 
@@ -64,7 +64,7 @@
64
64
  }
65
65
 
66
66
  .acceptHint {
67
- font-size: var(--bds-font_size--body--s);
67
+ font-size: var(--fileInput_font-size--hint, var(--bds-font_size--body--s));
68
68
  color: var(--bds-color_on-bg);
69
69
  }
70
70
 
@@ -1,8 +1,44 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { FileInput } from './FileInput';
4
4
 
5
+ // jsdom does not implement DataTransfer (https://github.com/jsdom/jsdom/issues/1568); the
6
+ // drop handler only needs an `items.add`/`files` surface, so a minimal stub is enough.
7
+ class MockDataTransfer {
8
+ private addedFiles: File[] = [];
9
+ items = {
10
+ add: (file: File) => {
11
+ this.addedFiles.push(file);
12
+ },
13
+ };
14
+ get files() {
15
+ return this.addedFiles;
16
+ }
17
+ }
18
+
19
+ function getDropZone() {
20
+ const input = document.querySelector('input[type="file"]') as HTMLInputElement;
21
+ // input.files has no public setter in jsdom; give this instance a writable own property
22
+ // so the component's `inputRef.current.files = ...` assignment doesn't throw.
23
+ Object.defineProperty(input, 'files', { configurable: true, writable: true, value: null });
24
+ return input.closest('label') as HTMLLabelElement;
25
+ }
26
+
27
+ function fireDragLeave(target: HTMLElement, relatedTarget: EventTarget | null) {
28
+ const event = new Event('dragleave', { bubbles: true, cancelable: true });
29
+ Object.defineProperty(event, 'relatedTarget', { value: relatedTarget });
30
+ fireEvent(target, event);
31
+ }
32
+
5
33
  describe('FileInput', () => {
34
+ beforeEach(() => {
35
+ vi.stubGlobal('DataTransfer', MockDataTransfer);
36
+ });
37
+
38
+ afterEach(() => {
39
+ vi.unstubAllGlobals();
40
+ });
41
+
6
42
  it('renders with label', () => {
7
43
  render(<FileInput label="Upload file" name="file" />);
8
44
  expect(screen.getByText('Upload file')).toBeInTheDocument();
@@ -50,4 +86,56 @@ describe('FileInput', () => {
50
86
  await userEvent.upload(input, file);
51
87
  expect(onChange).toHaveBeenCalledOnce();
52
88
  });
89
+
90
+ it('keeps only the first dropped file when multiple is not set', () => {
91
+ const onChange = vi.fn();
92
+ render(<FileInput label="Upload" name="file" onChange={onChange} />);
93
+ const dropZone = getDropZone();
94
+ const fileA = new File(['a'], 'a.txt', { type: 'text/plain' });
95
+ const fileB = new File(['b'], 'b.txt', { type: 'text/plain' });
96
+
97
+ fireEvent.drop(dropZone, { dataTransfer: { files: [fileA, fileB] } });
98
+
99
+ expect(onChange).toHaveBeenCalledOnce();
100
+ const received = onChange.mock.calls[0][0] as unknown as File[];
101
+ expect(Array.from(received).map(f => f.name)).toEqual(['a.txt']);
102
+ expect(screen.getByText('a.txt')).toBeInTheDocument();
103
+ });
104
+
105
+ it('keeps every dropped file when multiple is set', () => {
106
+ const onChange = vi.fn();
107
+ render(<FileInput label="Upload" name="file" multiple onChange={onChange} />);
108
+ const dropZone = getDropZone();
109
+ const fileA = new File(['a'], 'a.txt', { type: 'text/plain' });
110
+ const fileB = new File(['b'], 'b.txt', { type: 'text/plain' });
111
+
112
+ fireEvent.drop(dropZone, { dataTransfer: { files: [fileA, fileB] } });
113
+
114
+ expect(onChange).toHaveBeenCalledOnce();
115
+ const received = onChange.mock.calls[0][0] as unknown as File[];
116
+ expect(Array.from(received).map(f => f.name)).toEqual(['a.txt', 'b.txt']);
117
+ });
118
+
119
+ it('keeps the dragging state when the drag leaves into a child element', () => {
120
+ render(<FileInput label="Upload" name="file" />);
121
+ const dropZone = getDropZone();
122
+ const icon = dropZone.querySelector('svg') as SVGElement;
123
+
124
+ fireEvent.dragOver(dropZone);
125
+ expect(dropZone).toHaveClass('isDragging');
126
+
127
+ fireDragLeave(dropZone, icon);
128
+ expect(dropZone).toHaveClass('isDragging');
129
+ });
130
+
131
+ it('clears the dragging state when the drag leaves the drop zone entirely', () => {
132
+ render(<FileInput label="Upload" name="file" />);
133
+ const dropZone = getDropZone();
134
+
135
+ fireEvent.dragOver(dropZone);
136
+ expect(dropZone).toHaveClass('isDragging');
137
+
138
+ fireDragLeave(dropZone, null);
139
+ expect(dropZone).not.toHaveClass('isDragging');
140
+ });
53
141
  });
@@ -61,7 +61,9 @@ export function FileInput({
61
61
  setIsDragging(false);
62
62
  if (disabled) return;
63
63
  const dt = new DataTransfer();
64
- Array.from(e.dataTransfer.files).filter(isFileAccepted).forEach(f => dt.items.add(f));
64
+ const accepted = Array.from(e.dataTransfer.files).filter(isFileAccepted);
65
+ const filesToAdd = multiple ? accepted : accepted.slice(0, 1);
66
+ filesToAdd.forEach(f => dt.items.add(f));
65
67
  if (inputRef.current) inputRef.current.files = dt.files;
66
68
  handleFiles(dt.files.length > 0 ? dt.files : null);
67
69
  };
@@ -71,7 +73,9 @@ export function FileInput({
71
73
  if (!disabled) setIsDragging(true);
72
74
  };
73
75
 
74
- const handleDragLeave = () => setIsDragging(false);
76
+ const handleDragLeave = (e: DragEvent<HTMLLabelElement>) => {
77
+ if (!e.currentTarget.contains(e.relatedTarget as Node)) setIsDragging(false);
78
+ };
75
79
 
76
80
  return (
77
81
  <InputContainer {...rest} className={cn(css.formGroup, className)}>
@@ -33,18 +33,21 @@ Text input field with label, optional hint, and error state. The label and input
33
33
 
34
34
  ## CSS variables
35
35
 
36
-
37
36
  <table>
38
37
  <thead>
39
- <tr><th>Variable</th><th>Description</th></tr>
38
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
40
39
  </thead>
41
40
  <tbody>
42
- <tr><td>`--input_border-color`</td><td>Border colour (set by state — focus, error)</td></tr>
43
- <tr><td>`--input_color`</td><td>Background colour</td></tr>
44
- <tr><td>`--input_on-color`</td><td>Text colour</td></tr>
41
+ <tr><td>`--formInput_color_bg`</td><td>`var(--bds-color_bg)`</td><td>Background colour</td></tr>
42
+ <tr><td>`--formInput_color`</td><td>`var(--bds-color_on-bg)`</td><td>Text colour</td></tr>
43
+ <tr><td>`--formInput_border`</td><td>`none`</td><td>Border</td></tr>
44
+ <tr><td>`--formInput_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Corner radius</td></tr>
45
+ <tr><td>`--formInput_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Box shadow</td></tr>
45
46
  </tbody>
46
47
  </table>
47
48
 
49
+ `--input_*` (e.g. `--input_border`, `--input_radius`, `--input_shadow`, `--input_color`, `--input_color_bg`) are deprecated aliases for the `--formInput_*` tokens above — they still work but `--formInput_*` is the documented, forward-compatible name.
50
+
48
51
 
49
52
  ## Accessibility
50
53
 
@@ -52,3 +55,4 @@ Text input field with label, optional hint, and error state. The label and input
52
55
  - Error message uses `aria-describedby` to associate with the input
53
56
  - `required` sets both the HTML attribute and a visual indicator
54
57
  - `disabled` removes the field from the tab order
58
+ - Set the native `aria-label` attribute to add an accessible name beyond the visible label (e.g. for extra context read by assistive tech). The `ariaLabel` prop is `@deprecated` — it still works, but prefer `aria-label`.