@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
@@ -59,4 +59,61 @@ describe('bds-radio-group', () => {
59
59
  expect(radio?.getAttribute('name')).toBe('contact');
60
60
  cleanup(el);
61
61
  });
62
+
63
+ it('propagates disabled to slotted bds-radio elements', async () => {
64
+ const el = await fixture(
65
+ '<bds-radio-group label="Contact" name="contact" disabled><bds-radio value="email">Email</bds-radio><bds-radio value="phone">Phone</bds-radio></bds-radio-group>',
66
+ ) as BdsRadioGroup;
67
+ await Promise.resolve();
68
+ const radios = Array.from(el.querySelectorAll('bds-radio'));
69
+ expect(radios.every(radio => radio.hasAttribute('disabled'))).toBe(true);
70
+ cleanup(el);
71
+ });
72
+
73
+ it('removes propagated disabled from slotted bds-radio elements when re-enabled, keeping individually-disabled ones', async () => {
74
+ const el = await fixture(
75
+ '<bds-radio-group label="Contact" name="contact" disabled>' +
76
+ '<bds-radio value="email">Email</bds-radio>' +
77
+ '<bds-radio value="phone" disabled>Phone</bds-radio>' +
78
+ '</bds-radio-group>',
79
+ ) as BdsRadioGroup;
80
+ await Promise.resolve();
81
+
82
+ el.disabled = false;
83
+ await el.updateComplete;
84
+ await Promise.resolve();
85
+
86
+ const [email, phone] = Array.from(el.querySelectorAll('bds-radio'));
87
+ expect(email.hasAttribute('disabled')).toBe(false);
88
+ expect(phone.hasAttribute('disabled')).toBe(true);
89
+ cleanup(el);
90
+ });
91
+
92
+ it('sets aria-required on the fieldset when required', async () => {
93
+ const el = await fixture('<bds-radio-group label="Test" required></bds-radio-group>');
94
+ const fieldset = el.shadowRoot!.querySelector('fieldset')!;
95
+ expect(fieldset.getAttribute('aria-required')).toBe('true');
96
+ cleanup(el);
97
+ });
98
+
99
+ it('does not set aria-required when not required', async () => {
100
+ const el = await fixture('<bds-radio-group label="Test"></bds-radio-group>');
101
+ const fieldset = el.shadowRoot!.querySelector('fieldset')!;
102
+ expect(fieldset.hasAttribute('aria-required')).toBe(false);
103
+ cleanup(el);
104
+ });
105
+
106
+ it('wires aria-describedby on the fieldset to the error message id', async () => {
107
+ const el = await fixture('<bds-radio-group label="Test" error="Required field"></bds-radio-group>');
108
+ const fieldset = el.shadowRoot!.querySelector('fieldset')!;
109
+ const message = el.shadowRoot!.querySelector('.message--error')!;
110
+ expect(fieldset.getAttribute('aria-describedby')).toBe(message.id);
111
+ cleanup(el);
112
+ });
113
+
114
+ it('uses medium font-weight token for the legend (not semibold)', () => {
115
+ const styles = BdsRadioGroup.styles.toString();
116
+ expect(styles).toContain('--bds-font_weight--medium');
117
+ expect(styles).not.toContain('--bds-font_weight--semibold');
118
+ });
62
119
  });
@@ -1,17 +1,20 @@
1
- import { LitElement, css, html } from 'lit';
1
+ import { LitElement, css, html, nothing } from 'lit';
2
2
 
3
3
  /**
4
4
  * `<bds-radio-group>` — groups related radio buttons with a shared label and name.
5
5
  *
6
6
  * Renders a `<fieldset>` with a `<legend>`. Propagates the `name` attribute to
7
- * all slotted `<bds-radio>` elements so radios form a mutually-exclusive set.
7
+ * all slotted `<bds-radio>` elements so radios form a mutually-exclusive set,
8
+ * and propagates `disabled` the same way — a group-level `disabled` attribute
9
+ * disables every slotted `<bds-radio>` (a shadow-DOM `<fieldset disabled>` has
10
+ * no effect on slotted light-DOM content on its own).
8
11
  *
9
12
  * Attributes:
10
13
  * label — visible group label (rendered as `<legend>`)
11
14
  * name — shared name forwarded to all slotted `<bds-radio>` elements
12
15
  * error — error message shown below items
13
16
  * hint — hint text shown below items
14
- * disabled — disables the entire fieldset
17
+ * disabled — disables the entire fieldset and all slotted `<bds-radio>` elements
15
18
  * required — marks the group as required
16
19
  *
17
20
  * Slots:
@@ -25,6 +28,16 @@ import { LitElement, css, html } from 'lit';
25
28
  */
