@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
@@ -2,16 +2,19 @@ import * as lit from 'lit';
2
2
  import { LitElement } from 'lit';
3
3
 
4
4
  type NotificationBannerVariant = 'info' | 'success' | 'warning' | 'error';
5
+ type NotificationBannerAppearance = 'default' | 'gradient';
5
6
  /**
6
7
  * `<bds-notification-banner>` — framework-agnostic Notification Banner custom element.
7
8
  *
8
9
  * Attributes:
9
10
  * variant — "info" (default) | "success" | "warning" | "error"
11
+ * appearance — "default" | "gradient" (fades the tint to the surface, accent line, shadow)
10
12
  * dismissible — boolean; shows a dismiss button when present
11
13
  * dismiss-label — aria-label for the dismiss button (default: "Dismiss notification")
12
14
  *
13
15
  * Slots:
14
16
  * (default) — banner body content
17
+ * icon — optional icon displayed before the content
15
18
  * action — optional action area (e.g. a button)
16
19
  *
17
20
  * Events:
@@ -30,6 +33,10 @@ declare class BdsNotificationBanner extends LitElement {
30
33
  type: StringConstructor;
31
34
  reflect: boolean;
32
35
  };
36
+ appearance: {
37
+ type: StringConstructor;
38
+ reflect: boolean;
39
+ };
33
40
  dismissible: {
34
41
  type: BooleanConstructor;
35
42
  reflect: boolean;
@@ -41,12 +48,18 @@ declare class BdsNotificationBanner extends LitElement {
41
48
  _actionFilled: {
42
49
  state: boolean;
43
50
  };
51
+ _iconFilled: {
52
+ state: boolean;
53
+ };
44
54
  };
45
55
  variant: NotificationBannerVariant;
56
+ appearance: NotificationBannerAppearance;
46
57
  dismissible: boolean;
47
58
  dismissLabel: string;
48
59
  private _actionFilled;
60
+ private _iconFilled;
49
61
  constructor();
62
+ private _onIconSlotChange;
50
63
  private _onActionSlotChange;
51
64
  private get _isUrgent();
52
65
  private _handleDismiss;
@@ -58,4 +71,4 @@ declare global {
58
71
  }
59
72
  }
60
73
 
61
- export { BdsNotificationBanner, type NotificationBannerVariant };
74
+ export { BdsNotificationBanner, type NotificationBannerAppearance, type NotificationBannerVariant };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsNotificationBanner
3
- } from "../chunk-LDIPCTRN.js";
3
+ } from "../chunk-WWWJCSXN.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsNotificationBanner
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsRating
3
- } from "../chunk-SYNLFKD3.js";
3
+ } from "../chunk-3LQKFSMR.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsRating
@@ -13,6 +13,7 @@ import { LitElement } from 'lit';
13
13
  * subtitle — optional subheading text
14
14
  * alignment — "start" (default) | "center" | "end"
15
15
  * size — "xs" | "small" | "medium" (default) | "large"
16
+ * appearance — "default" | "gradient" (gradient-filled title)
16
17
  * title-as — tag name for the title element (default: "h2")
17
18
  *
18
19
  * @example
@@ -38,6 +39,10 @@ declare class BdsSectionHeader extends LitElement {
38
39
  type: StringConstructor;
39
40
  reflect: boolean;
40
41
  };
42
+ appearance: {
43
+ type: StringConstructor;
44
+ reflect: boolean;
45
+ };
41
46
  titleAs: {
42
47
  type: StringConstructor;
43
48
  attribute: string;
@@ -53,6 +58,7 @@ declare class BdsSectionHeader extends LitElement {
53
58
  subtitle: string;
54
59
  alignment: 'start' | 'center' | 'end';
55
60
  size: 'xs' | 'small' | 'medium' | 'large';
61
+ appearance: 'default' | 'gradient';
56
62
  titleAs: string;
57
63
  constructor();
58
64
  private get _headingText();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsSectionHeader
3
- } from "../chunk-RVF7W653.js";
3
+ } from "../chunk-DGOCPKHI.js";
4
4
  import "../chunk-QPOKHLMQ.js";
5
5
  import "../chunk-LGEZYFUU.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsSeparator
3
- } from "../chunk-5HUAFCU6.js";
3
+ } from "../chunk-TJV7UIXC.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsSeparator
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsTable
3
- } from "../chunk-HBLJ4SLY.js";
3
+ } from "../chunk-DD3OUXNG.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsTable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "2.15.0",
3
+ "version": "2.17.0",
4
4
  "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
5
  "keywords": [
6
6
  "React",
@@ -171,6 +171,10 @@
171
171
  "types": "./dist/web-components/interaction/bds-dropdown-menu-item.d.ts",
172
172
  "import": "./dist/web-components/interaction/bds-dropdown-menu-item.js"
173
173
  },
174
+ "./web-components/interaction/bds-dropdown-menu-group": {
175
+ "types": "./dist/web-components/interaction/bds-dropdown-menu-group.d.ts",
176
+ "import": "./dist/web-components/interaction/bds-dropdown-menu-group.js"
177
+ },
174
178
  "./web-components/interaction/bds-popover": {
175
179
  "types": "./dist/web-components/interaction/bds-popover.d.ts",
176
180
  "import": "./dist/web-components/interaction/bds-popover.js"
@@ -338,7 +342,7 @@
338
342
  "changelog:unreleased": "pnpm exec git-cliff --unreleased"
339
343
  },
340
344
  "devDependencies": {
341
- "@boostdev/design-system-foundation": "^2.3.0",
345
+ "@boostdev/design-system-foundation": "^2.4.0",
342
346
  "@boostdev/design-system-react-native": "0.2.0",
343
347
  "@modelcontextprotocol/sdk": "^1.27.0",
344
348
  "@storybook/addon-docs": "8.6.14",
@@ -5,7 +5,7 @@ import * as Stories from './DropdownMenu.stories';
5
5
 
6
6
  # DropdownMenu
7
7
 
8
- Floating menu with a list of selectable actions, triggered by a button. Supports disabled items and separators.
8
+ Floating menu with a list of selectable actions, triggered by a button. Supports disabled items, separators, destructive items, shortcut hints and labelled groups.
9
9
 
10
10
  ## When to use
11
11
  - Overflow actions in a toolbar or table row
@@ -26,6 +26,12 @@ Floating menu with a list of selectable actions, triggered by a button. Supports
26
26
  ### With disabled item
27
27
  <Canvas of={Stories.WithDisabledItem} />
28
28
 
29
+ ### With groups
30
+ <Canvas of={Stories.WithGroups} />
31
+
32
+ ### With destructive item
33
+ <Canvas of={Stories.WithDestructiveItem} />
34
+
29
35
  ### Aligned end
30
36
  <Canvas of={Stories.AlignedEnd} />
31
37
 
@@ -45,14 +51,37 @@ Floating menu with a list of selectable actions, triggered by a button. Supports
45
51
  <tr><td>`--dropdown_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Menu container corner radius</td></tr>
46
52
  <tr><td>`--dropdown_color`</td><td>`var(--bds-color_bg)`</td><td>Menu background colour</td></tr>
47
53
  <tr><td>`--dropdown_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Menu text colour</td></tr>
48
- <tr><td>`--dropdown_shadow`</td><td>`var(--bds-shadow_m)`</td><td>Menu box shadow</td></tr>
54
+ <tr><td>`--dropdown_shadow`</td><td>`var(--bds-shadow_l)`</td><td>Menu box shadow</td></tr>
49
55
  <tr><td>`--dropdown_separator-color`</td><td>`var(--bds-color_on-bg--as-surface)`</td><td>Separator line colour — the non-text member of the colour set (≥ 3:1)</td></tr>
50
- <tr><td>`--dropdown_item-bg--hover`</td><td>`var(--bds-color_bg--subtle)`</td><td>Menu item background on hover</td></tr>
51
- <tr><td>`--dropdown_item-color--hover`</td><td>`var(--bds-color_interactive_on-bg)`</td><td>Menu item text colour on hover</td></tr>
56
+ <tr><td>`--dropdown_item-bg--hover`</td><td>`var(--bds-color_primary--subtle)`</td><td>Menu item background on hover and focus</td></tr>
57
+ <tr><td>`--dropdown_item-color--hover`</td><td>`var(--bds-color_on-primary--subtle)`</td><td>Menu item text colour on hover and focus</td></tr>
58
+ <tr><td>`--dropdown_padding`</td><td>`var(--bds-space_xxs)`</td><td>Menu padding on all sides</td></tr>
59
+ <tr><td>`--dropdown_border-color`</td><td>`var(--bds-color_on-bg--as-surface)`</td><td>Menu outline colour</td></tr>
60
+ <tr><td>`--dropdown_item-padding`</td><td>`var(--bds-space_xs) var(--bds-space_s)`</td><td>Item padding</td></tr>
61
+ <tr><td>`--dropdown_item-radius`</td><td>`var(--bds-border_radius--xs)`</td><td>Item corner radius</td></tr>
62
+ <tr><td>`--dropdown_item-font-size`</td><td>`var(--bds-font_size--body)`</td><td>Item font size</td></tr>
63
+ <tr><td>`--dropdown_item-opacity--disabled`</td><td>`0.4`</td><td>Disabled item opacity</td></tr>
64
+ <tr><td>`--dropdown_item-color--destructive`</td><td>`var(--bds-color_error_on-bg)`</td><td>Destructive item text colour</td></tr>
65
+ <tr><td>`--dropdown_item-bg--destructive-hover`</td><td>`var(--bds-color_error--subtle)`</td><td>Destructive item background on hover and focus</td></tr>
66
+ <tr><td>`--dropdown_item-color--destructive-hover`</td><td>`var(--bds-color_on-error--subtle)`</td><td>Destructive item text colour on hover and focus</td></tr>
67
+ <tr><td>`--dropdown_group-label-font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Group label font size</td></tr>
68
+ <tr><td>`--dropdown_group-label-font-weight`</td><td>`var(--bds-font_weight--bold)`</td><td>Group label font weight</td></tr>
69
+ <tr><td>`--dropdown_group-label-color`</td><td>`var(--bds-color_on-bg--muted)`</td><td>Group label colour</td></tr>
70
+ <tr><td>`--dropdown_group-label-padding`</td><td>`var(--bds-space_xs) var(--bds-space_s) var(--bds-space_xxs)`</td><td>Group label padding</td></tr>
71
+ <tr><td>`--dropdown_shortcut-color`</td><td>`var(--bds-color_on-bg--muted)`</td><td>Shortcut hint colour</td></tr>
72
+ <tr><td>`--dropdown_shortcut-font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Shortcut hint font size</td></tr>
52
73
  </tbody>
53
74
  </table>
54
75
 
55
76
 
77
+ ## Item options
78
+
79
+ | Option | Type | Description |
80
+ |--------|------|-------------|
81
+ | `destructive` | boolean | Renders the item in the error colour |
82
+ | `shortcut` | string | Display-only shortcut hint at the inline end; rendered `aria-hidden`, the consumer wires the actual key |
83
+ | `group` | string | Consecutive items with the same `group` render inside a `role="group"` named by a visible label |
84
+
56
85
  ## Accessibility
57
86
 
58
87
  - Implements ARIA menu pattern: `role="menu"`, `role="menuitem"`
@@ -60,3 +89,5 @@ Floating menu with a list of selectable actions, triggered by a button. Supports
60
89
  - Disabled items have `aria-disabled="true"` and are skipped during keyboard navigation
61
90
  - Opening the menu focuses the first enabled item (disabled items are skipped as the initial target)
62
91
  - Closing the menu via `Escape` or item activation returns focus to the trigger; closing via `Tab` lets focus move naturally to the next element
92
+ - Grouped items sit inside `role="group"` with `aria-labelledby` pointing at the visible group label; the label is not focusable and arrow keys skip it
93
+ - Shortcut hints are `aria-hidden` so they do not become part of the item name
@@ -9,15 +9,15 @@
9
9
  inset-block-start: calc(100% + var(--bds-space_xxs));
10
10
  z-index: var(--bds-z-index_dropdown);
11
11
  min-inline-size: 10rem;
12
- padding: var(--bds-space_xxs) 0;
12
+ padding: var(--dropdown_padding, var(--bds-space_xxs));
13
13
  border: var(--dropdown_border, none);
14
- --bdc_color: currentcolor;
14
+ --bdc_color: var(--dropdown_border-color, var(--bds-color_on-bg--as-surface, var(--bds-color_on-bg--border, var(--bds-color_on-bg))));
15
15
  --bdc-outline_radius: var(--bds-border_radius--s);
16
16
 
17
17
  border-radius: var(--dropdown_radius, var(--bdc-outline_radius));
18
18
  background-color: var(--dropdown_color, var(--bds-color_bg--strong, var(--bds-color_bg)));
19
19
  color: var(--dropdown_on-color, var(--bds-color_on-bg));
20
- box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--dropdown_shadow, var(--bds-shadow_m));
20
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--dropdown_shadow, var(--bds-shadow_l));
21
21
  list-style: none;
22
22
  margin: 0;
23
23
  }
@@ -29,10 +29,29 @@
29
29
  .--placement_bottom-start { inset-inline-start: 0; }
30
30
  .--placement_bottom-end { inset-inline-end: 0; }
31
31
 
32
+ .separatorRow {
33
+ padding-block: var(--bds-space_xxs);
34
+ }
35
+
32
36
  .separator {
33
37
  border: none;
34
38
  border-block-start: 1px solid var(--dropdown_separator-color, var(--bds-color_on-bg--as-surface, var(--bds-color_on-bg--border, var(--bds-color_on-bg))));
35
- margin: var(--bds-space_xxs) 0;
39
+ margin: 0;
40
+ }
41
+
42
+ .group {
43
+ margin: 0;
44
+ padding: 0;
45
+ list-style: none;
46
+ }
47
+
48
+ .groupLabel {
49
+ display: block;
50
+ padding: var(--dropdown_group-label-padding, var(--bds-space_xs) var(--bds-space_s) var(--bds-space_xxs));
51
+ font-family: var(--bds-font_family--body);
52
+ font-size: var(--dropdown_group-label-font-size, var(--bds-font_size--body--s));
53
+ font-weight: var(--dropdown_group-label-font-weight, var(--bds-font_weight--bold));
54
+ color: var(--dropdown_group-label-color, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)));
36
55
  }
37
56
 
38
57
  .item {
@@ -41,9 +60,10 @@
41
60
  align-items: center;
42
61
  gap: var(--bds-space_xs);
43
62
  inline-size: 100%;
44
- padding: var(--bds-space_xs) var(--bds-space_m);
63
+ padding: var(--dropdown_item-padding, var(--bds-space_xs) var(--bds-space_s));
64
+ border-radius: var(--dropdown_item-radius, var(--bds-border_radius--xs));
45
65
  font-family: var(--bds-font_family--body);
46
- font-size: var(--bds-font_size--body);
66
+ font-size: var(--dropdown_item-font-size, var(--bds-font_size--body));
47
67
  color: currentcolor;
48
68
  cursor: pointer;
49
69
  box-sizing: border-box;
@@ -51,23 +71,51 @@
51
71
  }
52
72
 
53
73
  .item:disabled {
54
- opacity: 0.4;
74
+ opacity: var(--dropdown_item-opacity--disabled, 0.4);
55
75
  cursor: not-allowed;
56
76
  }
57
77
 
58
78
  .item:focus-visible {
59
79
  outline: var(--bds-outline_default);
60
80
  outline-offset: calc(var(--bds-outline_offset) * -1);
61
- border-radius: var(--bds-border_radius--xs);
81
+ }
82
+
83
+ .item:not(:disabled):focus-visible {
84
+ background-color: var(--dropdown_item-bg--hover, var(--bds-color_primary--subtle, var(--bds-color_blue--subtle)));
85
+ color: var(--dropdown_item-color--hover, var(--bds-color_on-primary--subtle, var(--bds-color_on-blue--subtle)));
86
+ }
87
+
88
+ .--destructive {
89
+ color: var(--dropdown_item-color--destructive, var(--bds-color_error_on-bg));
90
+ }
91
+
92
+ .--destructive:not(:disabled):focus-visible {
93
+ background-color: var(--dropdown_item-bg--destructive-hover, var(--bds-color_error--subtle));
94
+ color: var(--dropdown_item-color--destructive-hover, var(--bds-color_on-error--subtle));
62
95
  }
63
96
 
64
97
  @media (hover: hover) and (pointer: fine) {
65
98
  .item:not(:disabled):hover {
66
- background-color: var(--dropdown_item-bg--hover, var(--bds-color_bg--subtle));
67
- color: var(--dropdown_item-color--hover, var(--bds-color_interactive_on-bg));
99
+ background-color: var(--dropdown_item-bg--hover, var(--bds-color_primary--subtle, var(--bds-color_blue--subtle)));
100
+ color: var(--dropdown_item-color--hover, var(--bds-color_on-primary--subtle, var(--bds-color_on-blue--subtle)));
101
+ }
102
+
103
+ .--destructive:not(:disabled):hover {
104
+ background-color: var(--dropdown_item-bg--destructive-hover, var(--bds-color_error--subtle));
105
+ color: var(--dropdown_item-color--destructive-hover, var(--bds-color_on-error--subtle));
68
106
  }
69
107
  }
70
108
 
109
+ .label {
110
+ flex: 1;
111
+ }
112
+
113
+ .shortcut {
114
+ flex-shrink: 0;
115
+ color: var(--dropdown_shortcut-color, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)));
116
+ font-size: var(--dropdown_shortcut-font-size, var(--bds-font_size--body--s));
117
+ }
118
+
71
119
  .icon {
72
120
  display: flex;
73
121
  align-items: center;
@@ -1,7 +1,14 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { render, screen, within } from '@testing-library/react';
2
4
  import userEvent from '@testing-library/user-event';
3
5
  import { DropdownMenu } from './DropdownMenu';
4
6
 
7
+ const cssSource = readFileSync(
8
+ resolve(process.cwd(), 'src/components/interaction/DropdownMenu/DropdownMenu.module.css'),
9
+ 'utf8',
10
+ );
11
+
5
12
  const items = [
6
13
  { id: 'edit', label: 'Edit', onClick: vi.fn() },
7
14
  { id: 'duplicate', label: 'Duplicate', onClick: vi.fn() },
@@ -105,4 +112,110 @@ describe('DropdownMenu', () => {
105
112
  await user.click(screen.getByRole('button', { name: 'Actions' }));
106
113
  expect(screen.getByRole('menuitem', { name: 'Edit' })).toHaveFocus();
107
114
  });
115
+
116
+ describe('groups, destructive and shortcut', () => {
117
+ const grouped = [
118
+ { id: 'edit', label: 'Edit', group: 'Edit' },
119
+ { id: 'duplicate', label: 'Duplicate', shortcut: '⌘D', group: 'Edit' },
120
+ { id: 'delete', label: 'Delete', destructive: true, group: 'Danger' },
121
+ ];
122
+
123
+ it('renders a labelled group for consecutive items with the same group', async () => {
124
+ const user = userEvent.setup();
125
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={grouped} />);
126
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
127
+ const group = screen.getByRole('group', { name: 'Edit' });
128
+ expect(within(group).getAllByRole('menuitem')).toHaveLength(2);
129
+ expect(screen.getByRole('group', { name: 'Danger' })).toBeInTheDocument();
130
+ });
131
+
132
+ it('moves focus from the last item of a group to the first of the next', async () => {
133
+ const user = userEvent.setup();
134
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={grouped} />);
135
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
136
+ await user.keyboard('{ArrowDown}');
137
+ expect(screen.getByRole('menuitem', { name: 'Duplicate' })).toHaveFocus();
138
+ await user.keyboard('{ArrowDown}');
139
+ expect(screen.getByRole('menuitem', { name: 'Delete' })).toHaveFocus();
140
+ });
141
+
142
+ it('does not render a group for items without a group', async () => {
143
+ const user = userEvent.setup();
144
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={items} />);
145
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
146
+ expect(screen.queryByRole('group')).not.toBeInTheDocument();
147
+ });
148
+
149
+ it('marks a destructive item with the destructive class', async () => {
150
+ const user = userEvent.setup();
151
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={grouped} />);
152
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
153
+ expect(screen.getByRole('menuitem', { name: 'Delete' }).className).toMatch(/destructive/);
154
+ expect(screen.getByRole('menuitem', { name: 'Edit' }).className).not.toMatch(/destructive/);
155
+ });
156
+
157
+ it('renders the shortcut aria-hidden and keeps it out of the item name', async () => {
158
+ const user = userEvent.setup();
159
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={grouped} />);
160
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
161
+ expect(screen.getByText('⌘D')).toHaveAttribute('aria-hidden', 'true');
162
+ expect(screen.getByRole('menuitem', { name: 'Duplicate' })).toBeInTheDocument();
163
+ });
164
+
165
+ it('renders a separator before an item with separator set', async () => {
166
+ const user = userEvent.setup();
167
+ render(
168
+ <DropdownMenu
169
+ trigger={<button type="button">Actions</button>}
170
+ items={[{ id: 'a', label: 'A' }, { id: 'b', label: 'B', separator: true }]}
171
+ />
172
+ );
173
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
174
+ expect(screen.getByRole('separator')).toBeInTheDocument();
175
+ });
176
+
177
+ it('renders the separator of a group\'s first item outside the group', async () => {
178
+ const user = userEvent.setup();
179
+ render(
180
+ <DropdownMenu
181
+ trigger={<button type="button">Actions</button>}
182
+ items={[
183
+ { id: 'a', label: 'A', group: 'One' },
184
+ { id: 'b', label: 'B', group: 'Two', separator: true },
185
+ { id: 'c', label: 'C', group: 'Two', separator: true },
186
+ ]}
187
+ />
188
+ );
189
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
190
+ const groups = screen.getAllByRole('group');
191
+ const separators = screen.getAllByRole('separator');
192
+ expect(separators).toHaveLength(2);
193
+ expect(groups[1]).not.toContainElement(separators[0]);
194
+ expect(groups[1]).toContainElement(separators[1]);
195
+ });
196
+ });
197
+
198
+ describe('stylesheet hooks', () => {
199
+ it.each([
200
+ '--dropdown_padding',
201
+ '--dropdown_border-color',
202
+ '--dropdown_item-padding',
203
+ '--dropdown_item-radius',
204
+ '--dropdown_item-font-size',
205
+ '--dropdown_item-bg--hover',
206
+ '--dropdown_item-color--hover',
207
+ '--dropdown_item-opacity--disabled',
208
+ '--dropdown_item-color--destructive',
209
+ '--dropdown_item-bg--destructive-hover',
210
+ '--dropdown_item-color--destructive-hover',
211
+ '--dropdown_group-label-font-size',
212
+ '--dropdown_group-label-font-weight',
213
+ '--dropdown_group-label-color',
214
+ '--dropdown_group-label-padding',
215
+ '--dropdown_shortcut-color',
216
+ '--dropdown_shortcut-font-size',
217
+ ])('reads %s through var()', hook => {
218
+ expect(cssSource).toContain(`var(${hook},`);
219
+ });
220
+ });
108
221
  });
@@ -66,3 +66,28 @@ export const AlignedEnd: Story = {
66
66
  />
67
67
  ),
68
68
  };
69
+
70
+ export const WithGroups: Story = {
71
+ render: () => (
72
+ <DropdownMenu
73
+ trigger={<Button variant="outline">Acties ▾</Button>}
74
+ items={[
75
+ { id: 'edit', label: 'Bewerken', icon: <span>✎</span>, shortcut: '⌘E', group: 'Bewerken' },
76
+ { id: 'duplicate', label: 'Dupliceren', shortcut: '⌘D', group: 'Bewerken' },
77
+ { id: 'delete', label: 'Verwijderen', destructive: true, group: 'Gevaarlijk' },
78
+ ]}
79
+ />
80
+ ),
81
+ };
82
+
83
+ export const WithDestructiveItem: Story = {
84
+ render: () => (
85
+ <DropdownMenu
86
+ trigger={<Button variant="outline">Beheer ▾</Button>}
87
+ items={[
88
+ { id: 'edit', label: 'Bewerken' },
89
+ { id: 'delete', label: 'Verwijderen', destructive: true, separator: true },
90
+ ]}
91
+ />
92
+ ),
93
+ };
@@ -2,6 +2,7 @@ import {
2
2
  HTMLAttributes,
3
3
  KeyboardEvent,
4
4
  ReactElement,
5
+ Fragment,
5
6
  cloneElement,
6
7
  isValidElement,
7
8
  useEffect,
@@ -20,8 +21,29 @@ export interface DropdownMenuItem {
20
21
  disabled?: boolean;
21
22
  icon?: ReactElement;
22
23
  separator?: boolean;
24
+ /** Renders the item in the error colour, for actions that delete or discard. */
25
+ destructive?: boolean;
26
+ /** Keyboard shortcut as display text. Decorative (`aria-hidden`); the consumer wires the actual key. */
27
+ shortcut?: string;
28
+ /** Consecutive items with the same group render inside a labelled `role="group"`. */
29
+ group?: string;
23
30
  }
