@boostdev/design-system-components 2.15.0 → 2.17.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 (299) hide show
  1. package/AGENTS.md +22 -15
  2. package/dist/client.cjs +271 -205
  3. package/dist/client.css +813 -650
  4. package/dist/client.d.cts +19 -4
  5. package/dist/client.d.ts +19 -4
  6. package/dist/client.js +206 -139
  7. package/dist/index.cjs +271 -205
  8. package/dist/index.css +813 -650
  9. package/dist/index.d.cts +19 -4
  10. package/dist/index.d.ts +19 -4
  11. package/dist/index.js +206 -139
  12. package/dist/web-components/chunk-2DHW2WJZ.js +154 -0
  13. package/dist/web-components/{chunk-3WAV6MDN.js → chunk-3FSGP35V.js} +1 -0
  14. package/dist/web-components/{chunk-SYNLFKD3.js → chunk-3LQKFSMR.js} +6 -0
  15. package/dist/web-components/{chunk-KA3VPKMY.js → chunk-4MB2ICSK.js} +1 -0
  16. package/dist/web-components/{chunk-6G22I2CK.js → chunk-5ATTKUCP.js} +33 -3
  17. package/dist/web-components/{chunk-IDSM74LM.js → chunk-6XL5P76D.js} +3 -3
  18. package/dist/web-components/{chunk-P24JYC5V.js → chunk-7VXRMCAL.js} +10 -4
  19. package/dist/web-components/{chunk-CUKQ2L5B.js → chunk-AGI6YQRC.js} +9 -16
  20. package/dist/web-components/chunk-BDRV452N.js +51 -0
  21. package/dist/web-components/{chunk-3AY2YKCM.js → chunk-CKCPV347.js} +1 -0
  22. package/dist/web-components/{chunk-HBLJ4SLY.js → chunk-DD3OUXNG.js} +5 -5
  23. package/dist/web-components/{chunk-RVF7W653.js → chunk-DGOCPKHI.js} +30 -10
  24. package/dist/web-components/{chunk-2OLVQUBU.js → chunk-EYCW7TKB.js} +8 -15
  25. package/dist/web-components/{chunk-VANAKB6D.js → chunk-GPJWLAKU.js} +33 -16
  26. package/dist/web-components/{chunk-VED2DFJ6.js → chunk-GQNQBEYR.js} +8 -15
  27. package/dist/web-components/{chunk-WFVKU4GX.js → chunk-IKAJLROF.js} +49 -4
  28. package/dist/web-components/{chunk-RSZ3BWBK.js → chunk-KSK4RZZQ.js} +30 -7
  29. package/dist/web-components/{chunk-FEDABOR5.js → chunk-LHFUXZ52.js} +7 -15
  30. package/dist/web-components/{chunk-M3ECULRH.js → chunk-RBF7WPLI.js} +10 -8
  31. package/dist/web-components/{chunk-BPYKZVDK.js → chunk-SJLMW2VR.js} +2 -2
  32. package/dist/web-components/chunk-SRKQ6GST.js +40 -0
  33. package/dist/web-components/{chunk-5HUAFCU6.js → chunk-TJV7UIXC.js} +7 -0
  34. package/dist/web-components/{chunk-LDIPCTRN.js → chunk-WWWJCSXN.js} +50 -4
  35. package/dist/web-components/{chunk-J3HVUIUH.js → chunk-XI2U6U26.js} +8 -15
  36. package/dist/web-components/{chunk-JH6LQKLK.js → chunk-XJU7KITO.js} +8 -15
  37. package/dist/web-components/{chunk-JV5QT2OU.js → chunk-YLDQOESI.js} +27 -13
  38. package/dist/web-components/globals.js +1 -1
  39. package/dist/web-components/index.d.ts +1 -0
  40. package/dist/web-components/index.js +35 -30
  41. package/dist/web-components/interaction/bds-dropdown-menu-group.d.ts +41 -0
  42. package/dist/web-components/interaction/bds-dropdown-menu-group.js +7 -0
  43. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +14 -1
  44. package/dist/web-components/interaction/bds-dropdown-menu-item.js +1 -1
  45. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +2 -1
  46. package/dist/web-components/interaction/bds-dropdown-menu.js +1 -1
  47. package/dist/web-components/interaction/bds-tab.d.ts +5 -0
  48. package/dist/web-components/interaction/bds-tab.js +1 -1
  49. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  50. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +6 -0
  51. package/dist/web-components/interaction/form/bds-checkbox-group.js +2 -1
  52. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  53. package/dist/web-components/interaction/form/bds-combobox.js +2 -1
  54. package/dist/web-components/interaction/form/bds-form-input.js +2 -1
  55. package/dist/web-components/interaction/form/bds-number-input.js +2 -1
  56. package/dist/web-components/interaction/form/bds-radio-group.d.ts +5 -0
  57. package/dist/web-components/interaction/form/bds-radio-group.js +2 -1
  58. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  59. package/dist/web-components/interaction/form/bds-select.js +2 -1
  60. package/dist/web-components/interaction/form/bds-slider.js +2 -1
  61. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  62. package/dist/web-components/interaction/form/bds-textarea.js +2 -1
  63. package/dist/web-components/ui/bds-alert.d.ts +8 -1
  64. package/dist/web-components/ui/bds-alert.js +1 -1
  65. package/dist/web-components/ui/bds-avatar.d.ts +11 -0
  66. package/dist/web-components/ui/bds-avatar.js +1 -1
  67. package/dist/web-components/ui/bds-badge.js +1 -1
  68. package/dist/web-components/ui/bds-notification-banner.d.ts +14 -1
  69. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  70. package/dist/web-components/ui/bds-rating.js +1 -1
  71. package/dist/web-components/ui/bds-section-header.d.ts +6 -0
  72. package/dist/web-components/ui/bds-section-header.js +1 -1
  73. package/dist/web-components/ui/bds-separator.js +1 -1
  74. package/dist/web-components/ui/bds-table.js +1 -1
  75. package/package.json +6 -2
  76. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +35 -4
  77. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +58 -10
  78. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +114 -1
  79. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +25 -0
  80. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +69 -17
  81. package/src/components/interaction/Rating/Rating.mdx +4 -1
  82. package/src/components/interaction/Rating/Rating.module.css +9 -0
  83. package/src/components/interaction/Rating/Rating.spec.tsx +9 -0
  84. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -0
  85. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +7 -0
  86. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +2 -1
  87. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +8 -1
  88. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +27 -0
  89. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +1 -1
  90. package/src/components/interaction/form/Combobox/Combobox.module.css +1 -0
  91. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +7 -0
  92. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -0
  93. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -0
  94. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +7 -0
  95. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -0
  96. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +7 -0
  97. package/src/components/interaction/form/Radio/Radio.module.css +1 -0
  98. package/src/components/interaction/form/Radio/Radio.spec.tsx +7 -0
  99. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +2 -1
  100. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +8 -1
  101. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +27 -0
  102. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +1 -1
  103. package/src/components/interaction/form/Select/Select.module.css +1 -0
  104. package/src/components/interaction/form/Select/Select.spec.tsx +7 -0
  105. package/src/components/interaction/form/Switch/Switch.module.css +1 -0
  106. package/src/components/interaction/form/Switch/Switch.spec.tsx +7 -0
  107. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -0
  108. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +7 -0
  109. package/src/components/interaction/form/atoms/Message.module.css +14 -3
  110. package/src/components/interaction/form/atoms/Message.spec.tsx +22 -0
  111. package/src/components/interaction/form/atoms/Message.tsx +5 -0
  112. package/src/components/layout/SectionHeader/SectionHeader.mdx +18 -5
  113. package/src/components/layout/SectionHeader/SectionHeader.module.css +35 -14
  114. package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +44 -0
  115. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +5 -0
  116. package/src/components/layout/SectionHeader/SectionHeader.tsx +3 -1
  117. package/src/components/ui/Alert/Alert.mdx +12 -1
  118. package/src/components/ui/Alert/Alert.module.css +32 -3
  119. package/src/components/ui/Alert/Alert.spec.tsx +27 -0
  120. package/src/components/ui/Alert/Alert.stories.tsx +20 -0
  121. package/src/components/ui/Alert/Alert.tsx +8 -1
  122. package/src/components/ui/Avatar/Avatar.mdx +22 -2
  123. package/src/components/ui/Avatar/Avatar.module.css +29 -2
  124. package/src/components/ui/Avatar/Avatar.spec.tsx +26 -0
  125. package/src/components/ui/Avatar/Avatar.stories.tsx +23 -0
  126. package/src/components/ui/Avatar/Avatar.tsx +4 -2
  127. package/src/components/ui/Avatar/avatarTone.spec.ts +41 -0
  128. package/src/components/ui/Avatar/avatarTone.ts +22 -0
  129. package/src/components/ui/Badge/Badge.mdx +1 -1
  130. package/src/components/ui/Badge/Badge.module.css +2 -2
  131. package/src/components/ui/Badge/Badge.spec.tsx +8 -0
  132. package/src/components/ui/Collapsible/Collapsible.module.css +1 -2
  133. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +27 -1
  134. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +33 -3
  135. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +35 -0
  136. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +25 -0
  137. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +15 -1
  138. package/src/components/ui/Separator/Separator.mdx +1 -0
  139. package/src/components/ui/Separator/Separator.module.css +6 -0
  140. package/src/components/ui/Separator/Separator.spec.tsx +19 -0
  141. package/src/components/ui/Separator/Separator.stories.tsx +15 -0
  142. package/src/components/ui/Table/Table.mdx +4 -1
  143. package/src/components/ui/Table/Table.module.css +7 -7
  144. package/src/components/ui/Table/Table.spec.tsx +11 -0
  145. package/src/components/ui/Tabs/Tabs.mdx +11 -5
  146. package/src/components/ui/Tabs/Tabs.module.css +23 -5
  147. package/src/components/ui/Tabs/Tabs.spec.tsx +39 -0
  148. package/src/components/ui/Tabs/Tabs.tsx +2 -1
  149. package/src/components/ui/Tabs/getNodeText.spec.tsx +23 -0
  150. package/src/components/ui/Tabs/getNodeText.ts +13 -0
  151. package/src/components/ui/Tooltip/Tooltip.mdx +6 -3
  152. package/src/components/ui/Tooltip/Tooltip.module.css +6 -2
  153. package/src/components/ui/Tooltip/Tooltip.spec.tsx +10 -0
  154. package/src/components/ui/Tooltip/Tooltip.stories.tsx +13 -0
  155. package/src/css/bdc.css +5 -2
  156. package/src/css/bdc.spec.ts +65 -0
  157. package/src/stories/DesignSystem/Borders.mdx +17 -9
  158. package/src/test/token-contract.spec.ts +1 -0
  159. package/src/web-components/globals.ts +10 -4
  160. package/src/web-components/index.ts +1 -0
  161. package/src/web-components/interaction/BdsAccordion.mdx +1 -1
  162. package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
  163. package/src/web-components/interaction/BdsButton.mdx +1 -1
  164. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  165. package/src/web-components/interaction/BdsCollapsible.mdx +1 -1
  166. package/src/web-components/interaction/BdsCollapsible.stories.tsx +1 -1
  167. package/src/web-components/interaction/BdsDialog.mdx +1 -1
  168. package/src/web-components/interaction/BdsDialog.stories.tsx +1 -1
  169. package/src/web-components/interaction/BdsDrawer.mdx +1 -1
  170. package/src/web-components/interaction/BdsDrawer.stories.tsx +1 -1
  171. package/src/web-components/interaction/BdsDropdownMenu.mdx +62 -6
  172. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +32 -7
  173. package/src/web-components/interaction/BdsPopover.mdx +1 -1
  174. package/src/web-components/interaction/BdsPopover.stories.tsx +1 -1
  175. package/src/web-components/interaction/BdsSkipLink.mdx +1 -1
  176. package/src/web-components/interaction/BdsSkipLink.stories.tsx +1 -1
  177. package/src/web-components/interaction/BdsTabs.mdx +10 -1
  178. package/src/web-components/interaction/BdsTabs.stories.tsx +1 -1
  179. package/src/web-components/interaction/BdsToastProvider.mdx +1 -1
  180. package/src/web-components/interaction/BdsToastProvider.stories.tsx +1 -1
  181. package/src/web-components/interaction/BdsTooltip.mdx +13 -5
  182. package/src/web-components/interaction/BdsTooltip.stories.tsx +12 -12
  183. package/src/web-components/interaction/bds-dropdown-menu-group.ts +72 -0
  184. package/src/web-components/interaction/bds-dropdown-menu-item.ts +65 -17
  185. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +135 -0
  186. package/src/web-components/interaction/bds-dropdown-menu.ts +5 -4
  187. package/src/web-components/interaction/bds-tab.spec.ts +34 -2
  188. package/src/web-components/interaction/bds-tab.ts +31 -6
  189. package/src/web-components/interaction/bds-tooltip.spec.ts +16 -0
  190. package/src/web-components/interaction/bds-tooltip.ts +10 -8
  191. package/src/web-components/interaction/form/BdsCheckbox.mdx +5 -1
  192. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +1 -1
  193. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +5 -1
  194. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +1 -1
  195. package/src/web-components/interaction/form/BdsCombobox.mdx +5 -1
  196. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +1 -1
  197. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  198. package/src/web-components/interaction/form/BdsFileInput.mdx +1 -1
  199. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +1 -1
  200. package/src/web-components/interaction/form/BdsFormInput.mdx +8 -1
  201. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +1 -1
  202. package/src/web-components/interaction/form/BdsNumberInput.mdx +5 -1
  203. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +1 -1
  204. package/src/web-components/interaction/form/BdsRadio.mdx +5 -1
  205. package/src/web-components/interaction/form/BdsRadio.stories.tsx +1 -1
  206. package/src/web-components/interaction/form/BdsRadioGroup.mdx +5 -1
  207. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +1 -1
  208. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +1 -1
  209. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +1 -1
  210. package/src/web-components/interaction/form/BdsSelect.mdx +5 -1
  211. package/src/web-components/interaction/form/BdsSelect.stories.tsx +1 -1
  212. package/src/web-components/interaction/form/BdsSlider.mdx +5 -1
  213. package/src/web-components/interaction/form/BdsSlider.stories.tsx +1 -1
  214. package/src/web-components/interaction/form/BdsSwitch.mdx +5 -1
  215. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +1 -1
  216. package/src/web-components/interaction/form/BdsTextarea.mdx +5 -1
  217. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +1 -1
  218. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +32 -3
  219. package/src/web-components/interaction/form/bds-checkbox-group.ts +33 -17
  220. package/src/web-components/interaction/form/bds-checkbox.ts +1 -0
  221. package/src/web-components/interaction/form/bds-combobox.ts +6 -16
  222. package/src/web-components/interaction/form/bds-form-input.ts +6 -16
  223. package/src/web-components/interaction/form/bds-number-input.ts +6 -16
  224. package/src/web-components/interaction/form/bds-radio-group.spec.ts +32 -3
  225. package/src/web-components/interaction/form/bds-radio-group.ts +26 -14
  226. package/src/web-components/interaction/form/bds-radio.ts +1 -0
  227. package/src/web-components/interaction/form/bds-select.ts +6 -16
  228. package/src/web-components/interaction/form/bds-slider.ts +5 -16
  229. package/src/web-components/interaction/form/bds-switch.ts +1 -0
  230. package/src/web-components/interaction/form/bds-textarea.ts +6 -16
  231. package/src/web-components/interaction/form/field-message.spec.ts +62 -0
  232. package/src/web-components/interaction/form/field-message.ts +55 -0
  233. package/src/web-components/layout/BdsGrid.mdx +1 -1
  234. package/src/web-components/layout/BdsGrid.stories.tsx +1 -1
  235. package/src/web-components/layout/BdsGridItem.mdx +1 -1
  236. package/src/web-components/layout/BdsGridItem.stories.tsx +1 -1
  237. package/src/web-components/ui/BdsAlert.mdx +20 -1
  238. package/src/web-components/ui/BdsAlert.stories.tsx +29 -3
  239. package/src/web-components/ui/BdsAvatar.mdx +20 -3
  240. package/src/web-components/ui/BdsAvatar.stories.tsx +25 -2
  241. package/src/web-components/ui/BdsBadge.mdx +2 -1
  242. package/src/web-components/ui/BdsBadge.stories.tsx +1 -1
  243. package/src/web-components/ui/BdsBreadcrumb.mdx +1 -1
  244. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +1 -1
  245. package/src/web-components/ui/BdsButtonContainer.mdx +1 -1
  246. package/src/web-components/ui/BdsButtonContainer.stories.tsx +1 -1
  247. package/src/web-components/ui/BdsCalendar.mdx +1 -1
  248. package/src/web-components/ui/BdsCalendar.stories.tsx +1 -1
  249. package/src/web-components/ui/BdsCalendarRange.mdx +1 -1
  250. package/src/web-components/ui/BdsCalendarRange.stories.tsx +1 -1
  251. package/src/web-components/ui/BdsCard.mdx +1 -1
  252. package/src/web-components/ui/BdsCard.stories.tsx +1 -1
  253. package/src/web-components/ui/BdsCarousel.mdx +1 -1
  254. package/src/web-components/ui/BdsCarousel.stories.tsx +1 -1
  255. package/src/web-components/ui/BdsDescriptionList.mdx +1 -1
  256. package/src/web-components/ui/BdsDescriptionList.stories.tsx +1 -1
  257. package/src/web-components/ui/BdsIconWrapper.mdx +1 -1
  258. package/src/web-components/ui/BdsIconWrapper.stories.tsx +1 -1
  259. package/src/web-components/ui/BdsLink.mdx +1 -1
  260. package/src/web-components/ui/BdsLink.stories.tsx +1 -1
  261. package/src/web-components/ui/BdsLoading.mdx +1 -1
  262. package/src/web-components/ui/BdsLoading.stories.tsx +1 -1
  263. package/src/web-components/ui/BdsNotificationBanner.mdx +11 -2
  264. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +29 -2
  265. package/src/web-components/ui/BdsPagination.mdx +1 -1
  266. package/src/web-components/ui/BdsPagination.stories.tsx +1 -1
  267. package/src/web-components/ui/BdsProgress.mdx +1 -1
  268. package/src/web-components/ui/BdsProgress.stories.tsx +1 -1
  269. package/src/web-components/ui/BdsProgressCircle.mdx +1 -1
  270. package/src/web-components/ui/BdsProgressCircle.stories.tsx +1 -1
  271. package/src/web-components/ui/BdsRating.mdx +5 -2
  272. package/src/web-components/ui/BdsRating.stories.tsx +1 -1
  273. package/src/web-components/ui/BdsSectionHeader.mdx +18 -6
  274. package/src/web-components/ui/BdsSectionHeader.stories.tsx +10 -2
  275. package/src/web-components/ui/BdsSeparator.mdx +2 -1
  276. package/src/web-components/ui/BdsSeparator.stories.tsx +13 -1
  277. package/src/web-components/ui/BdsSkeleton.mdx +1 -1
  278. package/src/web-components/ui/BdsSkeleton.stories.tsx +1 -1
  279. package/src/web-components/ui/BdsTable.mdx +5 -1
  280. package/src/web-components/ui/BdsTable.stories.tsx +1 -1
  281. package/src/web-components/ui/BdsTypography.mdx +1 -1
  282. package/src/web-components/ui/BdsTypography.stories.tsx +1 -1
  283. package/src/web-components/ui/bds-alert.spec.ts +34 -0
  284. package/src/web-components/ui/bds-alert.ts +36 -3
  285. package/src/web-components/ui/bds-avatar.spec.ts +47 -0
  286. package/src/web-components/ui/bds-avatar.ts +36 -3
  287. package/src/web-components/ui/bds-badge.spec.ts +6 -0
  288. package/src/web-components/ui/bds-badge.ts +2 -2
  289. package/src/web-components/ui/bds-notification-banner.spec.ts +52 -0
  290. package/src/web-components/ui/bds-notification-banner.ts +55 -3
  291. package/src/web-components/ui/bds-rating.spec.ts +7 -0
  292. package/src/web-components/ui/bds-rating.ts +6 -0
  293. package/src/web-components/ui/bds-section-header.spec.ts +36 -3
  294. package/src/web-components/ui/bds-section-header.ts +32 -10
  295. package/src/web-components/ui/bds-separator.spec.ts +8 -0
  296. package/src/web-components/ui/bds-separator.ts +7 -0
  297. package/src/web-components/ui/bds-table.spec.ts +11 -0
  298. package/src/web-components/ui/bds-table.ts +5 -5
  299. package/dist/web-components/chunk-QZAIRJ75.js +0 -111