26
29
  export class BdsRadioGroup extends LitElement {
27
30
  static styles = css`
31
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
32
+ here; otherwise elements with an explicit size + padding size as
33
+ content-box and layouts drift from the React (light-DOM) versions. */
34
+ :host,
35
+ *,
36
+ *::before,
37
+ *::after {
38
+ box-sizing: border-box;
39
+ }
40
+
28
41
  :host {
29
42
  display: block;
30
43
  }
@@ -38,7 +51,7 @@ export class BdsRadioGroup extends LitElement {
38
51
  .legend {
39
52
  font-size: var(--bds-font_size--body--s);
40
53
  font-family: var(--bds-font_family--body);
41
- font-weight: var(--bds-font_weight--semibold);
54
+ font-weight: var(--bds-font_weight--medium);
42
55
  color: var(--bds-color_on-bg);
43
56
  margin-block-end: var(--bds-space_xs);
44
57
  }
@@ -86,6 +99,8 @@ export class BdsRadioGroup extends LitElement {
86
99
  declare disabled: boolean;
87
100
  declare required: boolean;
88
101
 
102
+ private readonly _uid = Math.random().toString(36).slice(2, 8);
103
+
89
104
  constructor() {
90
105
  super();
91
106
  this.label = '';
@@ -101,33 +116,66 @@ export class BdsRadioGroup extends LitElement {
101
116
  });
102
117
  };
103
118
 
119
+ /**
120
+ * Propagates `disabled` to slotted `<bds-radio>` elements. Only radios that were
121
+ * enabled before the group disabled them are re-enabled when the group re-enables —
122
+ * a radio individually marked `disabled` in markup stays disabled.
123
+ */
124
+ private _propagateDisabled = () => {
125
+ this.querySelectorAll('bds-radio').forEach(radio => {
126
+ const el = radio as HTMLElement;
127
+ if (this.disabled) {
128
+ if (!el.hasAttribute('disabled')) {
129
+ el.setAttribute('data-bds-group-disabled', '');
130
+ }
131
+ el.setAttribute('disabled', '');
132
+ } else if (el.hasAttribute('data-bds-group-disabled')) {
133
+ el.removeAttribute('disabled');
134
+ el.removeAttribute('data-bds-group-disabled');
135
+ }
136
+ });
137
+ };
138
+
139
+ private _propagateToRadios = () => {
140
+ this._propagateName();
141
+ this._propagateDisabled();
142
+ };
143
+
104
144
  connectedCallback() {
105
145
  super.connectedCallback();
106
- // Propagate name after slot content has been parsed
107
- Promise.resolve().then(this._propagateName);
146
+ // Propagate after slot content has been parsed
147
+ Promise.resolve().then(this._propagateToRadios);
108
148
  }
109
149
 
110
150
  updated(changed: Map<string, unknown>) {
111
151
  super.updated(changed);
112
- if (changed.has('name')) {
113
- this._propagateName();
152
+ if (changed.has('name') || changed.has('disabled')) {
153
+ this._propagateToRadios();
114
154
  }
115
155
  }
116
156
 
117
157
  render() {
158
+ const errorId = `bds-radio-group-${this._uid}-error`;
159
+ const hintId = `bds-radio-group-${this._uid}-hint`;
160
+ const describedBy = [this.error && errorId, this.hint && hintId].filter(Boolean).join(' ') || undefined;
161
+
118
162
  return html`
119
- <fieldset ?disabled=${this.disabled}>
163
+ <fieldset
164
+ ?disabled=${this.disabled}
165
+ aria-required=${this.required ? 'true' : nothing}
166
+ aria-describedby=${describedBy ?? nothing}
167
+ >
120
168
  <legend class="legend">
121
169
  ${this.label}
122
170
  ${this.required ? html`<span class="required" aria-hidden="true"> *</span>` : ''}
123
171
  </legend>
124
172
  <div class="items">
125
- <slot @slotchange=${this._propagateName}></slot>
173
+ <slot @slotchange=${this._propagateToRadios}></slot>
126
174
  </div>
127
175
  ${this.error
128
- ? html`<p class="message message--error" role="alert">${this.error}</p>`
176
+ ? html`<p id=${errorId} class="message message--error" role="alert">${this.error}</p>`
129
177
  : this.hint
130
- ? html`<p class="message message--hint">${this.hint}</p>`
178
+ ? html`<p id=${hintId} class="message message--hint">${this.hint}</p>`
131
179
  : ''}
132
180
  </fieldset>
133
181
  `;
@@ -67,4 +67,127 @@ describe('bds-radio', () => {
67
67
  it('is form-associated', () => {
68
68
  expect(BdsRadio.formAssociated).toBe(true);
69
69
  });
70
+
71
+ it('sets aria-invalid on the input when error is set', async () => {
72
+ const el = await fixture('<bds-radio name="size" value="m" error>Medium</bds-radio>');
73
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
74
+ expect(input.getAttribute('aria-invalid')).toBe('true');
75
+ cleanup(el);
76
+ });
77
+
78
+ it('renders description text linked via aria-describedby', async () => {
79
+ const el = await fixture('<bds-radio name="size" value="m" description="Fits most people">Medium</bds-radio>');
80
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
81
+ const description = el.shadowRoot!.querySelector('.description')!;
82
+ expect(description.textContent?.trim()).toBe('Fits most people');
83
+ expect(input.getAttribute('aria-describedby')).toBe(description.id);
84
+ cleanup(el);
85
+ });
86
+
87
+ it('restores the initial checked state on form reset', async () => {
88
+ const el = await fixture('<bds-radio name="size" value="m" checked>Medium</bds-radio>') as BdsRadio;
89
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
90
+ input.checked = false;
91
+ input.dispatchEvent(new Event('change', { bubbles: true }));
92
+ expect(el.checked).toBe(false);
93
+
94
+ el.formResetCallback();
95
+ await el.updateComplete;
96
+ expect(el.checked).toBe(true);
97
+ cleanup(el);
98
+ });
99
+
100
+ it('resets to unchecked when it was not initially checked', async () => {
101
+ const el = await fixture('<bds-radio name="size" value="m">Medium</bds-radio>') as BdsRadio;
102
+ el.checked = true;
103
+ await el.updateComplete;
104
+
105
+ el.formResetCallback();
106
+ await el.updateComplete;
107
+ expect(el.checked).toBe(false);
108
+ cleanup(el);
109
+ });
110
+
111
+ describe('group semantics (multiple bds-radio elements sharing a name)', () => {
112
+ it('unchecks other bds-radio elements sharing the same name when one becomes checked', async () => {
113
+ const a = await fixture('<bds-radio name="plan" value="a" checked>A</bds-radio>') as BdsRadio;
114
+ const b = await fixture('<bds-radio name="plan" value="b">B</bds-radio>') as BdsRadio;
115
+ const bInput = b.shadowRoot!.querySelector<HTMLInputElement>('input')!;
116
+
117
+ bInput.checked = true;
118
+ bInput.dispatchEvent(new Event('change', { bubbles: true }));
119
+ await b.updateComplete;
120
+ await a.updateComplete;
121
+
122
+ expect(b.checked).toBe(true);
123
+ expect(a.checked).toBe(false);
124
+ cleanup(a);
125
+ cleanup(b);
126
+ });
127
+
128
+ it('does not affect bds-radio elements with a different name', async () => {
129
+ const a = await fixture('<bds-radio name="plan-x" value="a" checked>A</bds-radio>') as BdsRadio;
130
+ const c = await fixture('<bds-radio name="plan-y" value="c">C</bds-radio>') as BdsRadio;
131
+ const cInput = c.shadowRoot!.querySelector<HTMLInputElement>('input')!;
132
+
133
+ cInput.checked = true;
134
+ cInput.dispatchEvent(new Event('change', { bubbles: true }));
135
+ await c.updateComplete;
136
+ await a.updateComplete;
137
+
138
+ expect(c.checked).toBe(true);
139
+ expect(a.checked).toBe(true);
140
+ cleanup(a);
141
+ cleanup(c);
142
+ });
143
+
144
+ it('dispatches a composed bds-change event when checked via user interaction', async () => {
145
+ const el = await fixture('<bds-radio name="notify" value="on">Notify</bds-radio>') as BdsRadio;
146
+ const handler = vi.fn();
147
+ el.addEventListener('bds-change', handler);
148
+
149
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
150
+ input.checked = true;
151
+ input.dispatchEvent(new Event('change', { bubbles: true }));
152
+
153
+ expect(handler).toHaveBeenCalledTimes(1);
154
+ const event = handler.mock.calls[0][0] as CustomEvent;
155
+ expect(event.composed).toBe(true);
156
+ expect(event.detail).toEqual({ value: 'on', checked: true });
157
+ cleanup(el);
158
+ });
159
+
160
+ it('moves selection to the next radio on ArrowDown', async () => {
161
+ const a = await fixture('<bds-radio name="nav" value="a" checked>A</bds-radio>') as BdsRadio;
162
+ const b = await fixture('<bds-radio name="nav" value="b">B</bds-radio>') as BdsRadio;
163
+
164
+ const aInput = a.shadowRoot!.querySelector<HTMLInputElement>('input')!;
165
+ aInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
166
+ await a.updateComplete;
167
+ await b.updateComplete;
168
+
169
+ expect(a.checked).toBe(false);
170
+ expect(b.checked).toBe(true);
171
+ cleanup(a);
172
+ cleanup(b);
173
+ });
174
+
175
+ it('skips disabled radios when navigating with arrow keys', async () => {
176
+ const a = await fixture('<bds-radio name="nav-2" value="a" checked>A</bds-radio>') as BdsRadio;
177
+ const b = await fixture('<bds-radio name="nav-2" value="b" disabled>B</bds-radio>') as BdsRadio;
178
+ const c = await fixture('<bds-radio name="nav-2" value="c">C</bds-radio>') as BdsRadio;
179
+
180
+ const aInput = a.shadowRoot!.querySelector<HTMLInputElement>('input')!;
181
+ aInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
182
+ await a.updateComplete;
183
+ await c.updateComplete;
184
+
185
+ expect(a.checked).toBe(false);
186
+ expect(b.checked).toBe(false);
187
+ expect(c.checked).toBe(true);
188
+ cleanup(a);
189
+ cleanup(b);
190
+ cleanup(c);
191
+ });
192
+ });
70
193
  });
@@ -1,21 +1,31 @@
1
- import { LitElement, css, html } from 'lit';
1
+ import { LitElement, css, html, nothing } from 'lit';
2
2
 
3
3
  /**
4
4
  * `<bds-radio>` — form-associated Radio custom element.
5
5
  *
6
6
  * Participates in HTML forms via the Form-Associated Custom Elements API.
7
- * Use multiple `<bds-radio>` elements with the same `name` to form a group.
7
+ * Use multiple `<bds-radio>` elements with the same `name` to form a group
8
+ * checking one automatically unchecks the others sharing that `name`
9
+ * (searched within the same document/shadow root), and Arrow Up/Down/Left/Right
10
+ * move selection between them, mirroring native `<input type="radio">` semantics.
8
11
  *
9
12
  * Attributes:
10
- * name — form field name (groups radio buttons)
11
- * value — submitted value when selected
12
- * checked — boolean; whether this option is selected
13
- * required — boolean
14
- * disabled — boolean
13
+ * name — form field name (groups radio buttons)
14
+ * value — submitted value when selected
15
+ * checked — boolean; whether this option is selected
16
+ * required — boolean
17
+ * disabled — boolean
18
+ * error — boolean; visual + aria-invalid error state
19
+ * description — optional supporting text rendered below the label
15
20
  *
16
21
  * Slots:
17
22
  * (default) — label text
18
23
  *
24
+ * Events:
25
+ * bds-change — fired when this radio becomes checked via user interaction;
26
+ * `detail: { value: string, checked: true }`. Composed so it
27
+ * crosses the shadow boundary (unlike the native `change` event).
28
+ *
19
29
  * @example
20
30
  * <bds-radio name="size" value="s">Small</bds-radio>
21
31
  * <bds-radio name="size" value="m" checked>Medium</bds-radio>
@@ -25,6 +35,16 @@ export class BdsRadio extends LitElement {
25
35
  static formAssociated = true;
26
36
 
27
37
  static styles = css`
38
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
39
+ here; otherwise elements with an explicit size + padding size as
40
+ content-box and layouts drift from the React (light-DOM) versions. */
41
+ :host,
42
+ *,
43
+ *::before,
44
+ *::after {
45
+ box-sizing: border-box;
46
+ }
47
+
28
48
  :host {
29
49
  display: inline-flex;
30
50
  align-items: flex-start;
@@ -42,22 +62,25 @@ export class BdsRadio extends LitElement {
42
62
  }
43
63
 
44
64
  .input {
65
+ box-sizing: border-box;
45
66
  --inputSize: calc(var(--bds-space_s) * 2);
46
- margin-block-start: 0.25em;
67
+ margin-block-start: var(--radio_offset, 0.2em);
47
68
  inline-size: var(--inputSize);
48
69
  block-size: var(--inputSize);
49
70
  flex-shrink: 0;
50
- --bdc_color: var(--radio_border-color, currentcolor);
51
- --bdc-outline_radius: 50%;
71
+ color: var(--radio_color, var(--radio_border-color, var(--bds-color_on-bg)));
72
+ border: var(--radio_border, none);
73
+ --bdc_color: currentcolor;
74
+ --bdc-outline_radius: var(--bds-border_radius--circle);
52
75
 
53
- border-radius: 50%;
76
+ border-radius: var(--radio_radius, var(--bdc-outline_radius));
54
77
  box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--radio_shadow, var(--bdc-outline_shadow));
55
- border: none;
56
78
  appearance: none;
57
79
  background-color: var(--radio_color_bg, var(--bds-color_bg));
58
80
  cursor: pointer;
59
81
  position: relative;
60
- transition: --bdc_color 0.3s ease, background-color 0.3s ease;
82
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing),
83
+ background-color var(--bds-animation_transition-duration) var(--bds-animation_easing);
61
84
  }