24
31
 
32
+ interface IndexedItem {
33
+ item: DropdownMenuItem;
34
+ index: number;
35
+ }
36
+
37
+ /** Splits items into runs of consecutive items sharing the same `group`. */
38
+ const toSegments = (items: DropdownMenuItem[]): IndexedItem[][] =>
39
+ items.reduce<IndexedItem[][]>((segments, item, index) => {
40
+ const last = segments[segments.length - 1];
41
+ const startsNewSegment = !last || item.group === undefined || last[0].item.group !== item.group;
42
+ return startsNewSegment
43
+ ? [...segments, [{ item, index }]]
44
+ : [...segments.slice(0, -1), [...last, { item, index }]];
45
+ }, []);
46
+
25
47
  export interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
26
48
  trigger: ReactElement;
27
49
  items: DropdownMenuItem[];
@@ -125,23 +147,53 @@ export function DropdownMenu({
125
147
  role="menu"
126
148
  className={cn(css.menu, css[`--placement_${placement}`])}
127
149
  >
128
- {items.map((item, index) => (
129
- <li key={item.id} role="presentation">
130
- {item.separator && <hr className={css.separator} role="separator" />}
131
- <button
132
- ref={el => { itemRefs.current[index] = el; }}
133
- type="button"
134
- role="menuitem"
135
- disabled={item.disabled}
136
- className={css.item}
137
- onClick={() => { item.onClick?.(); close(true); }}
138
- onKeyDown={e => handleItemKeyDown(e, index)}
139
- >
140
- {item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
141
- {item.label}
142
- </button>
143
- </li>
144
- ))}
150
+ {toSegments(items).map(segment => {
151
+ const { item: first } = segment[0];
152
+ const groupLabel = first.group;
153
+ const renderSeparator = () => (
154
+ <li role="presentation" className={css.separatorRow}>
155
+ <hr className={css.separator} role="separator" />
156
+ </li>
157
+ );
158
+ /* The first item's separator sits before the group, so the group holds only its label and items. */
159
+ const leadingSeparator = groupLabel !== undefined && first.separator;
160
+ const rows = segment.map(({ item, index }, position) => (
161
+ <Fragment key={item.id}>
162
+ {item.separator && !(leadingSeparator && position === 0) && renderSeparator()}
163
+ <li role="presentation">
164
+ <button
165
+ ref={el => { itemRefs.current[index] = el; }}
166
+ type="button"
167
+ role="menuitem"
168
+ disabled={item.disabled}
169
+ className={cn(css.item, item.destructive && css['--destructive'])}
170
+ onClick={() => { item.onClick?.(); close(true); }}
171
+ onKeyDown={e => handleItemKeyDown(e, index)}
172
+ >
173
+ {item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
174
+ <span className={css.label}>{item.label}</span>
175
+ {item.shortcut && <span className={css.shortcut} aria-hidden="true">{item.shortcut}</span>}
176
+ </button>
177
+ </li>
178
+ </Fragment>
179
+ ));
180
+
181
+ return groupLabel === undefined ? (
182
+ rows
183
+ ) : (
184
+ <Fragment key={`group-${first.id}`}>
185
+ {leadingSeparator && renderSeparator()}
186
+ <li role="presentation">
187
+ <ul role="group" aria-labelledby={`${menuId}-group-${first.id}`} className={css.group}>
188
+ <li role="presentation" id={`${menuId}-group-${first.id}`} className={css.groupLabel}>
189
+ {groupLabel}
190
+ </li>
191
+ {rows}
192
+ </ul>
193
+ </li>
194
+ </Fragment>
195
+ );
196
+ })}
145
197
  </ul>
146
198
  )}
147
199
  </div>
@@ -40,8 +40,11 @@ Read-only star rating display. Use to show an existing score; for user input, co
40
40
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
41
41
  </thead>
42
42
  <tbody>
43
- <tr><td>`--rating_color`</td><td>`var(--bds-color_bg)`</td><td>Empty star colour</td></tr>
43
+ <tr><td>`--rating_color`</td><td>`var(--bds-color_bg)`</td><td>Empty star fill</td></tr>
44
44
  <tr><td>`--rating_color--filled`</td><td>`var(--bds-color_warning)`</td><td>Filled star colour</td></tr>
45
+ <tr><td>`--rating_stroke-color`</td><td>`var(--bds-color_on-bg--as-surface)`</td><td>Empty star outline, a non-text mark (falls back to `--bds-color_on-bg--border`, then `--bds-color_on-bg`, on foundations before 2.3.0)</td></tr>
46
+ <tr><td>`--rating_stroke-color--filled`</td><td>`currentcolor`</td><td>Filled star outline, the filled colour by default so both kinds keep the same size</td></tr>
47
+ <tr><td>`--rating_stroke-width`</td><td>`var(--bdc-outline_width)`</td><td>Outline width, in the star's 24-unit viewBox</td></tr>
45
48
  </tbody>
46
49
  </table>
47
50
 
@@ -5,12 +5,21 @@
5
5
  color: var(--rating_color, var(--bds-color_bg));
6
6
  }
7
7
 
8
+ /* Empty stars are outlined, filled stars solid: the two differ in shape, not
9
+ only in hue, because the as-surface grey and the warning fill are close in
10
+ lightness. The outline is a non-text mark, so it takes the as-surface
11
+ member (>= 3:1 on the page). Filled stars stroke in their own colour so
12
+ both kinds keep the same size. */
8
13
  .star {
9
14
  inline-size: 1.5em;
10
15
  block-size: 1.5em;
16
+ stroke: var(--rating_stroke-color, var(--bds-color_on-bg--as-surface, var(--bds-color_on-bg--border, var(--bds-color_on-bg))));
17
+ stroke-width: var(--rating_stroke-width, var(--bdc-outline_width));
18
+ stroke-linejoin: round;
11
19
  }
12
20
 
13
21
  .--filled {
14
22
  color: var(--rating_color--filled, var(--bds-color_warning));
23
+ stroke: var(--rating_stroke-color--filled, currentcolor);
15
24
  }
16
25
  }