@@ -37,7 +37,7 @@ function ToastDemo() {
37
37
  const meta = {
38
38
  title: 'Web Components/Interaction/ToastProvider',
39
39
  component: ToastDemo,
40
- tags: ['!stable', 'experimental'],
40
+ tags: ['!stable', 'alpha'],
41
41
  parameters: { layout: 'centered' },
42
42
  } satisfies Meta<typeof ToastDemo>;
43
43
 
@@ -7,7 +7,7 @@ import * as Stories from './BdsTooltip.stories';
7
7
 
8
8
  Framework-agnostic Tooltip custom element. Displays a small informational label on hover and keyboard focus using CSS-only visibility — no JavaScript positioning required.
9
9
 
10
- > **Status: experimental** — API may change before stable release.
10
+ > **Status: alpha** — API may change before stable release.
11
11
 
12
12
  ## Installation
13
13
 
@@ -52,6 +52,9 @@ Or in HTML without a bundler:
52
52
  ### Right placement
53
53
  <Canvas of={Stories.Right} />
54
54
 
55
+ ### Long text
56
+ <Canvas of={Stories.LongText} />
57
+
55
58
  ### All placements
56
59
  <Canvas of={Stories.AllPlacements} />
57
60
 
@@ -74,10 +77,15 @@ Or in HTML without a bundler:
74
77
 
75
78
  ## CSS Custom Properties
76
79
 
77
- | Property | Description |
78
- |----------|-------------|
79
- | `--tooltip_bg` | Tooltip background colour (default: `var(--bds-color_on-bg)`) |
80
- | `--tooltip_text` | Tooltip text colour (default: `var(--bds-color_bg)`) |
80
+ | Property | Default | Description |
81
+ |----------|---------|-------------|
82
+ | `--tooltip_bg` | `var(--bds-color_on-bg)` | Tooltip background colour |
83
+ | `--tooltip_text` | `var(--bds-color_bg)` | Tooltip text colour |
84
+ | `--tooltip_radius` | `var(--bds-border_radius--xs)` | Tooltip corner radius |
85
+ | `--tooltip_padding` | `var(--bds-space_xxs) var(--bds-space_s)` | Tooltip padding |
86
+ | `--tooltip_font-size` | `var(--bds-font_size--body--s)` | Tooltip text size |
87
+ | `--tooltip_offset` | `var(--bds-space_xs)` | Gap between the trigger and the tooltip |
88
+ | `--tooltip_max-inline-size` | `20rem` | Maximum inline size before content wraps. The tooltip is as wide as its text (`max-content`) up to this value |
81
89
 
82
90
  ## Usage in plain HTML
83
91
 
@@ -16,14 +16,14 @@ function BdsTooltip({
16
16
  return React.createElement(
17
17
  'bds-tooltip',
18
18
  { content, placement },
19
- children ?? React.createElement('button', { type: 'button' }, 'Hover me'),
19
+ children ?? React.createElement('bds-button', null, 'Hover me'),
20
20
  );
21
21
  }
22
22
 
23
23
  const meta = {
24
24
  title: 'Web Components/UI/Tooltip',
25
25
  component: BdsTooltip,
26
- tags: ['!stable', 'experimental'],
26
+ tags: ['!stable', 'alpha'],
27
27
  parameters: { layout: 'centered' },
28
28
  argTypes: {
29
29
  content: { control: 'text' },
@@ -44,11 +44,7 @@ export const OnButton: Story = {
44
44
  React.createElement(
45
45
  'bds-tooltip',
46
46
  { content: args.content, placement: args.placement },
47
- React.createElement(
48
- 'button',
49
- { type: 'button', style: { padding: '0.5rem 1rem', cursor: 'pointer' } },
50
- 'Save',
51
- ),
47
+ React.createElement('bds-button', null, 'Save'),
52
48
  ),
53
49
  };
54
50
 
@@ -64,6 +60,14 @@ export const Right: Story = {
64
60
  args: { content: 'Appears to the right', placement: 'right' },
65
61
  };
66
62
 
63
+ export const LongText: Story = {
64
+ args: {
65
+ content:
66
+ 'Longer explanation that wraps onto a second line once it reaches the maximum width of the tooltip.',
67
+ placement: 'bottom',
68
+ },
69
+ };
70
+
67
71
  export const AllPlacements: Story = {
68
72
  render: () => (
69
73
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, auto)', gap: '4rem', padding: '3rem' }}>
@@ -74,11 +78,7 @@ export const AllPlacements: Story = {
74
78
  React.createElement(
75
79
  'bds-tooltip',
76
80
  { content: `Placement: ${placement}`, placement },
77
- React.createElement(
78
- 'button',
79
- { type: 'button', style: { padding: '0.5rem 1rem', cursor: 'pointer' } },
80
- placement,
81
- ),
81
+ React.createElement('bds-button', null, placement),
82
82
  ),
83
83
  ),
84
84
  )}
@@ -0,0 +1,72 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ /**
4
+ * `<bds-dropdown-menu-group>` — a labelled group of `<bds-dropdown-menu-item>`
5
+ * elements inside a `<bds-dropdown-menu>`.
6
+ *
7
+ * The shadow root renders a `role="group"` element named by the visible label
8
+ * (`aria-labelledby` inside the same shadow root). The label is not focusable;
9
+ * keyboard navigation in the menu only visits the items.
10
+ *
11
+ * Attributes:
12
+ * label — visible group label, also the accessible name of the group
13
+ *
14
+ * Slots:
15
+ * (default) — `<bds-dropdown-menu-item>` elements
16
+ *
17
+ * @example
18
+ * <bds-dropdown-menu-group label="Edit">
19
+ * <bds-dropdown-menu-item value="edit">Edit</bds-dropdown-menu-item>
20
+ * </bds-dropdown-menu-group>
21
+ */
22
+ export class BdsDropdownMenuGroup extends LitElement {
23
+ static styles = css`
24
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it here. */
25
+ :host,
26
+ *,
27
+ *::before,
28
+ *::after {
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ :host {
33
+ display: block;
34
+ }
35
+
36
+ .label {
37
+ padding: var(--dropdown_group-label-padding, var(--bds-space_xs) var(--bds-space_s) var(--bds-space_xxs));
38
+ font-family: var(--bds-font_family--body);
39
+ font-size: var(--dropdown_group-label-font-size, var(--bds-font_size--body--s));
40
+ font-weight: var(--dropdown_group-label-font-weight, var(--bds-font_weight--bold));
41
+ color: var(--dropdown_group-label-color, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)));
42
+ }
43
+ `;
44
+
45
+ static properties = {
46
+ label: { type: String, reflect: true },
47
+ };
48
+
49
+ declare label: string;
50
+
51
+ constructor() {
52
+ super();
53
+ this.label = '';
54
+ }
55
+
56
+ render() {
57
+ return html`
58
+ <div role="group" aria-labelledby=${this.label ? 'label' : nothing}>
59
+ ${this.label ? html`<div id="label" class="label">${this.label}</div>` : nothing}
60
+ <slot></slot>
61
+ </div>
62
+ `;
63
+ }
64
+ }
65
+
66
+ customElements.define('bds-dropdown-menu-group', BdsDropdownMenuGroup);
67
+
68
+ declare global {
69
+ interface HTMLElementTagNameMap {
70
+ 'bds-dropdown-menu-group': BdsDropdownMenuGroup;
71
+ }
72
+ }
@@ -1,4 +1,4 @@
1
- import { LitElement, css, html } from 'lit';
1
+ import { LitElement, css, html, nothing } from 'lit';
2
2
 
3
3
  /**
4
4
  * `<bds-dropdown-menu-item>` — a single item inside a `<bds-dropdown-menu>`.
@@ -6,9 +6,12 @@ import { LitElement, css, html } from 'lit';
6
6
  * Attributes:
7
7
  * value — value associated with this item
8
8
  * disabled — disables this item
9
- * destructive — renders the item in a destructive (red) style
9
+ * destructive — renders the item in a destructive (error colour) style
10
+ * shortcut — keyboard shortcut as display text (decorative, `aria-hidden`);
11
+ * the consumer wires the actual key
10
12
  *
11
13
  * Slots:
14
+ * icon — optional icon shown before the label (decorative)
12
15
  * (default) — item label text
13
16
  *
14
17
  * Events:
@@ -34,7 +37,7 @@ export class BdsDropdownMenuItem extends LitElement {
34
37
  }
35
38
 
36
39
  :host([disabled]) {
37
- opacity: 0.5;
40
+ opacity: var(--dropdown_item-opacity--disabled, 0.4);
38
41
  pointer-events: none;
39
42
  }
40
43
 
@@ -43,36 +46,67 @@ export class BdsDropdownMenuItem extends LitElement {
43
46
  align-items: center;
44
47
  gap: var(--bds-space_xs);
45
48
  inline-size: 100%;
46
- padding: var(--bds-space_xs) var(--bds-space_s);
47
- font-size: var(--bds-font_size--body);
49
+ padding: var(--dropdown_item-padding, var(--bds-space_xs) var(--bds-space_s));
50
+ font-size: var(--dropdown_item-font-size, var(--bds-font_size--body));
48
51
  font-family: var(--bds-font_family--body);
49
- color: var(--bds-color_on-bg);
52
+ color: inherit;
50
53
  background: none;
51
54
  border: none;
52
55
  text-align: start;
53
56
  cursor: pointer;
54
- border-radius: var(--bds-border_radius--xs);
57
+ border-radius: var(--dropdown_item-radius, var(--bds-border_radius--xs));
55
58
  transition: var(--bds-animation_transition);
56
59
  }
57
60
 
58
61
  :host([destructive]) .item {
59
- color: var(--bds-color_error);
62
+ color: var(--dropdown_item-color--destructive, var(--bds-color_error_on-bg));
63
+ }
64
+
65
+ .item:focus-visible {
66
+ outline: var(--bds-outline_default);
67
+ outline-offset: calc(var(--bds-outline_offset) * -1);
68
+ }
69
+
70
+ .item:not(:disabled):focus-visible {
71
+ background-color: var(--dropdown_item-bg--hover, var(--bds-color_primary--subtle, var(--bds-color_blue--subtle)));
72
+ color: var(--dropdown_item-color--hover, var(--bds-color_on-primary--subtle, var(--bds-color_on-blue--subtle)));
73
+ }
74
+
75
+ :host([destructive]) .item:not(:disabled):focus-visible {
76
+ background-color: var(--dropdown_item-bg--destructive-hover, var(--bds-color_error--subtle));
77
+ color: var(--dropdown_item-color--destructive-hover, var(--bds-color_on-error--subtle));
60
78
  }
61
79
 
62
80
  @media (hover: hover) and (pointer: fine) {
63
- .item:hover {
64
- background-color: var(--dropdown_item-bg--hover, var(--bds-color_bg--subtle));
65
- color: var(--dropdown_item-color--hover, var(--bds-color_interactive_on-bg));
81
+ .item:not(:disabled):hover {
82
+ background-color: var(--dropdown_item-bg--hover, var(--bds-color_primary--subtle, var(--bds-color_blue--subtle)));
83
+ color: var(--dropdown_item-color--hover, var(--bds-color_on-primary--subtle, var(--bds-color_on-blue--subtle)));
66
84
  }
67
85
 
68
- :host([destructive]) .item:hover {
69
- background-color: color-mix(in srgb, var(--bds-color_error) 10%, transparent);
86
+ :host([destructive]) .item:not(:disabled):hover {
87
+ background-color: var(--dropdown_item-bg--destructive-hover, var(--bds-color_error--subtle));
88
+ color: var(--dropdown_item-color--destructive-hover, var(--bds-color_on-error--subtle));
70
89
  }
71
90
  }
72
91
 
73
- .item:focus-visible {
74
- outline: var(--bds-outline_default);
75
- outline-offset: var(--bds-outline_offset);
92
+ .icon[hidden] {
93
+ display: none;
94
+ }
95
+
96
+ .icon {
97
+ display: flex;
98
+ align-items: center;
99
+ flex-shrink: 0;
100
+ }
101
+
102
+ .label {
103
+ flex: 1;
104
+ }
105
+
106
+ .shortcut {
107
+ flex-shrink: 0;
108
+ color: var(--dropdown_shortcut-color, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)));
109
+ font-size: var(--dropdown_shortcut-font-size, var(--bds-font_size--body--s));
76
110
  }
77
111
  `;
78
112
 
@@ -80,17 +114,27 @@ export class BdsDropdownMenuItem extends LitElement {
80
114
  value: { type: String, reflect: true },
81
115
  disabled: { type: Boolean, reflect: true },
82
116
  destructive: { type: Boolean, reflect: true },
117
+ shortcut: { type: String, reflect: true },
118
+ _hasIcon: { state: true },
83
119
  };
84
120
 
85
121
  declare value: string;
86
122
  declare disabled: boolean;
87
123
  declare destructive: boolean;
124
+ declare shortcut: string;
125
+ declare _hasIcon: boolean;
88
126
 
89
127
  constructor() {
90
128
  super();
91
129
  this.value = '';
92
130
  this.disabled = false;
93
131
  this.destructive = false;
132
+ this.shortcut = '';
133
+ this._hasIcon = false;
134
+ }
135
+
136
+ private _onIconSlotChange(e: Event) {
137
+ this._hasIcon = (e.target as HTMLSlotElement).assignedNodes().length > 0;
94
138
  }
95
139
 
96
140
  private _handleClick() {
@@ -120,7 +164,11 @@ export class BdsDropdownMenuItem extends LitElement {
120
164
  @click=${this._handleClick}
121
165
  @keydown=${this._handleKeydown}
122
166
  >
123
- <slot></slot>
167
+ <span class="icon" aria-hidden="true" ?hidden=${!this._hasIcon}>
168
+ <slot name="icon" @slotchange=${this._onIconSlotChange}></slot>
169
+ </span>
170
+ <span class="label"><slot></slot></span>
171
+ ${this.shortcut ? html`<span class="shortcut" aria-hidden="true">${this.shortcut}</span>` : nothing}
124
172
  </button>
125
173
  `;
126
174
  }
@@ -1,6 +1,13 @@
1
1
  import { fixture, cleanup } from '../test/helpers';
2
2
  import { BdsDropdownMenu } from './bds-dropdown-menu';
3
3
  import { BdsDropdownMenuItem } from './bds-dropdown-menu-item';
4
+ import { BdsDropdownMenuGroup } from './bds-dropdown-menu-group';
5
+ import { readFileSync } from 'node:fs';
6
+ import { resolve } from 'node:path';
7
+
8
+ const itemSource = readFileSync(resolve(process.cwd(), 'src/web-components/interaction/bds-dropdown-menu-item.ts'), 'utf8');
9
+ const groupSource = readFileSync(resolve(process.cwd(), 'src/web-components/interaction/bds-dropdown-menu-group.ts'), 'utf8');
10
+ const menuSource = readFileSync(resolve(process.cwd(), 'src/web-components/interaction/bds-dropdown-menu.ts'), 'utf8');
4
11
 
5
12
  describe('bds-dropdown-menu', () => {
6
13
  it('is registered as a custom element', () => {
@@ -163,3 +170,131 @@ describe('bds-dropdown-menu-item', () => {
163
170
  cleanup(el);
164
171
  });
165
172
  });
173
+
174
+ describe('bds-dropdown-menu-item colour', () => {
175
+ it('inherits the colour from the menu panel so --dropdown_on-color applies', () => {
176
+ expect(String(BdsDropdownMenuItem.styles)).toMatch(/\.item \{[^}]*color:\s*inherit/);
177
+ expect(String(BdsDropdownMenuItem.styles)).not.toMatch(/\.item \{[^}]*color:\s*var\(--bds-color_on-bg\)/);
178
+ expect(itemSource).toContain(':host([destructive]) .item {');
179
+ });
180
+ });
181
+
182
+ describe('bds-dropdown-menu-item shortcut, icon and destructive', () => {
183
+ it('renders the shortcut aria-hidden and reflects the attribute', async () => {
184
+ const el = await fixture('<bds-dropdown-menu-item value="edit" shortcut="⌘E">Edit</bds-dropdown-menu-item>') as BdsDropdownMenuItem;
185
+ const shortcut = el.shadowRoot!.querySelector('.shortcut')!;
186
+ expect(shortcut.textContent).toBe('⌘E');
187
+ expect(shortcut.getAttribute('aria-hidden')).toBe('true');
188
+ expect(el.getAttribute('shortcut')).toBe('⌘E');
189
+ cleanup(el);
190
+ });
191
+
192
+ it('omits the shortcut element when no shortcut is set', async () => {
193
+ const el = await fixture('<bds-dropdown-menu-item value="edit">Edit</bds-dropdown-menu-item>');
194
+ expect(el.shadowRoot!.querySelector('.shortcut')).toBeNull();
195
+ cleanup(el);
196
+ });
197
+
198
+ it('offers an icon slot before the label, hidden until filled', async () => {
199
+ const el = await fixture('<bds-dropdown-menu-item value="edit">Edit</bds-dropdown-menu-item>') as BdsDropdownMenuItem;
200
+ const icon = el.shadowRoot!.querySelector('.icon')!;
201
+ expect(icon.querySelector('slot[name="icon"]')).toBeTruthy();
202
+ expect(icon.hasAttribute('hidden')).toBe(true);
203
+ expect(icon.compareDocumentPosition(el.shadowRoot!.querySelector('.label')!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
204
+ cleanup(el);
205
+ });
206
+
207
+ it('reflects the destructive attribute', async () => {
208
+ const el = await fixture('<bds-dropdown-menu-item value="delete" destructive>Delete</bds-dropdown-menu-item>') as BdsDropdownMenuItem;
209
+ expect(el.destructive).toBe(true);
210
+ expect(el.hasAttribute('destructive')).toBe(true);
211
+ cleanup(el);
212
+ });
213
+
214
+ it.each([
215
+ '--dropdown_item-padding',
216
+ '--dropdown_item-radius',
217
+ '--dropdown_item-font-size',
218
+ '--dropdown_item-bg--hover',
219
+ '--dropdown_item-color--hover',
220
+ '--dropdown_item-opacity--disabled',
221
+ '--dropdown_item-color--destructive',
222
+ '--dropdown_item-bg--destructive-hover',
223
+ '--dropdown_item-color--destructive-hover',
224
+ '--dropdown_shortcut-color',
225
+ '--dropdown_shortcut-font-size',
226
+ ])('reads %s through var() in the item stylesheet', hook => {
227
+ expect(itemSource).toContain(`var(${hook},`);
228
+ });
229
+ });
230
+
231
+ describe('bds-dropdown-menu-group', () => {
232
+ it('is registered as a custom element', () => {
233
+ expect(customElements.get('bds-dropdown-menu-group')).toBe(BdsDropdownMenuGroup);
234
+ });
235
+
236
+ it('renders a role="group" named by its visible label', async () => {
237
+ const el = await fixture('<bds-dropdown-menu-group label="Edit"></bds-dropdown-menu-group>') as BdsDropdownMenuGroup;
238
+ const group = el.shadowRoot!.querySelector('[role="group"]')!;
239
+ const labelId = group.getAttribute('aria-labelledby')!;
240
+ const label = el.shadowRoot!.getElementById(labelId)!;
241
+ expect(label.textContent?.trim()).toBe('Edit');
242
+ cleanup(el);
243
+ });
244
+
245
+ it('renders no label element and no aria-labelledby without a label', async () => {
246
+ const el = await fixture('<bds-dropdown-menu-group></bds-dropdown-menu-group>') as BdsDropdownMenuGroup;
247
+ const group = el.shadowRoot!.querySelector('[role="group"]')!;
248
+ expect(group.hasAttribute('aria-labelledby')).toBe(false);
249
+ expect(el.shadowRoot!.querySelector('.label')).toBeNull();
250
+ cleanup(el);
251
+ });
252
+
253
+ it('updates the label when the attribute changes', async () => {
254
+ const el = await fixture('<bds-dropdown-menu-group label="Edit"></bds-dropdown-menu-group>') as BdsDropdownMenuGroup;
255
+ el.setAttribute('label', 'Danger');
256
+ await el.updateComplete;
257
+ expect(el.shadowRoot!.querySelector('.label')!.textContent?.trim()).toBe('Danger');
258
+ cleanup(el);
259
+ });
260
+
261
+ it.each([
262
+ '--dropdown_group-label-font-size',
263
+ '--dropdown_group-label-font-weight',
264
+ '--dropdown_group-label-color',
265
+ '--dropdown_group-label-padding',
266
+ ])('reads %s through var() in the group stylesheet', hook => {
267
+ expect(groupSource).toContain(`var(${hook},`);
268
+ });
269
+
270
+ it('moves focus from the last item of one group to the first of the next', async () => {
271
+ const el = await fixture(`
272
+ <bds-dropdown-menu>
273
+ <button slot="trigger" type="button">Options</button>
274
+ <bds-dropdown-menu-group label="Edit">
275
+ <bds-dropdown-menu-item value="a">A</bds-dropdown-menu-item>
276
+ <bds-dropdown-menu-item value="b">B</bds-dropdown-menu-item>
277
+ </bds-dropdown-menu-group>
278
+ <bds-dropdown-menu-group label="Danger">
279
+ <bds-dropdown-menu-item value="c" destructive>C</bds-dropdown-menu-item>
280
+ </bds-dropdown-menu-group>
281
+ </bds-dropdown-menu>
282
+ `) as BdsDropdownMenu;
283
+ const items = [...el.querySelectorAll('bds-dropdown-menu-item')] as BdsDropdownMenuItem[];
284
+ await Promise.all(items.map(item => item.updateComplete));
285
+ el._open();
286
+ await el.updateComplete;
287
+ const buttons = items.map(item => item.shadowRoot!.querySelector<HTMLButtonElement>('button')!);
288
+ const spy = vi.spyOn(buttons[2], 'focus');
289
+ buttons[1].focus();
290
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
291
+ expect(spy).toHaveBeenCalled();
292
+ cleanup(el);
293
+ });
294
+ });
295
+
296
+ describe('bds-dropdown-menu panel hooks', () => {
297
+ it.each(['--dropdown_padding', '--dropdown_border-color'])('reads %s through var()', hook => {
298
+ expect(menuSource).toContain(`var(${hook},`);
299
+ });
300
+ });
@@ -19,7 +19,8 @@ export type DropdownMenuPlacement = 'bottom-start' | 'bottom-end' | 'top-start'
19
19
  *
20
20
  * Slots:
21
21
  * trigger — the activating element (button or similar)
22
- * (default) — `<bds-dropdown-menu-item>` elements
22
+ * (default) — `<bds-dropdown-menu-item>` elements, optionally wrapped in
23
+ * `<bds-dropdown-menu-group label="...">`
23
24
  *
24
25
  * Events:
25
26
  * bds-open — fired when the menu opens
@@ -55,15 +56,15 @@ export class BdsDropdownMenu extends LitElement {
55
56
  inset-inline-start: 0;
56
57
  min-inline-size: 10rem;
57
58
  border: var(--dropdown_border, none);
58
- --bdc_color: currentcolor;
59
+ --bdc_color: var(--dropdown_border-color, var(--bds-color_on-bg--as-surface, var(--bds-color_on-bg--border, var(--bds-color_on-bg))));
59
60
  --bdc-outline_radius: var(--bds-border_radius--s);
60
61
 
61
62
  border-radius: var(--dropdown_radius, var(--bdc-outline_radius));
62
63
  background-color: var(--dropdown_color, var(--bds-color_bg--strong, var(--bds-color_bg)));
63
64
  color: var(--dropdown_on-color, var(--bds-color_on-bg));
64
65
  box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color),
65
- var(--dropdown_shadow, var(--bds-shadow_m));
66
- padding: var(--bds-space_xxs);
66
+ var(--dropdown_shadow, var(--bds-shadow_l));
67
+ padding: var(--dropdown_padding, var(--bds-space_xxs));
67
68
  z-index: var(--bds-z-index_dropdown);
68
69
  display: none;
69
70
  flex-direction: column;
@@ -14,6 +14,22 @@ describe('bds-tab', () => {
14
14
  cleanup(el);
15
15
  });
16
16
 
17
+ it('copies the slotted label into data-label to reserve the bold width', async () => {
18
+ const el = await fixture('<bds-tab>Overview</bds-tab>') as BdsTab;
19
+ const slot = el.shadowRoot!.querySelector('slot')!;
20
+ slot.dispatchEvent(new Event('slotchange'));
21
+ await el.updateComplete;
22
+ expect(el.shadowRoot!.querySelector('.label')!.getAttribute('data-label')).toBe('Overview');
23
+ cleanup(el);
24
+ });
25
+
26
+ it('styles a hidden ::after at the active weight', () => {
27
+ const css = BdsTab.styles.toString();
28
+ expect(css).toMatch(/\.label::after\s*\{[^}]*content:\s*attr\(data-label\)/);
29
+ expect(css).toMatch(/\.label::after\s*\{[^}]*font-weight:\s*var\(--tab_font-weight,\s*var\(--tab_font-weight--active,\s*var\(--bds-font_weight--bold\)\)\)/);
30
+ expect(css).toMatch(/\.label::after\s*\{[^}]*visibility:\s*hidden/);
31
+ });
32
+
17
33
  it('is not selected by default', async () => {
18
34
  const el = await fixture('<bds-tab>Label</bds-tab>') as BdsTab;
19
35
  expect(el.selected).toBe(false);
@@ -72,10 +88,10 @@ describe('bds-tab', () => {
72
88
  it('lets the public --tab_color/--tab_border-color tokens win over selected/hover-state defaults', () => {
73
89
  const cssText = BdsTab.styles.cssText;
74
90
  expect(cssText).toMatch(
75
- /\.tab\s*{[^}]*color:\s*var\(--tab_color,\s*var\(--tab_color--state,\s*var\(--bds-color_on-bg\)\)\)/,
91
+ /\.tab\s*{[^}]*color:\s*var\(--tab_color,\s*var\(--tab_color--state,\s*var\(--tab_color--inactive,\s*var\(--bds-color_on-bg--muted,\s*var\(--bds-color_on-bg\)\)\)\)\)/,
76
92
  );
77
93
  expect(cssText).toMatch(
78
- /\.tab\s*{[^}]*border-block-end:\s*2px solid var\(--tab_border-color,\s*var\(--tab_border-color--state,\s*transparent\)\)/,
94
+ /\.tab\s*{[^}]*border-block-end:\s*var\(--tab_indicator-width,\s*3px\) solid var\(--tab_border-color,\s*var\(--tab_border-color--state,\s*transparent\)\)/,
79
95
  );
80
96
  });
81
97
 
@@ -93,4 +109,20 @@ describe('bds-tab', () => {
93
109
  /--tab_color--state:\s*var\(--tab_color--hover,/,
94
110
  );
95
111
  });
112
+
113
+ it('styles the resting tab muted and medium, the selected tab strong and bold, and hover in the full text colour', () => {
114
+ const cssText = BdsTab.styles.cssText;
115
+ expect(cssText).toMatch(
116
+ /\.tab\s*{[^}]*font-weight:\s*var\(--tab_font-weight,\s*var\(--tab_font-weight--state,\s*var\(--tab_font-weight--inactive,\s*var\(--bds-font_weight--medium\)\)\)\)/,
117
+ );
118
+ expect(cssText).toMatch(
119
+ /:host\(\[selected\]\)\s*\.tab\s*{[^}]*--tab_color--state:\s*var\(--tab_color--active,\s*var\(--bds-color_on-bg\)\)/,
120
+ );
121
+ expect(cssText).toMatch(
122
+ /:host\(\[selected\]\)\s*\.tab\s*{[^}]*--tab_font-weight--state:\s*var\(--tab_font-weight--active,\s*var\(--bds-font_weight--bold\)\)/,
123
+ );
124
+ expect(cssText).toMatch(
125
+ /--tab_color--state:\s*var\(--tab_color--hover,\s*var\(--bds-color_on-bg\)\)/,
126
+ );
127
+ });
96
128
  });
@@ -38,14 +38,14 @@ export class BdsTab extends LitElement {
38
38
  padding: var(--bds-space_s) var(--bds-space_m);
39
39
  font-family: var(--bds-font_family--body);
40
40
  font-size: var(--bds-font_size--body);
41
- font-weight: var(--bds-font_weight--semibold);
41
+ font-weight: var(--tab_font-weight, var(--tab_font-weight--state, var(--tab_font-weight--inactive, var(--bds-font_weight--medium))));
42
42
 
43
43
  /* Layered cascade: public overrides (--tab_color, --tab_border-color) win over
44
44
  per-state defaults. Active/hover write to the inner --tab_*--state slot —
45
45
  mirrors the React Tabs.module.css pattern so a consumer's --tab_color
46
46
  override is never shadowed by the selected/hover rules below. */
47
- color: var(--tab_color, var(--tab_color--state, var(--bds-color_on-bg)));
48
- border-block-end: 2px solid var(--tab_border-color, var(--tab_border-color--state, transparent));
47
+ color: var(--tab_color, var(--tab_color--state, var(--tab_color--inactive, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)))));
48
+ border-block-end: var(--tab_indicator-width, 3px) solid var(--tab_border-color, var(--tab_border-color--state, transparent));
49
49
  margin-block-end: -1px;
50
50
  cursor: pointer;
51
51
  transition: var(--bds-animation_transition);
@@ -53,10 +53,27 @@ export class BdsTab extends LitElement {
53
53
  }
54
54
 
55
55
  :host([selected]) .tab {
56
- --tab_color--state: var(--tab_color--active, var(--bds-color_interactive_on-bg));
56
+ --tab_color--state: var(--tab_color--active, var(--bds-color_on-bg));
57
+ --tab_font-weight--state: var(--tab_font-weight--active, var(--bds-font_weight--bold));
57
58
  --tab_border-color--state: var(--tab_border-color--active, var(--bds-color_interactive));
58
59
  }
59
60
 
61
+ /* Reserves the width of the bold label so selecting a tab does not shift the row.
62
+ The hidden ::after repeats the label at the active weight in the same grid cell. */
63
+ .label {
64
+ display: inline-grid;
65
+ }
66
+
67
+ .label::after {
68
+ content: attr(data-label);
69
+ grid-area: 1 / 1;
70
+ font-weight: var(--tab_font-weight, var(--tab_font-weight--active, var(--bds-font_weight--bold)));
71
+ visibility: hidden;
72
+ pointer-events: none;
73
+ block-size: 0;
74
+ overflow: hidden;
75
+ }
76
+
60
77
  .tab:disabled {
61
78
  opacity: 0.4;
62
79
  cursor: not-allowed;
@@ -70,7 +87,7 @@ export class BdsTab extends LitElement {
70
87
 
71
88
  @media (hover: hover) and (pointer: fine) {
72
89
  .tab:not(:disabled, [aria-selected='true']):hover {
73
- --tab_color--state: var(--tab_color--hover, var(--bds-color_interactive_on-bg));
90
+ --tab_color--state: var(--tab_color--hover, var(--bds-color_on-bg));
74
91
  }
75
92
  }
76
93
  `;
@@ -78,15 +95,18 @@ export class BdsTab extends LitElement {
78
95
  static properties = {
79
96
  selected: { type: Boolean, reflect: true },
80
97
  disabled: { type: Boolean, reflect: true },
98
+ _label: { state: true },
81
99
  };
82
100
 
83
101
  declare selected: boolean;
84
102
  declare disabled: boolean;
103
+ declare private _label: string;
85
104
 
86
105
  constructor() {
87
106
  super();
88
107
  this.selected = false;
89
108
  this.disabled = false;
109
+ this._label = '';
90
110
  }
91
111
 
92
112
  /** Called by `bds-tabs` to focus the inner button for keyboard navigation. */
@@ -94,6 +114,11 @@ export class BdsTab extends LitElement {
94
114
  this.shadowRoot?.querySelector<HTMLButtonElement>('button')?.focus();
95
115
  }
96
116
 
117
+ private _handleSlotChange(e: Event) {
118
+ const slot = e.target as HTMLSlotElement;
119
+ this._label = slot.assignedNodes({ flatten: true }).map((n) => n.textContent ?? '').join('').trim();
120
+ }
121
+
97
122
  private _handleClick() {
98
123
  if (this.disabled) return;
99
124
  this.dispatchEvent(new CustomEvent('bds-tab-select', { bubbles: true, composed: true }));
@@ -110,7 +135,7 @@ export class BdsTab extends LitElement {
110
135
  ?disabled=${this.disabled}
111
136
  @click=${this._handleClick}
112
137
  >
113
- <slot></slot>
138
+ <span class="label" data-label=${this._label}><slot @slotchange=${this._handleSlotChange}></slot></span>
114
139
  </button>
115
140
  `;
116
141
  }
@@ -117,4 +117,20 @@ describe('bds-tooltip', () => {
117
117
  expect(cssText).toMatch(/z-index:\s*var\(--bds-z-index_popover\);/);
118
118
  expect(cssText).not.toContain('200');
119
119
  });
120
+
121
+ it('sizes the tooltip to its text up to a max width and lets it wrap', () => {
122
+ const cssText = BdsTooltip.styles.cssText;
123
+ expect(cssText).toMatch(/inline-size:\s*max-content;/);
124
+ expect(cssText).toMatch(/max-inline-size:\s*var\(--tooltip_max-inline-size,\s*20rem\);/);
125
+ expect(cssText).toMatch(/white-space:\s*normal;/);
126
+ expect(cssText).not.toContain('nowrap;\n pointer-events');
127
+ });
128
+
129
+ it('exposes the radius, padding, font-size and offset hooks with their defaults', () => {
130
+ const cssText = BdsTooltip.styles.cssText;
131
+ expect(cssText).toContain('var(--tooltip_radius, var(--bds-border_radius--xs))');
132
+ expect(cssText).toContain('var(--tooltip_padding, var(--bds-space_xxs) var(--bds-space_s))');
133
+ expect(cssText).toContain('var(--tooltip_font-size, var(--bds-font_size--body--s))');
134
+ expect(cssText).toContain('var(--tooltip_offset, var(--bds-space_xs))');
135
+ });
120
136
  });