62
85
 
63
86
  .input:checked {
@@ -69,23 +92,45 @@ export class BdsRadio extends LitElement {
69
92
  --bdc_color: var(--radio_color-active, var(--bdc-color_active));
70
93
  }
71
94
 
95
+ .input:focus {
96
+ --bdc_color: var(--bdc_color--focus);
97
+ }
98
+
72
99
  .input:focus-visible {
73
100
  outline: var(--bds-outline_default);
74
101
  outline-offset: var(--bds-outline_offset);
75
102
  }
76
103
 
104
+ :host([error]) .input {
105
+ --bdc_color: var(--bdc_color--error);
106
+ }
107
+
108
+ .textWrapper {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--bds-space_xxxs);
112
+ flex: 1;
113
+ }
114
+
77
115
  label {
78
116
  cursor: pointer;
79
- flex: 1;
117
+ }
118
+
119
+ .description {
120
+ font-size: var(--bds-font_size--body--s);
121
+ color: var(--radio_description-color, var(--bds-color_on-bg--subtle));
122
+ line-height: var(--bds-font_line-height--body);
80
123
  }
81
124
  `;
82
125
 
83
126
  static properties = {
84
- name: { type: String, reflect: true },
85
- value: { type: String },
86
- checked: { type: Boolean, reflect: true },
87
- required: { type: Boolean, reflect: true },
88
- disabled: { type: Boolean, reflect: true },
127
+ name: { type: String, reflect: true },
128
+ value: { type: String },
129
+ checked: { type: Boolean, reflect: true },
130
+ required: { type: Boolean, reflect: true },
131
+ disabled: { type: Boolean, reflect: true },
132
+ error: { type: Boolean, reflect: true },
133
+ description: { type: String },
89
134
  };
90
135
 
91
136
  declare name: string;
@@ -93,9 +138,12 @@ export class BdsRadio extends LitElement {
93
138
  declare checked: boolean;
94
139
  declare required: boolean;
95
140
  declare disabled: boolean;
141
+ declare error: boolean;
142
+ declare description: string | undefined;
96
143
 
97
144
  private _internals!: ElementInternals;
98
145
  private readonly _uid = Math.random().toString(36).slice(2, 8);
146
+ private _initialChecked = false;
99
147
 
100
148
  constructor() {
101
149
  super();
@@ -105,6 +153,12 @@ export class BdsRadio extends LitElement {
105
153
  this.checked = false;
106
154
  this.required = false;
107
155
  this.disabled = false;
156
+ this.error = false;
157
+ }
158
+
159
+ connectedCallback() {
160
+ super.connectedCallback();
161
+ this._initialChecked = this.hasAttribute('checked');
108
162
  }
109
163
 
110
164
  updated(changed: Map<string, unknown>) {
@@ -115,16 +169,68 @@ export class BdsRadio extends LitElement {
115
169
  }
116
170
 
117
171
  formResetCallback() {
118
- this.checked = false;
119
- this._internals.setFormValue?.(null);
172
+ this.checked = this._initialChecked;
173
+ this._internals.setFormValue?.(this._initialChecked ? this.value : null);
174
+ }
175
+
176
+ /** All `<bds-radio>` elements sharing this radio's `name`, in document order (including this one). */
177
+ private _getGroupMembers(): BdsRadio[] {
178
+ if (!this.name) return [this];
179
+ const root = this.getRootNode() as Document | ShadowRoot;
180
+ return Array.from(root.querySelectorAll('bds-radio')).filter(radio => radio.name === this.name);
181
+ }
182
+
183
+ /** Marks `radio` as the sole checked member of its group and notifies listeners. */
184
+ private _selectRadio(radio: BdsRadio) {
185
+ this._getGroupMembers().forEach(member => {
186
+ if (member !== radio) member.checked = false;
187
+ });
188
+ radio.checked = true;
189
+ radio.dispatchEvent(new CustomEvent('bds-change', {
190
+ detail: { value: radio.value, checked: true },
191
+ bubbles: true,
192
+ composed: true,
193
+ }));
120
194
  }
121
195
 
122
196
  private _handleChange(e: Event) {
123
- this.checked = (e.target as HTMLInputElement).checked;
197
+ const isChecked = (e.target as HTMLInputElement).checked;
198
+ if (isChecked) {
199
+ this._selectRadio(this);
200
+ } else {
201
+ this.checked = false;
202
+ }
203
+ }
204
+
205
+ private _handleKeydown = (e: KeyboardEvent) => {
206
+ if (this.disabled) return;
207
+ if (!['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(e.key)) return;
208
+
209
+ const group = this._getGroupMembers().filter(member => !member.disabled);
210
+ if (group.length < 2) return;
211
+
212
+ e.preventDefault();
213
+ const currentIndex = group.indexOf(this);
214
+ const direction = e.key === 'ArrowDown' || e.key === 'ArrowRight' ? 1 : -1;
215
+ const next = group[(currentIndex + direction + group.length) % group.length];
216
+
217
+ this._selectRadio(next);
218
+ void next.updateComplete.then(() => {
219
+ next.shadowRoot?.querySelector<HTMLInputElement>('input')?.focus();
220
+ });
221
+ };
222
+
223
+ /** Roving tabindex: only the checked member (or the first member if none is checked) is tabbable. */
224
+ private _isTabbable(): boolean {
225
+ const group = this._getGroupMembers();
226
+ if (group.length <= 1) return true;
227
+ const checkedMember = group.find(member => member.checked);
228
+ return checkedMember ? checkedMember === this : group[0] === this;
124
229
  }
125
230
 
126
231
  render() {
127
232
  const id = `bds-radio-${this._uid}`;
233
+ const descriptionId = `${id}-description`;
128
234
  return html`
