@boostdev/design-system-components 2.8.0 → 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 (534) 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-OF6FFGOT.js → chunk-3AY2YKCM.js} +77 -21
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-UUKSDCYS.js → chunk-47AT5KZD.js} +87 -15
  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-QLT6MTZF.js → chunk-AIBOHNDU.js} +10 -0
  23. package/dist/web-components/{chunk-X3NQMBAQ.js → chunk-AP4RDUL2.js} +14 -3
  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-ARMKC4BQ.js → chunk-JH6LQKLK.js} +77 -5
  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-FSZRP43R.js → chunk-WFVKU4GX.js} +21 -10
  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-ORC56MDY.js → chunk-XIZWAWMJ.js} +45 -6
  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 +55 -8
  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 +101 -16
  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 +135 -22
  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 +95 -7
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +154 -26
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +87 -20
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +108 -7
  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 +29 -11
  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 +109 -32
  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 +15 -14
  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 +10 -0
  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 +17 -4
  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-7YURXHTS.js +0 -147
  526. package/dist/web-components/chunk-A3RLT3AS.js +0 -49
  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-DZOEPVFT.js +0 -107
  530. package/dist/web-components/chunk-JYYRJS2D.js +0 -118
  531. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  532. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  533. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  534. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -52,6 +52,23 @@ Or in HTML without a bundler:
52
52
 
53
53
  <ArgTypes of={Stories} />
54
54
 
55
+ ## CSS Custom Properties — `<bds-dropdown-menu>`
56
+
57
+ | Property | Default | Description |
58
+ |----------|---------|-------------|
59
+ | `--dropdown_border` | `none` | Real CSS `border` on the panel (in addition to the bdc inset outline) |
60
+ | `--dropdown_radius` | `--bdc-outline_radius` (`--bds-border_radius--s`) | Panel corner radius |
61
+ | `--dropdown_color` | `--bds-color_bg` | Panel background colour |
62
+ | `--dropdown_on-color` | `--bds-color_on-bg` | Panel text colour |
63
+ | `--dropdown_shadow` | `--bds-shadow_m` | Panel elevation shadow, layered behind the inset border |
64
+
65
+ ## CSS Custom Properties — `<bds-dropdown-menu-item>`
66
+
67
+ | Property | Default | Description |
68
+ |----------|---------|-------------|
69
+ | `--dropdown_item-bg--hover` | `--bds-color_bg--subtle` | Item background colour on hover |
70
+ | `--dropdown_item-color--hover` | `--bds-color_interactive_on-bg` | Item text colour on hover |
71
+
55
72
  ## Attributes — `<bds-dropdown-menu>`
56
73
 
57
74
  | Attribute | Type | Default | Description |
@@ -101,8 +118,10 @@ Or in HTML without a bundler:
101
118
 
102
119
  ## Accessibility
103
120
 
121
+ - The slotted trigger receives `aria-haspopup="menu"`, `aria-controls` (pointing at the panel's id), and `aria-expanded` kept in sync with open/close state
104
122
  - The menu panel has `role="menu"` and `aria-hidden` toggled on open/close
105
123
  - Each item renders a `<button role="menuitem">`
106
- - Keyboard navigation: ArrowDown / ArrowUp moves between enabled items; Escape closes and returns focus to the trigger; Tab closes the menu
124
+ - Keyboard navigation: ArrowDown / ArrowUp moves between enabled items; Home / End jump to the first / last enabled item; Escape closes and returns focus to the trigger; Tab closes the menu
107
125
  - Disabled items are excluded from keyboard navigation
108
126
  - Focus returns to the trigger element on close
127
+ - The panel is an absolutely-positioned shadow-DOM element (not the native Popover API), so it can be clipped by an ancestor's `overflow` or stacking context
@@ -45,12 +45,13 @@ import '@boostdev/design-system-components/web-components';
45
45
  |-----------|------|---------|-------------|
46
46
  | `placement` | `"bottom" \| "top" \| "left" \| "right"` | `"bottom"` | Preferred panel position relative to the trigger |
47
47
  | `open` | boolean | `false` | Controls panel visibility; reflects on the element |
48
+ | `aria-label` | string | — | Accessible name for the panel. When set, the panel gets `role="region"` and this label; when omitted, neither is rendered |
48
49
 
49
50
  ## Slots
50
51
 
51
52
  | Slot | Description |
52
53
  |------|-------------|
53
- | `trigger` | Element that opens and closes the panel on click |
54
+ | `trigger` | Element that opens and closes the panel on click. Automatically receives `aria-haspopup="true"`, `aria-controls` (pointing at the panel) and `aria-expanded` synced to the open state |
54
55
  | `(default)` | Panel content |
55
56
 
56
57
  ## Events
@@ -65,6 +66,7 @@ import '@boostdev/design-system-components/web-components';
65
66
  - Clicking the trigger toggles the panel open/closed
66
67
  - Pressing `Escape` closes an open panel
67
68
  - Clicking outside the element closes the panel
69
+ - The panel is `position: fixed`, so it escapes `overflow: hidden`/`scroll` ancestors and stacking contexts instead of being clipped
68
70
  - CSS Anchor Positioning automatically tries fallback placements (`@position-try`) when the preferred placement would overflow the viewport
69
71
  - Each instance gets a unique CSS anchor name — multiple popovers on the same page don't interfere
70
72
 
@@ -114,7 +116,9 @@ popover.addEventListener('bds-close', () => console.log('closed'));
114
116
 
115
117
  ## Accessibility
116
118
 
117
- - The panel has `role="region"` and `aria-label="Popover"`
118
- - The trigger element retains its own role (e.g. `<button>`)no ARIA is added by the component
119
+ - The trigger element automatically receives `aria-haspopup="true"`, `aria-controls` (referencing the panel) and `aria-expanded` — kept in sync as the panel opens and closes
120
+ - The panel gets `role="region"` and the given label only when you set `aria-label` on `<bds-popover>` — omit it if the trigger's own accessible name already describes the panel content, to avoid a meaningless landmark announcement
121
+ - The trigger element otherwise retains its own role (e.g. `<button>`) — no other ARIA is added by the component
119
122
  - For menus, use a `<nav>` or `role="menu"` inside the default slot as appropriate
123
+ - When the panel closes (`Escape` or an outside click) while focus was inside the popover, focus returns to the trigger; an outside click that moves focus elsewhere is left alone
120
124
  - The component does not trap focus — use `<bds-dialog>` for modal, focus-trapped overlays
@@ -57,6 +57,18 @@ Or in HTML without a bundler:
57
57
  |------|-------------|
58
58
  | `(default)` | Link text; defaults to `"Skip to main content"` when no content is provided |
59
59
 
60
+ ## CSS Custom Properties
61
+
62
+ | Property | Description |
63
+ |----------|-------------|
64
+ | `--skip-link_inset` | Distance from the block-start/inline-start edge when focused (default: `var(--bds-space_s)`) |
65
+ | `--skip-link_z-index` | Stacking order so the link rises above sticky headers/nav (default: `var(--bds-z-index_xl)`) |
66
+ | `--skip-link_padding` | Padding of the link surface (default: `var(--bds-space_xs) var(--bds-space_m)`) |
67
+ | `--skip-link_font-size` | Font size of the link text (default: `var(--bds-font_size--body)`) |
68
+ | `--skip-link_radius` | Corner radius (default: `var(--bds-border_radius--s)`) |
69
+ | `--skip-link_border` | Border around the link surface (default: `none`) |
70
+ | `--skip-link_shadow` | Extra shadow appended after the bdc focus-border box-shadow (default: `var(--bdc-outline_shadow)`) |
71
+
60
72
  ## Usage in plain HTML
61
73
 
62
74
  Place `<bds-skip-link>` as the very first element inside `<body>`:
@@ -86,5 +98,6 @@ With custom label text:
86
98
  - Visually hidden via `transform: translateY(...)` until focused — avoids `display: none` and `visibility: hidden` which remove the element from the accessibility tree
87
99
  - Becomes visible on `:focus-visible`, ensuring it is discoverable by keyboard and switch-access users
88
100
  - Uses an `<a>` element so it is announced as a link by screen readers
89
- - `z-index: 9999` ensures it appears above all other content when focused
101
+ - `z-index: var(--skip-link_z-index, var(--bds-z-index_xl))` ensures it rises above sticky headers/navigation when focused
90
102
  - Satisfies WCAG 2.4.1 Bypass Blocks (Level A)
103
+ - The slide-in transition respects `prefers-reduced-motion: reduce`
@@ -86,6 +86,22 @@ Or in HTML without a bundler:
86
86
  |-------|----------|---------|-------------|
87
87
  | `bds-tab-select` | `<bds-tab>` | Yes | Fired when a tab is clicked; handled internally by `<bds-tabs>` |
88
88
 
89
+ ## CSS Custom Properties
90
+
91
+ Resting and state colours are layered overrides: a top-level override (e.g.
92
+ `--tab_color`) always wins over the selected/hover default for that state
93
+ (e.g. `--tab_color--active`), which falls back to the design token. Set
94
+ `--tab_color` on `<bds-tab>` (or an ancestor) to theme the resting colour
95
+ across every state; set `--tab_color--active` to theme only the selected tab.
96
+
97
+ | Property | Description |
98
+ |----------|-------------|
99
+ | `--tab_color` | Tab text colour override (all states) |
100
+ | `--tab_border-color` | Tab bottom-border colour override (all states) |
101
+ | `--tab_color--active` | Text colour override for the selected tab |
102
+ | `--tab_border-color--active` | Bottom-border colour override for the selected tab |
103
+ | `--tab_color--hover` | Text colour override on hover (unselected, enabled tabs) |
104
+
89
105
  ## Usage in plain HTML
90
106
 
91
107
  Tabs and panels are matched by DOM order (first tab → first panel, etc.):
@@ -112,6 +128,7 @@ Tabs and panels are matched by DOM order (first tab → first panel, etc.):
112
128
 
113
129
  - The tab list container has `role="tablist"`
114
130
  - Each `<bds-tab>` renders a `<button role="tab">` with `aria-selected` and the correct `tabindex` (0 for active, -1 for others)
131
+ - `<bds-tabs>` also assigns `role="tab"`, a generated `id`, and `aria-controls` (pointing at the matching panel's `id`) on each `<bds-tab>` **host** element — and `role="tabpanel"`, a generated `id`, and `aria-labelledby` (pointing back at the tab's `id`) on each `<bds-tab-panel>` **host** element. These live on the light-DOM host elements (not inside either element's shadow root) so the `id` references resolve correctly across the shared light-DOM tree. A consumer-supplied `id` on `<bds-tab>` / `<bds-tab-panel>` is preserved rather than overwritten.
115
132
  - Each `<bds-tab-panel>` receives `hidden` when not active
116
- - Keyboard navigation: ArrowRight / ArrowLeft cycles through enabled tabs; Home / End jumps to first / last enabled tab
133
+ - Keyboard navigation: ArrowRight / ArrowLeft cycles through enabled tabs; Home / End jumps to first / last enabled tab. The listener only reacts to keys that originate from a `<bds-tab>` — typing inside tab-panel content (an `<input>`, slider, etc.) never hijacks focus or switches the active tab.
117
134
  - Disabled tabs are excluded from keyboard navigation
@@ -89,6 +89,24 @@ document.dispatchEvent(new CustomEvent('bds-show-toast', {
89
89
 
90
90
  ## Accessibility
91
91
 
92
- - Each toast renders with `role="status"` + `aria-live="polite"` by default so screen readers announce it without interrupting the user
93
- - `error` toasts use `role="alert"` + `aria-live="assertive"` for immediate announcement
94
- - The dismiss button has an accessible label
92
+ - Every toast (all variants, including `error`) renders with `role="status"` + `aria-live="polite"` so screen readers announce it without interrupting the user — this matches the `ToastProvider` React component
93
+ - The dismiss button has an accessible label and a >=44&times;44px hit area (the visible icon stays 16px; the click target is expanded with a transparent `::after` pseudo-element)
94
+
95
+ ## CSS Custom Properties
96
+
97
+ | Property | Default | Description |
98
+ |----------|---------|-------------|
99
+ | `--toast_color` | _(unset)_ | Public override for the toast background colour — wins over the per-variant default on every variant |
100
+ | `--toast_on-color` | _(unset)_ | Public override for the toast text colour — wins over the per-variant default on every variant |
101
+ | `--toast_radius` | `--bds-border_radius--s` | Toast corner radius |
102
+ | `--toast_shadow` | `--bds-shadow_s` | Toast elevation shadow |
103
+ | `--toast_color--success` | `--bds-color_success` | Background colour for `success` toasts |
104
+ | `--toast_on-color--success` | `--bds-color_on-success` | Text colour for `success` toasts |
105
+ | `--toast_color--warning` | `--bds-color_warning` | Background colour for `warning` toasts |
106
+ | `--toast_on-color--warning` | `--bds-color_on-warning` | Text colour for `warning` toasts |
107
+ | `--toast_color--info` | `--bds-color_bg--subtle` | Background colour for `info` toasts |
108
+ | `--toast_on-color--info` | `--bds-color_on-bg--subtle` | Text colour for `info` toasts |
109
+ | `--toast_color--error` | `--bds-color_error` | Background colour for `error` toasts |
110
+ | `--toast_on-color--error` | `--bds-color_on-error` | Text colour for `error` toasts |
111
+
112
+ Setting `--toast_color`/`--toast_on-color` on `<bds-toast-provider>` (or an ancestor) overrides every variant at once; the `--toast_color--<variant>`/`--toast_on-color--<variant>` tokens only change that one variant's default.
@@ -96,9 +96,9 @@ Or in HTML without a bundler:
96
96
 
97
97
  ## Accessibility
98
98
 
99
- - The tooltip panel has `role="tooltip"`
100
- - Visibility is controlled by `:hover` and `:focus-within` no JavaScript required
101
- - The tooltip is rendered adjacent to the trigger inside the same shadow root, so screen readers can read it via proximity
102
- - Note: `aria-describedby` cannot cross shadow DOM boundaries; if your project requires explicit ARIA association, use `<bds-popover>` instead
99
+ - `content` is programmatically associated with the slotted trigger: the element appends a visually-hidden light-DOM `<span>` holding the tooltip text and sets `aria-describedby` on every slotted (non-hidden) child to point at it. This keeps the id reference inside the light DOM's tree scope, so it resolves correctly — `aria-describedby` cannot reference an id that lives inside this element's shadow root, since id references only resolve within the same tree scope.
100
+ - The association is re-computed whenever `content` changes or the default slot's assigned nodes change (`slotchange`), and existing `aria-describedby` values set by the consumer on the trigger are preserved (the tooltip's id is appended, not overwritten).
101
+ - The description is available to assistive technology at all times, independent of hover/focus it does not depend on the visual tooltip being shown.
102
+ - The visible tooltip bubble (`role="tooltip"`) is `aria-hidden="true"`; it is purely the visual presentation shown on `:hover`/`:focus-within` and is not itself the accessible description.
103
103
  - `pointer-events: none` on the tooltip prevents it from interfering with mouse events
104
104
  - Fade transition respects `prefers-reduced-motion`
@@ -27,6 +27,16 @@ import { LitElement, css, html } from 'lit';
27
27
  */
28
28
  export class BdsAccordionItem extends LitElement {
29
29
  static styles = css`
30
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
31
+ here; otherwise elements with an explicit size + padding size as
32
+ content-box and layouts drift from the React (light-DOM) versions. */
33
+ :host,
34
+ *,
35
+ *::before,
36
+ *::after {
37
+ box-sizing: border-box;
38
+ }
39
+
30
40
  :host {
31
41
  display: block;
32
42
  border-block-end: 1px solid var(--bds-color_bg--subtle);
@@ -70,4 +70,45 @@ describe('bds-accordion', () => {
70
70
  expect(items[0].open).toBe(false);
71
71
  cleanup(el);
72
72
  });
73
+
74
+ it('does not let a nested accordion double-handle its own items (and vice versa)', async () => {
75
+ const el = await fixture(`
76
+ <bds-accordion id="outer">
77
+ <bds-accordion-item id="a">
78
+ <span slot="header">Outer Q1</span>
79
+ <bds-accordion id="inner">
80
+ <bds-accordion-item id="b"><span slot="header">Inner Q1</span><p>B</p></bds-accordion-item>
81
+ <bds-accordion-item id="c"><span slot="header">Inner Q2</span><p>C</p></bds-accordion-item>
82
+ </bds-accordion>
83
+ </bds-accordion-item>
84
+ <bds-accordion-item id="d"><span slot="header">Outer Q2</span><p>D</p></bds-accordion-item>
85
+ </bds-accordion>
86
+ `);
87
+ const a = el.querySelector<BdsAccordionItem>('#a')!;
88
+ const b = el.querySelector<BdsAccordionItem>('#b')!;
89
+ const c = el.querySelector<BdsAccordionItem>('#c')!;
90
+ const d = el.querySelector<BdsAccordionItem>('#d')!;
91
+
92
+ // Open outer item A first (exclusive mode).
93
+ a.dispatchEvent(new CustomEvent('bds-accordion-item-toggle', { bubbles: true, composed: true }));
94
+ expect(a.open).toBe(true);
95
+
96
+ // Toggle inner item B. Without ownership scoping, the bubbled event
97
+ // would also be re-processed by the outer accordion, which would
98
+ // erroneously close item A and immediately re-close item B.
99
+ b.dispatchEvent(new CustomEvent('bds-accordion-item-toggle', { bubbles: true, composed: true }));
100
+ expect(b.open).toBe(true);
101
+ expect(c.open).toBe(false);
102
+ expect(a.open).toBe(true);
103
+ expect(d.open).toBe(false);
104
+
105
+ // Toggling outer item D must not disturb the inner accordion's state.
106
+ d.dispatchEvent(new CustomEvent('bds-accordion-item-toggle', { bubbles: true, composed: true }));
107
+ expect(d.open).toBe(true);
108
+ expect(a.open).toBe(false);
109
+ expect(b.open).toBe(true);
110
+ expect(c.open).toBe(false);
111
+
112
+ cleanup(el);
113
+ });
73
114
  });
@@ -24,13 +24,24 @@ import type { BdsAccordionItem } from './bds-accordion-item';
24
24
  */
25
25
  export class BdsAccordion extends LitElement {
26
26
  static styles = css`
27
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
28
+ here; otherwise elements with an explicit size + padding size as
29
+ content-box and layouts drift from the React (light-DOM) versions. */
30
+ :host,
31
+ *,
32
+ *::before,
33
+ *::after {
34
+ box-sizing: border-box;
35
+ }
36
+
27
37
  :host {
28
38
  display: block;
29
39
  border: var(--accordion_border, none);
30
40
  --bdc_color: var(--bds-color_bg--subtle);
31
41
  --bdc-outline_radius: var(--bds-border_radius--s);
32
42
  border-radius: var(--accordion_radius, var(--bdc-outline_radius));
33
- box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
43
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color),
44
+ var(--accordion_shadow, var(--bdc-outline_shadow));
34
45
  overflow: hidden;
35
46
  }
36
47
  `;
@@ -63,7 +74,11 @@ export class BdsAccordion extends LitElement {
63
74
  }
64
75
 
65
76
  private get _items(): BdsAccordionItem[] {
66
- return [...this.querySelectorAll<BdsAccordionItem>('bds-accordion-item')];
77
+ // Only items owned by *this* accordion — excludes items that belong to a
78
+ // nested <bds-accordion> rendered inside one of our items' content.
79
+ return [...this.querySelectorAll<BdsAccordionItem>('bds-accordion-item')].filter(
80
+ item => item.closest('bds-accordion') === this,
81
+ );
67
82
  }
68
83
 
69
84
  private _discoverItems() {
@@ -74,6 +89,17 @@ export class BdsAccordion extends LitElement {
74
89
 
75
90
  private _handleItemToggle = (e: Event) => {
76
91
  const toggled = e.target as BdsAccordionItem;
92
+
93
+ // A nested <bds-accordion> is the closest owner of its own items' toggle
94
+ // events. If this element isn't that closest owner, the event belongs to
95
+ // a descendant accordion — ignore it and let that accordion's own
96
+ // listener (which fires first, being closer in the tree) handle it.
97
+ if (toggled.closest('bds-accordion') !== this) return;
98
+
99
+ // Handled by the rightful owner: stop it from also reaching any
100
+ // ancestor <bds-accordion> further up the tree.
101
+ e.stopPropagation();
102
+
77
103
  const items = this._items;
78
104
  const wasOpen = toggled.open;
79
105
 
@@ -127,4 +127,85 @@ describe('bds-button', () => {
127
127
  expect(btn!.getAttribute('aria-label')).toBe('Close');
128
128
  cleanup(el);
129
129
  });
130
+
131
+ describe('slotted icon styling (icons live in light DOM)', () => {
132
+ it('colours slotted icon-start/icon-end svgs via ::slotted(), not a dead shadow-tree "svg" descendant selector', () => {
133
+ const cssText = BdsButton.styles.cssText;
134
+ expect(cssText).toMatch(/\.icon-start ::slotted\(svg\),\s*\.icon-end ::slotted\(svg\)\s*{[^}]*fill:\s*currentcolor/);
135
+ // The old selector could never match slotted content — guard against regressing to it.
136
+ expect(cssText).not.toMatch(/\.button svg\s*{/);
137
+ });
138
+
139
+ it('uses ::slotted() for the hover fadeZoom animation on slotted icons', () => {
140
+ const cssText = BdsButton.styles.cssText;
141
+ expect(cssText).toMatch(
142
+ /\.button:hover \.icon-start ::slotted\(svg\),\s*\.button:hover \.icon-end ::slotted\(svg\)\s*{\s*animation:\s*fadeZoom/,
143
+ );
144
+ });
145
+ });
146
+
147
+ describe('prefers-reduced-motion', () => {
148
+ it('disables the pulse ring and hover icon animation under prefers-reduced-motion: reduce', () => {
149
+ const cssText = BdsButton.styles.cssText;
150
+ expect(cssText).toMatch(
151
+ /@media \(prefers-reduced-motion: reduce\)\s*{\s*\.button\.has-pulse,\s*\.button:hover \.icon-start ::slotted\(svg\),\s*\.button:hover \.icon-end ::slotted\(svg\)\s*{\s*animation:\s*none;/,
152
+ );
153
+ });
154
+ });
155
+
156
+ describe('form participation (type="submit"/"reset" via ElementInternals)', () => {
157
+ it('is form-associated', () => {
158
+ expect(BdsButton.formAssociated).toBe(true);
159
+ });
160
+
161
+ it('calls the associated form\'s requestSubmit() when a type="submit" button is clicked', async () => {
162
+ const requestSubmit = vi.fn();
163
+ const spy = vi
164
+ .spyOn(HTMLElement.prototype, 'attachInternals')
165
+ .mockReturnValue({ form: { requestSubmit, reset: vi.fn() } } as unknown as ElementInternals);
166
+
167
+ const el = await fixture('<bds-button type="submit">Save</bds-button>');
168
+ const btn = el.shadowRoot!.querySelector('button')!;
169
+ btn.click();
170
+
171
+ expect(requestSubmit).toHaveBeenCalledTimes(1);
172
+
173
+ spy.mockRestore();
174
+ cleanup(el);
175
+ });
176
+
177
+ it('calls the associated form\'s reset() when a type="reset" button is clicked', async () => {
178
+ const reset = vi.fn();
179
+ const spy = vi
180
+ .spyOn(HTMLElement.prototype, 'attachInternals')
181
+ .mockReturnValue({ form: { requestSubmit: vi.fn(), reset } } as unknown as ElementInternals);
182
+
183
+ const el = await fixture('<bds-button type="reset">Clear</bds-button>');
184
+ const btn = el.shadowRoot!.querySelector('button')!;
185
+ btn.click();
186
+
187
+ expect(reset).toHaveBeenCalledTimes(1);
188
+
189
+ spy.mockRestore();
190
+ cleanup(el);
191
+ });
192
+
193
+ it('does not call requestSubmit or reset for the default type="button"', async () => {
194
+ const requestSubmit = vi.fn();
195
+ const reset = vi.fn();
196
+ const spy = vi
197
+ .spyOn(HTMLElement.prototype, 'attachInternals')
198
+ .mockReturnValue({ form: { requestSubmit, reset } } as unknown as ElementInternals);
199
+
200
+ const el = await fixture('<bds-button>Click</bds-button>');
201
+ const btn = el.shadowRoot!.querySelector('button')!;
202
+ btn.click();
203
+
204
+ expect(requestSubmit).not.toHaveBeenCalled();
205
+ expect(reset).not.toHaveBeenCalled();
206
+
207
+ spy.mockRestore();
208
+ cleanup(el);
209
+ });
210
+ });
130
211
  });
@@ -13,10 +13,15 @@ export type ButtonSize = 'small' | 'medium' | 'large';
13
13
  * href — renders an <a> tag instead of <button>
14
14
  * target — forwarded to <a> when href is set
15
15
  * rel — forwarded to <a> when href is set
16
- * has-pulse — boolean, enables the pulse ring animation
17
- * is-icon-only — boolean, forces a 1:1 aspect ratio with minimal xs padding (requires aria-label)
16
+ * has-pulse — boolean, enables the pulse ring animation (respects prefers-reduced-motion)
17
+ * is-icon-only — boolean, forces a 1:1 aspect ratio with minimal xxs padding (requires aria-label)
18
18
  * type — "button" (default) | "submit" | "reset"
19
19
  *
20
+ * Form participation: form-associated via the Form-Associated Custom Elements
21
+ * API (ElementInternals). When nested inside a light-DOM <form>, type="submit"
22
+ * calls the form's requestSubmit() and type="reset" calls its reset() — the
23
+ * inner shadow-DOM <button> has no native form of its own to act on.
24
+ *
20
25
  * Slots:
21
26
  * (default) — button label
22
27
  * icon-start — icon placed before the label
@@ -32,7 +37,19 @@ export type ButtonSize = 'small' | 'medium' | 'large';
32
37
  * </bds-button>
33
38
  */
34
39
  export class BdsButton extends LitElement {
40
+ static formAssociated = true;
41
+
35
42
  static styles = css`
43
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
44
+ here; otherwise elements with an explicit size + padding size as
45
+ content-box and layouts drift from the React (light-DOM) versions. */
46
+ :host,
47
+ *,
48
+ *::before,
49
+ *::after {
50
+ box-sizing: border-box;
51
+ }
52
+
36
53
  @keyframes fadeZoom {
37
54
  0%, 100% { opacity: 1; transform: scale(1); }
38
55
  50% { opacity: 0.75; transform: scale(1.15); }
@@ -121,7 +138,7 @@ export class BdsButton extends LitElement {
121
138
  animation: pulse 3s infinite;
122
139
  }
123
140
 
124
- /* Icon-only: square (1:1), equal xs padding on both axes — composes with
141
+ /* Icon-only: square (1:1), equal xxs padding on both axes — composes with
125
142
  any variant/size. box-sizing: border-box keeps the outer box at
126
143
  --button_height; without it, all: unset reverts to content-box and
127
144
  padding-block would stretch the button past its declared height.
@@ -138,8 +155,11 @@ export class BdsButton extends LitElement {
138
155
  block-size: 100%;
139
156
  }
140
157
 
141
- /* SVG icon colour */
142
- .button svg {
158
+ /* SVG icon colour — icons are slotted (light DOM), so a shadow-tree
159
+ descendant selector like ".button svg" can never match them; ::slotted()
160
+ is required to reach content projected through a <slot>. */
161
+ .icon-start ::slotted(svg),
162
+ .icon-end ::slotted(svg) {
143
163
  --icon__stroke: currentcolor;
144
164
  fill: currentcolor;
145
165
  color: currentcolor;
@@ -171,10 +191,10 @@ export class BdsButton extends LitElement {
171
191
  margin-inline: 0;
172
192
  }
173
193
 
174
- /* Hover icon animation */
194
+ /* Hover icon animation — same ::slotted() requirement as the colour rule above */
175
195
  @media (hover: hover) and (pointer: fine) {
176
- .button:hover .icon-start svg,
177
- .button:hover .icon-end svg {
196
+ .button:hover .icon-start ::slotted(svg),
197
+ .button:hover .icon-end ::slotted(svg) {
178
198
  animation: fadeZoom 2s ease-out infinite;
179
199
  }
180
200
 
@@ -195,6 +215,16 @@ export class BdsButton extends LitElement {
195
215
  }
196
216
  }
197
217
 
218
+ /* Reduced motion — the pulse ring and hover icon animation both loop
219
+ indefinitely; disable them for users who opt out of motion. */
220
+ @media (prefers-reduced-motion: reduce) {
221
+ .button.has-pulse,
222
+ .button:hover .icon-start ::slotted(svg),
223
+ .button:hover .icon-end ::slotted(svg) {
224
+ animation: none;
225
+ }
226
+ }
227
+
198
228
  /* Focus visible */
199
229
  .button:focus-visible {
200
230
  outline: var(--bds-outline_default);
@@ -241,8 +271,11 @@ export class BdsButton extends LitElement {
241
271
  declare private _iconStartFilled: boolean;
242
272
  declare private _iconEndFilled: boolean;
243
273
 
274
+ private _internals!: ElementInternals;
275
+
244
276
  constructor() {
245
277
  super();
278
+ this._internals = this.attachInternals();
246
279
  this.variant = 'default';
247
280
  this.size = 'medium';
248
281
  this.disabled = false;
@@ -274,6 +307,19 @@ export class BdsButton extends LitElement {
274
307
  }
275
308
  }
276
309
 
310
+ // The shadow-DOM <button> below has no native <form> ancestor of its own
311
+ // (the light-DOM form, if any, lives outside the shadow tree), so its
312
+ // type="submit"/"reset" attribute is inert on its own. Drive the real
313
+ // behaviour through ElementInternals, which resolves the form this
314
+ // *custom element* participates in.
315
+ private _handleButtonClick() {
316
+ if (this.type === 'submit') {
317
+ this._internals.form?.requestSubmit();
318
+ } else if (this.type === 'reset') {
319
+ this._internals.form?.reset();
320
+ }
321
+ }
322
+
277
323
  private _iconSlot(position: 'start' | 'end') {
278
324
  const filled = position === 'start' ? this._iconStartFilled : this._iconEndFilled;
279
325
  return html`
@@ -309,6 +355,7 @@ export class BdsButton extends LitElement {
309
355
  type=${this.type}
310
356
  ?disabled=${this.disabled}
311
357
  aria-label=${this.ariaLabel ?? nothing}
358
+ @click=${this._handleButtonClick}
312
359
  >
313
360
  ${this._iconSlot('start')}
314
361
  <slot></slot>
@@ -37,12 +37,29 @@ describe('bds-collapsible', () => {
37
37
  const el = await fixture('<bds-collapsible><span slot="summary">Title</span><p>Content</p></bds-collapsible>') as BdsCollapsible;
38
38
  const handler = vi.fn();
39
39
  el.addEventListener('bds-toggle', handler);
40
- // jsdom does not fire the native toggle event on summary click;
41
- // dispatch it directly on the <details> element instead.
40
+ // jsdom does not simulate a real click-driven state change, so emulate it:
41
+ // flip the native <details> element's own `open` property first (what the
42
+ // browser does before dispatching), then fire the "toggle" event it would emit.
42
43
  const details = el.shadowRoot!.querySelector('details')!;
44
+ details.open = true;
43
45
  details.dispatchEvent(new Event('toggle', { bubbles: false }));
44
46
  await el.updateComplete;
45
47
  expect(handler).toHaveBeenCalledOnce();
48
+ expect(handler.mock.calls[0][0].detail).toEqual({ open: true });
49
+ expect(el.open).toBe(true);
50
+ cleanup(el);
51
+ });
52
+
53
+ it('does not dispatch a redundant bds-toggle when the toggle event fires without a real state change', async () => {
54
+ const el = await fixture('<bds-collapsible><span slot="summary">Title</span><p>Content</p></bds-collapsible>') as BdsCollapsible;
55
+ const handler = vi.fn();
56
+ el.addEventListener('bds-toggle', handler);
57
+ // Native <details> "toggle" event coalesces and only fires on real state changes;
58
+ // a spurious dispatch with no actual open-state change must be a no-op.
59
+ const details = el.shadowRoot!.querySelector('details')!;
60
+ details.dispatchEvent(new Event('toggle', { bubbles: false }));
61
+ await el.updateComplete;
62
+ expect(handler).not.toHaveBeenCalled();
46
63
  cleanup(el);
47
64
  });
48
65
 
@@ -53,4 +70,78 @@ describe('bds-collapsible', () => {
53
70
  expect(el.getAttribute('open')).not.toBeNull();
54
71
  cleanup(el);
55
72
  });
73
+
74
+ describe('name grouping (exclusive open)', () => {
75
+ it('closes other open members of the same group when one opens', async () => {
76
+ const a = await fixture(
77
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
78
+ ) as BdsCollapsible;
79
+ const b = await fixture(
80
+ '<bds-collapsible name="faq"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
81
+ ) as BdsCollapsible;
82
+ expect(a.open).toBe(true);
83
+
84
+ b.open = true;
85
+ await b.updateComplete;
86
+ await a.updateComplete;
87
+
88
+ expect(b.open).toBe(true);
89
+ expect(a.open).toBe(false);
90
+ expect(a.hasAttribute('open')).toBe(false);
91
+ cleanup(a);
92
+ cleanup(b);
93
+ });
94
+
95
+ it('fires bds-toggle on the sibling that gets force-closed', async () => {
96
+ const a = await fixture(
97
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
98
+ ) as BdsCollapsible;
99
+ const b = await fixture(
100
+ '<bds-collapsible name="faq"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
101
+ ) as BdsCollapsible;
102
+ const aHandler = vi.fn();
103
+ a.addEventListener('bds-toggle', aHandler);
104
+
105
+ b.open = true;
106
+ await b.updateComplete;
107
+ await a.updateComplete;
108
+
109
+ expect(aHandler).toHaveBeenCalledOnce();
110
+ expect(aHandler.mock.calls[0][0].detail).toEqual({ open: false });
111
+ cleanup(a);
112
+ cleanup(b);
113
+ });
114
+
115
+ it('does not affect open collapsibles in a different group', async () => {
116
+ const a = await fixture(
117
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
118
+ ) as BdsCollapsible;
119
+ const b = await fixture(
120
+ '<bds-collapsible name="other"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
121
+ ) as BdsCollapsible;
122
+
123
+ b.open = true;
124
+ await b.updateComplete;
125
+ await a.updateComplete;
126
+
127
+ expect(a.open).toBe(true);
128
+ expect(b.open).toBe(true);
129
+ cleanup(a);
130
+ cleanup(b);
131
+ });
132
+
133
+ it('does not group collapsibles with no name attribute', async () => {
134
+ const a = await fixture('<bds-collapsible open><span slot="summary">A</span><p>Content A</p></bds-collapsible>') as BdsCollapsible;
135
+ const b = await fixture('<bds-collapsible><span slot="summary">B</span><p>Content B</p></bds-collapsible>') as BdsCollapsible;
136
+
137
+ b.open = true;
138
+ await b.updateComplete;
139
+ await a.updateComplete;
140
+
141
+ expect(a.open).toBe(true);
142
+ expect(b.open).toBe(true);
143
+ cleanup(a);
144
+ cleanup(b);
145
+ });
146
+ });
56
147
  });