129
235
  <input
130
236
  id=${id}
@@ -135,9 +241,16 @@ export class BdsRadio extends LitElement {
135
241
  .checked=${this.checked}
136
242
  ?required=${this.required}
137
243
  ?disabled=${this.disabled}
244
+ tabindex=${this._isTabbable() ? 0 : -1}
245
+ aria-invalid=${this.error ? 'true' : 'false'}
246
+ aria-describedby=${this.description ? descriptionId : nothing}
138
247
  @change=${this._handleChange}
248
+ @keydown=${this._handleKeydown}
139
249
  />
140
- <label for=${id}><slot></slot></label>
250
+ <div class="textWrapper">
251
+ <label for=${id}><slot></slot></label>
252
+ ${this.description ? html`<span id=${descriptionId} class="description">${this.description}</span>` : ''}
253
+ </div>
141
254
  `;
142
255
  }
143
256
  }
@@ -71,4 +71,59 @@ describe('bds-segmented-control', () => {
71
71
  it('is form-associated', () => {
72
72
  expect(BdsSegmentedControl.formAssociated).toBe(true);
73
73
  });
74
+
75
+ it('fires a bds-change event with the selected value in detail', async () => {
76
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
77
+ const handler = vi.fn();
78
+ el.addEventListener('bds-change', handler);
79
+ const radios = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="radio"]')];
80
+ radios[0].dispatchEvent(new Event('change', { bubbles: true }));
81
+ expect(handler).toHaveBeenCalledOnce();
82
+ expect((handler.mock.calls[0][0] as CustomEvent).detail).toEqual({ value: 'list' });
83
+ cleanup(el);
84
+ });
85
+
86
+ it('sets a string aria-disabled value on disabled link-mode options', async () => {
87
+ const navOptions = JSON.stringify([
88
+ { value: 'home', label: 'Home', href: '/' },
89
+ { value: 'about', label: 'About', href: '/about', disabled: true },
90
+ ]);
91
+ const el = await fixture(`<bds-segmented-control value="home" options='${navOptions}'></bds-segmented-control>`);
92
+ const links = [...el.shadowRoot!.querySelectorAll('a')];
93
+ const disabledLink = links.find(a => a.textContent?.trim() === 'About')!;
94
+ const enabledLink = links.find(a => a.textContent?.trim() === 'Home')!;
95
+ expect(disabledLink.getAttribute('aria-disabled')).toBe('true');
96
+ expect(enabledLink.hasAttribute('aria-disabled')).toBe(false);
97
+ cleanup(el);
98
+ });
99
+
100
+ it('restores the initial value attribute on form reset', async () => {
101
+ const el = await fixture(`<bds-segmented-control name="view" value="grid" options='${OPTIONS}'></bds-segmented-control>`) as BdsSegmentedControl;
102
+ el.value = 'list';
103
+ el.formResetCallback();
104
+ expect(el.value).toBe('grid');
105
+ cleanup(el);
106
+ });
107
+
108
+ it('falls back to the first option on form reset when no initial value was set', async () => {
109
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`) as BdsSegmentedControl;
110
+ el.value = 'grid';
111
+ el.formResetCallback();
112
+ expect(el.value).toBe('list');
113
+ cleanup(el);
114
+ });
115
+
116
+ it('exposes --control_count and --control_selected-index on the control for CSS-driven positioning', async () => {
117
+ const el = await fixture(`<bds-segmented-control name="view" value="grid" options='${OPTIONS}'></bds-segmented-control>`);
118
+ const control = el.shadowRoot!.querySelector<HTMLElement>('.control')!;
119
+ expect(control.style.getPropertyValue('--control_count')).toBe('3');
120
+ expect(control.style.getPropertyValue('--control_selected-index')).toBe('1');
121
+ cleanup(el);
122
+ });
123
+
124
+ it('uses a focus-visible selector that can actually match (label wrapping the radio)', () => {
125
+ const cssText = BdsSegmentedControl.styles.toString();
126
+ expect(cssText).not.toContain('.item-label');
127
+ expect(cssText).toContain(':has(> .radio:focus-visible)');
128
+ });
74
129
  });