@boostdev/design-system-components 2.8.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/AGENTS.md +54 -46
  2. package/README.md +34 -20
  3. package/dist/client.cjs +1317 -779
  4. package/dist/client.css +1029 -812
  5. package/dist/client.d.cts +88 -26
  6. package/dist/client.d.ts +88 -26
  7. package/dist/client.js +1278 -732
  8. package/dist/index.cjs +1317 -779
  9. package/dist/index.css +1029 -812
  10. package/dist/index.d.cts +88 -26
  11. package/dist/index.d.ts +88 -26
  12. package/dist/index.js +1278 -732
  13. package/dist/web-components/chunk-2OLVQUBU.js +193 -0
  14. package/dist/web-components/{chunk-OF6FFGOT.js → chunk-3AY2YKCM.js} +77 -21
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-UUKSDCYS.js → chunk-47AT5KZD.js} +87 -15
  17. package/dist/web-components/chunk-4A2YYTYN.js +93 -0
  18. package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
  19. package/dist/web-components/chunk-66M6ETWD.js +77 -0
  20. package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
  21. package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
  22. package/dist/web-components/{chunk-QLT6MTZF.js → chunk-AIBOHNDU.js} +10 -0
  23. package/dist/web-components/{chunk-X3NQMBAQ.js → chunk-AP4RDUL2.js} +14 -3
  24. package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
  25. package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
  26. package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
  27. package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
  28. package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
  29. package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
  30. package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
  31. package/dist/web-components/chunk-FEDABOR5.js +248 -0
  32. package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
  33. package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
  34. package/dist/web-components/chunk-ITD2MV5K.js +392 -0
  35. package/dist/web-components/{chunk-ARMKC4BQ.js → chunk-JH6LQKLK.js} +77 -5
  36. package/dist/web-components/chunk-JMCXAAFK.js +120 -0
  37. package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
  38. package/dist/web-components/chunk-JV5QT2OU.js +146 -0
  39. package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
  40. package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
  41. package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
  42. package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
  43. package/dist/web-components/chunk-M3ECULRH.js +174 -0
  44. package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
  45. package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
  46. package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
  47. package/dist/web-components/chunk-OY74D4X6.js +302 -0
  48. package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
  49. package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
  50. package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
  51. package/dist/web-components/chunk-QZAIRJ75.js +111 -0
  52. package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
  53. package/dist/web-components/chunk-RVF7W653.js +101 -0
  54. package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
  55. package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
  56. package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
  57. package/dist/web-components/chunk-TMWXRHGF.js +192 -0
  58. package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
  59. package/dist/web-components/chunk-UO7ROSHD.js +424 -0
  60. package/dist/web-components/chunk-VANAKB6D.js +152 -0
  61. package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
  62. package/dist/web-components/chunk-W6H5ID2U.js +161 -0
  63. package/dist/web-components/{chunk-FSZRP43R.js → chunk-WFVKU4GX.js} +21 -10
  64. package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
  65. package/dist/web-components/chunk-XI7KRAH7.js +54 -0
  66. package/dist/web-components/{chunk-ORC56MDY.js → chunk-XIZWAWMJ.js} +45 -6
  67. package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
  68. package/dist/web-components/globals.js +1 -1
  69. package/dist/web-components/index.d.ts +77 -707
  70. package/dist/web-components/index.js +339 -2066
  71. package/dist/web-components/interaction/bds-accordion-item.js +1 -1
  72. package/dist/web-components/interaction/bds-accordion.js +1 -1
  73. package/dist/web-components/interaction/bds-button.d.ts +10 -2
  74. package/dist/web-components/interaction/bds-button.js +1 -1
  75. package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
  76. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  77. package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
  78. package/dist/web-components/interaction/bds-dialog.js +1 -1
  79. package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
  80. package/dist/web-components/interaction/bds-drawer.js +1 -1
  81. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
  82. package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
  83. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
  84. package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
  85. package/dist/web-components/interaction/bds-popover.d.ts +14 -3
  86. package/dist/web-components/interaction/bds-popover.js +1 -1
  87. package/dist/web-components/interaction/bds-skip-link.js +1 -1
  88. package/dist/web-components/interaction/bds-tab-panel.js +1 -1
  89. package/dist/web-components/interaction/bds-tab.js +1 -1
  90. package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
  91. package/dist/web-components/interaction/bds-tabs.js +1 -1
  92. package/dist/web-components/interaction/bds-toast-provider.js +1 -1
  93. package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
  94. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  95. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
  96. package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
  97. package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
  98. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  99. package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
  100. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  101. package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
  102. package/dist/web-components/interaction/form/bds-field-group.js +7 -0
  103. package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
  104. package/dist/web-components/interaction/form/bds-file-input.js +1 -1
  105. package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
  106. package/dist/web-components/interaction/form/bds-form-input.js +7 -0
  107. package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
  108. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  109. package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
  110. package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
  111. package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
  112. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  113. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
  114. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  115. package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
  116. package/dist/web-components/interaction/form/bds-select.js +1 -1
  117. package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
  118. package/dist/web-components/interaction/form/bds-slider.js +1 -1
  119. package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
  120. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  121. package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
  122. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  123. package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
  124. package/dist/web-components/layout/bds-grid-item.js +7 -0
  125. package/dist/web-components/layout/bds-grid.d.ts +108 -0
  126. package/dist/web-components/layout/bds-grid.js +7 -0
  127. package/dist/web-components/ui/bds-alert.d.ts +16 -2
  128. package/dist/web-components/ui/bds-alert.js +1 -1
  129. package/dist/web-components/ui/bds-avatar.d.ts +2 -1
  130. package/dist/web-components/ui/bds-avatar.js +1 -1
  131. package/dist/web-components/ui/bds-badge.js +1 -1
  132. package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
  133. package/dist/web-components/ui/bds-breadcrumb.js +1 -1
  134. package/dist/web-components/ui/bds-button-container.d.ts +52 -0
  135. package/dist/web-components/ui/bds-button-container.js +7 -0
  136. package/dist/web-components/ui/bds-button-group.d.ts +21 -0
  137. package/dist/web-components/ui/bds-button-group.js +8 -0
  138. package/dist/web-components/ui/bds-calendar.d.ts +121 -0
  139. package/dist/web-components/ui/bds-calendar.js +7 -0
  140. package/dist/web-components/ui/bds-card.d.ts +12 -3
  141. package/dist/web-components/ui/bds-card.js +1 -1
  142. package/dist/web-components/ui/bds-carousel.d.ts +98 -0
  143. package/dist/web-components/ui/bds-carousel.js +7 -0
  144. package/dist/web-components/ui/bds-description-list.js +1 -1
  145. package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
  146. package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
  147. package/dist/web-components/ui/bds-link.js +1 -1
  148. package/dist/web-components/ui/bds-loading.js +1 -1
  149. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  150. package/dist/web-components/ui/bds-pagination.d.ts +47 -0
  151. package/dist/web-components/ui/bds-pagination.js +7 -0
  152. package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
  153. package/dist/web-components/ui/bds-progress-circle.js +1 -1
  154. package/dist/web-components/ui/bds-progress.d.ts +3 -1
  155. package/dist/web-components/ui/bds-progress.js +1 -1
  156. package/dist/web-components/ui/bds-rating.js +1 -1
  157. package/dist/web-components/ui/bds-section-header.d.ts +18 -4
  158. package/dist/web-components/ui/bds-section-header.js +1 -1
  159. package/dist/web-components/ui/bds-separator.js +1 -1
  160. package/dist/web-components/ui/bds-skeleton.js +1 -1
  161. package/dist/web-components/ui/bds-table.d.ts +52 -0
  162. package/dist/web-components/ui/bds-table.js +7 -0
  163. package/dist/web-components/ui/bds-typography.d.ts +1 -0
  164. package/dist/web-components/ui/bds-typography.js +1 -1
  165. package/package.json +60 -3
  166. package/src/components/interaction/Button/Button.module.css +23 -14
  167. package/src/components/interaction/Button/Button.spec.tsx +20 -0
  168. package/src/components/interaction/Button/Button.tsx +2 -2
  169. package/src/components/interaction/Command/Command.mdx +5 -2
  170. package/src/components/interaction/Command/Command.module.css +23 -7
  171. package/src/components/interaction/Command/Command.spec.tsx +35 -0
  172. package/src/components/interaction/Command/Command.tsx +15 -4
  173. package/src/components/interaction/Command/index.ts +1 -1
  174. package/src/components/interaction/Dialog/Dialog.mdx +4 -4
  175. package/src/components/interaction/Dialog/Dialog.module.css +20 -0
  176. package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
  177. package/src/components/interaction/Dialog/Dialog.tsx +8 -3
  178. package/src/components/interaction/Dialog/index.ts +1 -0
  179. package/src/components/interaction/Drawer/Drawer.mdx +5 -2
  180. package/src/components/interaction/Drawer/Drawer.module.css +34 -3
  181. package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
  182. package/src/components/interaction/Drawer/Drawer.tsx +30 -12
  183. package/src/components/interaction/Drawer/index.ts +1 -0
  184. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
  185. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
  186. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
  187. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  188. package/src/components/interaction/Popover/Popover.mdx +6 -2
  189. package/src/components/interaction/Popover/Popover.module.css +26 -18
  190. package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
  191. package/src/components/interaction/Popover/Popover.tsx +19 -5
  192. package/src/components/interaction/Popover/index.ts +1 -0
  193. package/src/components/interaction/Rating/Rating.mdx +1 -1
  194. package/src/components/interaction/Rating/Rating.module.css +2 -2
  195. package/src/components/interaction/Rating/Rating.tsx +1 -1
  196. package/src/components/interaction/Rating/index.ts +1 -0
  197. package/src/components/interaction/Toast/Toast.mdx +3 -0
  198. package/src/components/interaction/Toast/Toast.module.css +35 -5
  199. package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
  200. package/src/components/interaction/Toast/Toast.tsx +15 -9
  201. package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
  202. package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
  203. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
  204. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  205. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
  206. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
  207. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
  208. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
  209. package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
  210. package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
  211. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
  212. package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
  213. package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
  214. package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
  215. package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
  216. package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
  217. package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
  218. package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
  219. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
  220. package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
  221. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
  222. package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
  223. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
  224. package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
  225. package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
  226. package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
  227. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
  228. package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
  229. package/src/components/interaction/form/Radio/Radio.mdx +8 -1
  230. package/src/components/interaction/form/Radio/Radio.module.css +20 -7
  231. package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
  232. package/src/components/interaction/form/Radio/Radio.tsx +10 -4
  233. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
  234. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
  235. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
  236. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
  237. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
  238. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
  239. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
  240. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
  241. package/src/components/interaction/form/Select/Select.mdx +10 -4
  242. package/src/components/interaction/form/Select/Select.module.css +4 -4
  243. package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
  244. package/src/components/interaction/form/Select/Select.tsx +21 -9
  245. package/src/components/interaction/form/Slider/Slider.mdx +4 -1
  246. package/src/components/interaction/form/Slider/Slider.module.css +32 -8
  247. package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
  248. package/src/components/interaction/form/Slider/Slider.tsx +9 -7
  249. package/src/components/interaction/form/Switch/Switch.mdx +2 -2
  250. package/src/components/interaction/form/Switch/Switch.module.css +12 -13
  251. package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
  252. package/src/components/interaction/form/Switch/Switch.tsx +2 -1
  253. package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
  254. package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
  255. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
  256. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
  257. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  258. package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
  259. package/src/components/interaction/form/atoms/Message.tsx +1 -1
  260. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
  261. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
  262. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
  263. package/src/components/layout/Card/Card.mdx +6 -1
  264. package/src/components/layout/Card/Card.module.css +26 -5
  265. package/src/components/layout/Card/Card.spec.tsx +7 -0
  266. package/src/components/layout/Card/Card.tsx +4 -3
  267. package/src/components/layout/Grid/Grid.spec.tsx +44 -0
  268. package/src/components/layout/Grid/Grid.tsx +5 -5
  269. package/src/components/layout/Grid/GridItem.tsx +6 -6
  270. package/src/components/layout/Grid/masonry.ts +31 -5
  271. package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
  272. package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
  273. package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
  274. package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
  275. package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
  276. package/src/components/ui/Accordion/Accordion.mdx +4 -1
  277. package/src/components/ui/Accordion/Accordion.module.css +2 -1
  278. package/src/components/ui/Alert/Alert.mdx +21 -4
  279. package/src/components/ui/Alert/Alert.module.css +9 -4
  280. package/src/components/ui/Alert/Alert.spec.tsx +20 -0
  281. package/src/components/ui/Avatar/Avatar.mdx +8 -3
  282. package/src/components/ui/Avatar/Avatar.module.css +13 -8
  283. package/src/components/ui/Badge/Badge.mdx +3 -1
  284. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
  285. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
  286. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
  287. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
  288. package/src/components/ui/Calendar/Calendar.mdx +6 -0
  289. package/src/components/ui/Calendar/Calendar.module.css +68 -19
  290. package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
  291. package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
  292. package/src/components/ui/Calendar/Calendar.tsx +143 -154
  293. package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
  294. package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
  295. package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
  296. package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
  297. package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
  298. package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
  299. package/src/components/ui/Calendar/calendar-core.ts +164 -0
  300. package/src/components/ui/Calendar/index.ts +1 -0
  301. package/src/components/ui/Carousel/Carousel.mdx +8 -3
  302. package/src/components/ui/Carousel/Carousel.module.css +7 -5
  303. package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
  304. package/src/components/ui/Carousel/Carousel.tsx +19 -7
  305. package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
  306. package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
  307. package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
  308. package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
  309. package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
  310. package/src/components/ui/Link/Link.mdx +4 -2
  311. package/src/components/ui/Loading/Loading.mdx +6 -3
  312. package/src/components/ui/Loading/Loading.module.css +7 -7
  313. package/src/components/ui/Loading/Loading.spec.tsx +12 -0
  314. package/src/components/ui/Loading/Loading.tsx +8 -3
  315. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
  316. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
  317. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
  318. package/src/components/ui/Pagination/Pagination.mdx +29 -7
  319. package/src/components/ui/Pagination/Pagination.module.css +15 -14
  320. package/src/components/ui/Progress/Progress.mdx +6 -2
  321. package/src/components/ui/Progress/Progress.module.css +1 -1
  322. package/src/components/ui/Progress/Progress.spec.tsx +17 -0
  323. package/src/components/ui/Progress/Progress.tsx +2 -2
  324. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
  325. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
  326. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
  327. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
  328. package/src/components/ui/Separator/Separator.mdx +7 -0
  329. package/src/components/ui/Separator/Separator.module.css +1 -0
  330. package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
  331. package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
  332. package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
  333. package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
  334. package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
  335. package/src/components/ui/Table/Table.mdx +14 -0
  336. package/src/components/ui/Table/Table.module.css +13 -8
  337. package/src/components/ui/Table/Table.spec.tsx +56 -0
  338. package/src/components/ui/Table/Table.tsx +44 -6
  339. package/src/components/ui/Tabs/Tabs.mdx +16 -0
  340. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  341. package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
  342. package/src/components/ui/Tabs/Tabs.tsx +5 -1
  343. package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
  344. package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
  345. package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
  346. package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
  347. package/src/components/ui/Typography/Typography.mdx +7 -0
  348. package/src/components/ui/Typography/Typography.module.css +5 -5
  349. package/src/index.ts +7 -3
  350. package/src/stories/DesignSystem/Borders.mdx +14 -9
  351. package/src/stories/DesignSystem/Colors.mdx +43 -41
  352. package/src/stories/DesignSystem/Overview.mdx +4 -3
  353. package/src/stories/Introduction.mdx +5 -5
  354. package/src/web-components/globals.ts +7 -0
  355. package/src/web-components/index.ts +2 -0
  356. package/src/web-components/interaction/BdsAccordion.mdx +10 -1
  357. package/src/web-components/interaction/BdsButton.mdx +15 -4
  358. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  359. package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
  360. package/src/web-components/interaction/BdsDialog.mdx +12 -7
  361. package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
  362. package/src/web-components/interaction/BdsDrawer.mdx +9 -6
  363. package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
  364. package/src/web-components/interaction/BdsPopover.mdx +7 -3
  365. package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
  366. package/src/web-components/interaction/BdsTabs.mdx +18 -1
  367. package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
  368. package/src/web-components/interaction/BdsTooltip.mdx +4 -4
  369. package/src/web-components/interaction/bds-accordion-item.ts +10 -0
  370. package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
  371. package/src/web-components/interaction/bds-accordion.ts +28 -2
  372. package/src/web-components/interaction/bds-button.spec.ts +81 -0
  373. package/src/web-components/interaction/bds-button.ts +55 -8
  374. package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
  375. package/src/web-components/interaction/bds-collapsible.ts +49 -7
  376. package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
  377. package/src/web-components/interaction/bds-dialog.ts +68 -11
  378. package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
  379. package/src/web-components/interaction/bds-drawer.ts +54 -21
  380. package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
  381. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
  382. package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
  383. package/src/web-components/interaction/bds-popover.spec.ts +80 -6
  384. package/src/web-components/interaction/bds-popover.ts +83 -32
  385. package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
  386. package/src/web-components/interaction/bds-skip-link.ts +29 -8
  387. package/src/web-components/interaction/bds-tab-panel.ts +10 -0
  388. package/src/web-components/interaction/bds-tab.spec.ts +25 -0
  389. package/src/web-components/interaction/bds-tab.ts +20 -5
  390. package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
  391. package/src/web-components/interaction/bds-tabs.ts +42 -1
  392. package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
  393. package/src/web-components/interaction/bds-toast-provider.ts +50 -21
  394. package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
  395. package/src/web-components/interaction/bds-tooltip.ts +100 -5
  396. package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
  397. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
  398. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
  399. package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
  400. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
  401. package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
  402. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  403. package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
  404. package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
  405. package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
  406. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
  407. package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
  408. package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
  409. package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
  410. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
  411. package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
  412. package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
  413. package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
  414. package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
  415. package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
  416. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
  417. package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
  418. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
  419. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
  420. package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
  421. package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
  422. package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
  423. package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
  424. package/src/web-components/interaction/form/bds-combobox.ts +101 -16
  425. package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
  426. package/src/web-components/interaction/form/bds-field-group.ts +167 -23
  427. package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
  428. package/src/web-components/interaction/form/bds-file-input.ts +71 -34
  429. package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
  430. package/src/web-components/interaction/form/bds-form-input.ts +27 -16
  431. package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
  432. package/src/web-components/interaction/form/bds-number-input.ts +118 -29
  433. package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
  434. package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
  435. package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
  436. package/src/web-components/interaction/form/bds-radio.ts +135 -22
  437. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
  438. package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
  439. package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
  440. package/src/web-components/interaction/form/bds-select.ts +95 -7
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +154 -26
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +87 -20
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +108 -7
  447. package/src/web-components/layout/BdsGrid.mdx +6 -2
  448. package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
  449. package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
  450. package/src/web-components/layout/bds-grid-item.ts +17 -3
  451. package/src/web-components/layout/bds-grid.spec.ts +98 -0
  452. package/src/web-components/layout/bds-grid.ts +139 -19
  453. package/src/web-components/ui/BdsAlert.mdx +6 -5
  454. package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
  455. package/src/web-components/ui/BdsAvatar.mdx +4 -2
  456. package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
  457. package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
  458. package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
  459. package/src/web-components/ui/BdsCalendar.mdx +29 -4
  460. package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
  461. package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
  462. package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
  463. package/src/web-components/ui/BdsCard.mdx +7 -3
  464. package/src/web-components/ui/BdsCard.stories.tsx +17 -1
  465. package/src/web-components/ui/BdsCarousel.mdx +40 -12
  466. package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
  467. package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
  468. package/src/web-components/ui/BdsLink.mdx +14 -2
  469. package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
  470. package/src/web-components/ui/BdsPagination.mdx +40 -1
  471. package/src/web-components/ui/BdsProgress.mdx +8 -2
  472. package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
  473. package/src/web-components/ui/BdsRating.mdx +7 -0
  474. package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
  475. package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
  476. package/src/web-components/ui/BdsTable.mdx +23 -2
  477. package/src/web-components/ui/BdsTable.stories.tsx +8 -2
  478. package/src/web-components/ui/BdsTypography.mdx +17 -1
  479. package/src/web-components/ui/bds-alert.spec.ts +17 -2
  480. package/src/web-components/ui/bds-alert.ts +31 -5
  481. package/src/web-components/ui/bds-avatar.spec.ts +29 -0
  482. package/src/web-components/ui/bds-avatar.ts +29 -11
  483. package/src/web-components/ui/bds-badge.spec.ts +5 -0
  484. package/src/web-components/ui/bds-badge.ts +11 -1
  485. package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
  486. package/src/web-components/ui/bds-breadcrumb.ts +26 -4
  487. package/src/web-components/ui/bds-button-container.spec.ts +39 -0
  488. package/src/web-components/ui/bds-button-container.ts +123 -0
  489. package/src/web-components/ui/bds-button-group.spec.ts +14 -20
  490. package/src/web-components/ui/bds-button-group.ts +13 -64
  491. package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
  492. package/src/web-components/ui/bds-calendar-range.ts +307 -0
  493. package/src/web-components/ui/bds-calendar.spec.ts +176 -0
  494. package/src/web-components/ui/bds-calendar.ts +186 -344
  495. package/src/web-components/ui/bds-card.spec.ts +32 -0
  496. package/src/web-components/ui/bds-card.ts +34 -9
  497. package/src/web-components/ui/bds-carousel.spec.ts +147 -0
  498. package/src/web-components/ui/bds-carousel.ts +109 -32
  499. package/src/web-components/ui/bds-description-list.spec.ts +15 -0
  500. package/src/web-components/ui/bds-description-list.ts +37 -3
  501. package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
  502. package/src/web-components/ui/bds-icon-wrapper.ts +15 -14
  503. package/src/web-components/ui/bds-link.spec.ts +17 -0
  504. package/src/web-components/ui/bds-link.ts +26 -11
  505. package/src/web-components/ui/bds-loading.ts +10 -0
  506. package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
  507. package/src/web-components/ui/bds-notification-banner.ts +18 -1
  508. package/src/web-components/ui/bds-pagination.spec.ts +44 -0
  509. package/src/web-components/ui/bds-pagination.ts +39 -19
  510. package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
  511. package/src/web-components/ui/bds-progress-circle.ts +17 -5
  512. package/src/web-components/ui/bds-progress.spec.ts +21 -0
  513. package/src/web-components/ui/bds-progress.ts +17 -4
  514. package/src/web-components/ui/bds-rating.spec.ts +6 -0
  515. package/src/web-components/ui/bds-rating.ts +12 -2
  516. package/src/web-components/ui/bds-section-header.spec.ts +39 -8
  517. package/src/web-components/ui/bds-section-header.ts +47 -13
  518. package/src/web-components/ui/bds-separator.ts +10 -0
  519. package/src/web-components/ui/bds-skeleton.ts +10 -0
  520. package/src/web-components/ui/bds-table.spec.ts +63 -0
  521. package/src/web-components/ui/bds-table.ts +88 -28
  522. package/src/web-components/ui/bds-typography.spec.ts +47 -0
  523. package/src/web-components/ui/bds-typography.ts +28 -10
  524. package/src/web-components/ui/calendar-styles.ts +94 -0
  525. package/dist/web-components/chunk-7YURXHTS.js +0 -147
  526. package/dist/web-components/chunk-A3RLT3AS.js +0 -49
  527. package/dist/web-components/chunk-AGKJTMJI.js +0 -56
  528. package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
  529. package/dist/web-components/chunk-DZOEPVFT.js +0 -107
  530. package/dist/web-components/chunk-JYYRJS2D.js +0 -118
  531. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  532. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  533. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  534. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -4,11 +4,12 @@ import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-comman
4
4
  // Install once at module load — no-op if native support is present.
5
5
  installInvokerCommandsPolyfill();
6
6
 
7
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
7
8
  import css from './Drawer.module.css';
8
9
  import { cn } from '@boostdev/design-system-foundation';
9
10
  import type { WithClassName } from '../../../types';
10
11
 
11
- interface DrawerProps extends WithClassName, Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
12
+ export interface DrawerProps extends WithClassName, Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
12
13
  isOpen: boolean;
13
14
  onOpen?: () => void;
14
15
  onClose: () => void;
@@ -29,6 +30,8 @@ export function Drawer({
29
30
  }: Readonly<DrawerProps>) {
30
31
  const generatedId = useId();
31
32
  const id = idProp ?? generatedId;
33
+ const titleId = useId();
34
+ const hasCustomLabel = 'aria-label' in rest || 'aria-labelledby' in rest;
32
35
 
33
36
  const dialogRef = useRef<HTMLDialogElement>(null);
34
37
  const triggerRef = useRef<Element | null>(null);
@@ -70,15 +73,12 @@ export function Drawer({
70
73
  return () => dialog.removeEventListener('command', handleCommand);
71
74
  }, [onOpen, onClose]);
72
75
 
73
- // Close on backdrop click
74
- const handleClick = (e: React.MouseEvent<HTMLDialogElement>) => {
75
- const dialog = dialogRef.current;
76
- if (!dialog) return;
77
- const rect = dialog.getBoundingClientRect();
78
- const outside =
79
- e.clientX < rect.left || e.clientX > rect.right ||
80
- e.clientY < rect.top || e.clientY > rect.bottom;
81
- if (outside) onClose();
76
+ // Close on backdrop click. The dialog element's own box is only the visible
77
+ // panel clicking the ::backdrop dispatches with e.target === the dialog
78
+ // itself, so this correctly ignores clicks (including keyboard-activated
79
+ // synthetic clicks at 0,0) on controls inside the panel.
80
+ const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
81
+ if (e.target === dialogRef.current) onClose();
82
82
  };
83
83
 
84
84
  // Close on Escape (dialog handles it natively, but we sync state)
@@ -87,6 +87,22 @@ export function Drawer({
87
87
  onClose();
88
88
  };
89
89
 
90
+ // Trap Tab focus inside the drawer while open, matching Dialog's behaviour.
91
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDialogElement>) => {
92
+ if (e.key !== 'Tab') return;
93
+ const dialog = dialogRef.current;
94
+ if (!dialog) return;
95
+ const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
96
+ if (focusable.length === 0) return;
97
+ const first = focusable[0];
98
+ const last = focusable[focusable.length - 1];
99
+ if (e.shiftKey) {
100
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
101
+ } else {
102
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
103
+ }
104
+ };
105
+
90
106
  return (
91
107
  <dialog
92
108
  {...rest}
@@ -94,11 +110,13 @@ export function Drawer({
94
110
  id={id}
95
111
  className={cn(css.drawer, css[`--side_${side}`], className)}
96
112
  aria-modal="true"
97
- onClick={handleClick}
113
+ aria-labelledby={hasCustomLabel ? rest['aria-labelledby'] : (title ? titleId : undefined)}
114
+ onClick={handleBackdropClick}
98
115
  onCancel={handleCancel}
116
+ onKeyDown={handleKeyDown}
99
117
  >
100
118
  <div className={css.header}>
101
- {!!title && title}
119
+ {!!title && <div id={titleId}>{title}</div>}
102
120
  <button
103
121
  type="button"
104
122
  className={css.closeButton}
@@ -1 +1,2 @@
1
1
  export { Drawer } from './Drawer';
2
+ export type { DrawerProps } from './Drawer';
@@ -57,3 +57,5 @@ Floating menu with a list of selectable actions, triggered by a button. Supports
57
57
  - Implements ARIA menu pattern: `role="menu"`, `role="menuitem"`
58
58
  - Arrow keys navigate items; `Enter`/`Space` activate; `Escape` closes
59
59
  - Disabled items have `aria-disabled="true"` and are skipped during keyboard navigation
60
+ - Opening the menu focuses the first enabled item (disabled items are skipped as the initial target)
61
+ - Closing the menu via `Escape` or item activation returns focus to the trigger; closing via `Tab` lets focus move naturally to the next element
@@ -71,4 +71,38 @@ describe('DropdownMenu', () => {
71
71
  await user.keyboard('{ArrowDown}');
72
72
  expect(screen.getByRole('menuitem', { name: 'Duplicate' })).toHaveFocus();
73
73
  });
74
+
75
+ it('restores focus to the trigger when Escape is pressed', async () => {
76
+ const user = userEvent.setup();
77
+ render(<DropdownMenu trigger={<button type="button">Actions</button>} items={items} />);
78
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
79
+ await user.keyboard('{Escape}');
80
+ expect(screen.getByRole('button', { name: 'Actions' })).toHaveFocus();
81
+ });
82
+
83
+ it('restores focus to the trigger when a menu item is activated', async () => {
84
+ const user = userEvent.setup();
85
+ render(
86
+ <DropdownMenu
87
+ trigger={<button type="button">Actions</button>}
88
+ items={[{ id: 'edit', label: 'Edit', onClick: vi.fn() }]}
89
+ />
90
+ );
91
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
92
+ await user.click(screen.getByRole('menuitem', { name: 'Edit' }));
93
+ expect(screen.getByRole('button', { name: 'Actions' })).toHaveFocus();
94
+ });
95
+
96
+ it('focuses the first enabled item when the first item is disabled', async () => {
97
+ const user = userEvent.setup();
98
+ const itemsWithDisabledFirst = [
99
+ { id: 'delete', label: 'Delete', onClick: vi.fn(), disabled: true },
100
+ { id: 'edit', label: 'Edit', onClick: vi.fn() },
101
+ ];
102
+ render(
103
+ <DropdownMenu trigger={<button type="button">Actions</button>} items={itemsWithDisabledFirst} />
104
+ );
105
+ await user.click(screen.getByRole('button', { name: 'Actions' }));
106
+ expect(screen.getByRole('menuitem', { name: 'Edit' })).toHaveFocus();
107
+ });
74
108
  });
@@ -22,7 +22,7 @@ export interface DropdownMenuItem {
22
22
  separator?: boolean;
23
23
  }
24
24
 
25
- interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
25
+ export interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
26
26
  trigger: ReactElement;
27
27
  items: DropdownMenuItem[];
28
28
  placement?: 'bottom-start' | 'bottom-end';
@@ -39,15 +39,25 @@ export function DropdownMenu({
39
39
  const containerRef = useRef<HTMLDivElement>(null);
40
40
  const menuId = useId();
41
41
  const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
42
+ const triggerRef = useRef<Element | null>(null);
42
43
  const open = () => {
44
+ triggerRef.current = document.activeElement;
43
45
  setIsOpen(true);
44
46
  };
45
47
 
46
- const close = () => setIsOpen(false);
48
+ const close = (restoreFocus = false) => {
49
+ setIsOpen(false);
50
+ if (restoreFocus) {
51
+ (triggerRef.current as HTMLElement | null)?.focus();
52
+ }
53
+ };
47
54
 
48
55
  useEffect(() => {
49
56
  if (isOpen) {
50
- itemRefs.current[0]?.focus();
57
+ const firstEnabledIndex = items.findIndex(item => !item.disabled);
58
+ if (firstEnabledIndex !== -1) {
59
+ itemRefs.current[firstEnabledIndex]?.focus();
60
+ }
51
61
  }
52
62
  }, [isOpen]);
53
63
 
@@ -58,7 +68,7 @@ export function DropdownMenu({
58
68
  if (!containerRef.current?.contains(e.target as Node)) close();
59
69
  };
60
70
  const handleKeyDown = (e: globalThis.KeyboardEvent) => {
61
- if (e.key === 'Escape') close();
71
+ if (e.key === 'Escape') close(true);
62
72
  };
63
73
 
64
74
  document.addEventListener('pointerdown', handlePointerDown);
@@ -124,7 +134,7 @@ export function DropdownMenu({
124
134
  role="menuitem"
125
135
  disabled={item.disabled}
126
136
  className={css.item}
127
- onClick={() => { item.onClick?.(); close(); }}
137
+ onClick={() => { item.onClick?.(); close(true); }}
128
138
  onKeyDown={e => handleItemKeyDown(e, index)}
129
139
  >
130
140
  {item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
@@ -1,2 +1,3 @@
1
1
  export { DropdownMenu } from './DropdownMenu';
2
2
  export type { DropdownMenuItem } from './DropdownMenu';
3
+ export type { DropdownMenuProps } from './DropdownMenu';
@@ -40,12 +40,16 @@ Floating panel anchored to a trigger element. Unlike `Tooltip`, popovers can con
40
40
  <tr><td>`--popover_shadow`</td><td>`var(--bds-shadow_m)`</td><td>Panel box shadow</td></tr>
41
41
  <tr><td>`--popover_border`</td><td>`none`</td><td>Panel border</td></tr>
42
42
  <tr><td>`--popover_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Panel corner radius</td></tr>
43
+ <tr><td>`--popover_z-index`</td><td>`var(--bds-z-index_dropdown)`</td><td>Panel stacking order</td></tr>
44
+ <tr><td>`--popover_max-width`</td><td>`20rem`</td><td>Panel maximum inline size</td></tr>
45
+ <tr><td>`--popover_max-height`</td><td>`80dvh`</td><td>Panel maximum block size</td></tr>
43
46
  </tbody>
44
47
  </table>
45
48
 
46
49
 
47
50
  ## Accessibility
48
51
 
49
- - Trigger element gets `aria-haspopup="true"` and `aria-expanded` toggled on open/close
50
- - Popover panel is linked via `aria-controls`
52
+ - Trigger element gets `aria-expanded` toggled on open/close (no `aria-haspopup` the panel is a disclosure region, not a menu)
53
+ - Popover panel is linked to the trigger via `aria-controls`
51
54
  - Focus moves into the panel on open; `Escape` closes and returns focus to trigger
55
+ - The trigger must render a native `<button>` for the built-in open/close behaviour (invoker commands are spec-limited to `<button>`). Non-`<button>` triggers still work — an `onClick` fallback calls `togglePopover()` directly — but should render an accessible, keyboard-operable control themselves.
@@ -16,7 +16,7 @@
16
16
  inset: unset;
17
17
  margin: 0;
18
18
  overflow-block: auto;
19
- z-index: var(--popover_z-index, 100);
19
+ z-index: var(--popover_z-index, var(--bds-z-index_dropdown));
20
20
  min-inline-size: 12rem;
21
21
  max-inline-size: var(--popover_max-width, 20rem);
22
22
  max-block-size: var(--popover_max-height, 80dvh);
@@ -36,31 +36,40 @@
36
36
  anchor() resolves relative to the element tagged with the matching anchor-name.
37
37
  @position-try fallbacks are tried in order when the primary placement overflows. */
38
38
 
39
+ /* Physical top/left/right/bottom (not logical inset-*) are intentional here:
40
+ `placement` is a physical direction chosen by the consumer ('top' |
41
+ 'bottom' | 'left' | 'right'), not a text-direction-relative one. Anchor
42
+ positioning's logical inset-inline-start/end flip meaning under
43
+ [dir='rtl'], which would silently reinterpret e.g. placement="right" as
44
+ "left" in RTL documents — changing consumer-visible behaviour, not just
45
+ CSS convention. Kept physical, consistent with the @position-try blocks
46
+ below (already an accepted logical-properties exception). */
47
+
39
48
  .panel[data-placement='bottom'] {
40
49
  top: anchor(bottom);
41
50
  left: anchor(left);
42
- margin-block-start: var(--bds-space_xs, 8px);
51
+ margin-block-start: var(--bds-space_xs);
43
52
  position-try-fallbacks: --popover-top, --popover-right, --popover-left;
44
53
  }
45
54
 
46
55
  .panel[data-placement='top'] {
47
56
  bottom: anchor(top);
48
57
  left: anchor(left);
49
- margin-block-end: var(--bds-space_xs, 8px);
58
+ margin-block-end: var(--bds-space_xs);
50
59
  position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
51
60
  }
52
61
 
53
62
  .panel[data-placement='right'] {
54
63
  top: anchor(top);
55
64
  left: anchor(right);
56
- margin-inline-start: var(--bds-space_xs, 8px);
65
+ margin-inline-start: var(--bds-space_xs);
57
66
  position-try-fallbacks: --popover-left, --popover-bottom, --popover-top;
58
67
  }
59
68
 
60
69
  .panel[data-placement='left'] {
61
70
  top: anchor(top);
62
71
  right: anchor(left);
63
- margin-inline-end: var(--bds-space_xs, 8px);
72
+ margin-inline-end: var(--bds-space_xs);
64
73
  position-try-fallbacks: --popover-right, --popover-bottom, --popover-top;
65
74
  }
66
75
 
@@ -74,7 +83,7 @@
74
83
  bottom: unset;
75
84
  left: anchor(left);
76
85
  margin: 0;
77
- margin-block-start: var(--bds-space_xs, 8px);
86
+ margin-block-start: var(--bds-space_xs);
78
87
  }
79
88
 
80
89
  @position-try --popover-top {
@@ -83,7 +92,7 @@
83
92
  bottom: anchor(top);
84
93
  left: anchor(left);
85
94
  margin: 0;
86
- margin-block-end: var(--bds-space_xs, 8px);
95
+ margin-block-end: var(--bds-space_xs);
87
96
  }
88
97
 
89
98
  @position-try --popover-right {
@@ -92,7 +101,7 @@
92
101
  bottom: unset;
93
102
  left: anchor(right);
94
103
  margin: 0;
95
- margin-inline-start: var(--bds-space_xs, 8px);
104
+ margin-inline-start: var(--bds-space_xs);
96
105
  }
97
106
 
98
107
  @position-try --popover-left {
@@ -101,7 +110,7 @@
101
110
  bottom: unset;
102
111
  left: unset;
103
112
  margin: 0;
104
- margin-inline-end: var(--bds-space_xs, 8px);
113
+ margin-inline-end: var(--bds-space_xs);
105
114
  }
106
115
 
107
116
  /* Full-width fallback blocks for narrow screens.
@@ -111,20 +120,20 @@
111
120
 
112
121
  @position-try --popover-bottom-fullwidth {
113
122
  top: anchor(bottom);
114
- right: var(--bds-space_xs, 8px);
123
+ right: var(--bds-space_xs);
115
124
  bottom: unset;
116
- left: var(--bds-space_xs, 8px);
125
+ left: var(--bds-space_xs);
117
126
  margin: 0;
118
- margin-block-start: var(--bds-space_xs, 8px);
127
+ margin-block-start: var(--bds-space_xs);
119
128
  }
120
129
 
121
130
  @position-try --popover-top-fullwidth {
122
131
  top: unset;
123
- right: var(--bds-space_xs, 8px);
132
+ right: var(--bds-space_xs);
124
133
  bottom: anchor(top);
125
- left: var(--bds-space_xs, 8px);
134
+ left: var(--bds-space_xs);
126
135
  margin: 0;
127
- margin-block-end: var(--bds-space_xs, 8px);
136
+ margin-block-end: var(--bds-space_xs);
128
137
  }
129
138
 
130
139
  /* Narrow screens (≤420px): span full viewport width and only flip vertically.
@@ -136,10 +145,9 @@
136
145
  .panel[data-placement='top'],
137
146
  .panel[data-placement='left'],
138
147
  .panel[data-placement='right'] {
139
- left: var(--bds-space_xs, 8px);
140
- right: var(--bds-space_xs, 8px);
148
+ inset-inline: var(--bds-space_xs);
141
149
  inline-size: 100%;
142
- max-inline-size: calc(100svw - 2 * var(--bds-space_xs, 8px));
150
+ max-inline-size: calc(100svw - 2 * var(--bds-space_xs));
143
151
  position-try-fallbacks: --popover-top-fullwidth, --popover-bottom-fullwidth;
144
152
  }
145
153
  }
@@ -189,4 +189,42 @@ describe('Popover', () => {
189
189
  );
190
190
  expect(getPanel()).not.toHaveAttribute('role');
191
191
  });
192
+
193
+ it('does not set aria-haspopup on the trigger', () => {
194
+ render(
195
+ <Popover content={<p>Panel content</p>}>
196
+ <button type="button">Open</button>
197
+ </Popover>
198
+ );
199
+ expect(screen.getByRole('button', { name: 'Open' })).not.toHaveAttribute('aria-haspopup');
200
+ });
201
+
202
+ it('falls back to togglePopover on click when the trigger does not render a <button>', async () => {
203
+ const user = userEvent.setup();
204
+ render(
205
+ <Popover content={<p>Panel content</p>}>
206
+ <span role="button" tabIndex={0}>
207
+ Open
208
+ </span>
209
+ </Popover>
210
+ );
211
+ const trigger = screen.getByRole('button', { name: 'Open' });
212
+ await user.click(trigger);
213
+ expect(HTMLElement.prototype.togglePopover).toHaveBeenCalled();
214
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
215
+ });
216
+
217
+ it('still calls the consumer onClick handler for a non-<button> trigger', async () => {
218
+ const user = userEvent.setup();
219
+ const onClick = vi.fn();
220
+ render(
221
+ <Popover content={<p>Panel content</p>}>
222
+ <span role="button" tabIndex={0} onClick={onClick}>
223
+ Open
224
+ </span>
225
+ </Popover>
226
+ );
227
+ await user.click(screen.getByRole('button', { name: 'Open' }));
228
+ expect(onClick).toHaveBeenCalledTimes(1);
229
+ });
192
230
  });
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  HTMLAttributes,
3
+ MouseEvent as ReactMouseEvent,
3
4
  ReactElement,
4
5
  ReactNode,
5
6
  cloneElement,
@@ -26,7 +27,7 @@ if (
26
27
  import('@oddbird/css-anchor-positioning').catch(() => {});
27
28
  }
28
29
 
29
- interface PopoverProps extends WithClassName, Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
30
+ export interface PopoverProps extends WithClassName, Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
30
31
  children: ReactElement;
31
32
  content: ReactNode;
32
33
  placement?: 'top' | 'bottom' | 'left' | 'right';
@@ -88,7 +89,6 @@ export function Popover({
88
89
  ? cloneElement(children as ReactElement<Record<string, unknown>>, {
89
90
  'aria-expanded': isOpen,
90
91
  'aria-controls': panelId,
91
- 'aria-haspopup': true,
92
92
  commandfor: panelId,
93
93
  command: 'toggle-popover',
94
94
  style: {
@@ -96,9 +96,23 @@ export function Popover({
96
96
  anchorName,
97
97
  },
98
98
  // Forward any existing onClick on the child (for custom side-effects).
99
- // Do NOT add our own toggle here commandfor/command handles it to
100
- // avoid a double-toggle when both onClick and invoker fire.
101
- onClick: (children.props as Record<string, unknown>).onClick as (() => void) | undefined,
99
+ // commandfor/command handles the toggle when the trigger renders a
100
+ // native <button> don't also toggle there (avoids a double-toggle).
101
+ // Invoker commands are spec-limited to <button>, so when the actual
102
+ // rendered element isn't one (e.g. a plain <a> or a custom non-button
103
+ // Link), fall back to an explicit togglePopover() call instead of
104
+ // silently going dead. Checked on the DOM node (event.currentTarget),
105
+ // not the React element type, so wrapper components that forward
106
+ // props onto an inner <button> (e.g. this library's Button) still
107
+ // take the native path.
108
+ onClick: (event: ReactMouseEvent<HTMLElement>) => {
109
+ ((children.props as Record<string, unknown>).onClick as
110
+ | ((e: ReactMouseEvent<HTMLElement>) => void)
111
+ | undefined)?.(event);
112
+ if (!(event.currentTarget instanceof HTMLButtonElement)) {
113
+ panelRef.current?.togglePopover();
114
+ }
115
+ },
102
116
  })
103
117
  : children;
104
118
 
@@ -1 +1,2 @@
1
1
  export { Popover } from './Popover';
2
+ export type { PopoverProps } from './Popover';
@@ -48,5 +48,5 @@ Read-only star rating display. Use to show an existing score; for user input, co
48
48
 
49
49
  ## Accessibility
50
50
 
51
- - Renders as a `<span>` with `role="img"` and `aria-label="X out of Y stars"`
51
+ - Renders as a `<div>` with `role="img"` and `aria-label="X out of Y stars"`
52
52
  - Stars are `aria-hidden` — the label carries the full meaning
@@ -6,8 +6,8 @@
6
6
  }
7
7
 
8
8
  .star {
9
- width: 1.5em;
10
- height: 1.5em;
9
+ inline-size: 1.5em;
10
+ block-size: 1.5em;
11
11
  }
12
12
 
13
13
  .--filled {
@@ -3,7 +3,7 @@ import css from './Rating.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
- interface RatingProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
6
+ export interface RatingProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
7
7
  value: number;
8
8
  max?: number;
9
9
  }
@@ -1 +1,2 @@
1
1
  export { Rating } from './Rating';
2
+ export type { RatingProps } from './Rating';
@@ -43,6 +43,7 @@ const { showToast } = useToast();
43
43
  showToast('Changes saved', 'success');
44
44
  showToast('Something went wrong', 'error');
45
45
  showToast('New message received', 'info');
46
+ showToast('Double-check this before continuing', 'warning');
46
47
  ```
47
48
 
48
49
  > **Next.js RSC note:** The root entry (`@boostdev/design-system-components`) is safe to import in Server Components for server-safe components like `Typography` or `Badge`. Toast itself is client-only and must be used inside a `'use client'` boundary.
@@ -78,6 +79,8 @@ The override surface is **layered**. Setting a public token on the toast (or any
78
79
  <tbody>
79
80
  <tr><td>`--toast_color`</td><td>per variant</td><td>Toast background. Set to theme every variant at once.</td></tr>
80
81
  <tr><td>`--toast_on-color`</td><td>per variant</td><td>Toast text colour.</td></tr>
82
+ <tr><td>`--toast_radius`</td><td>`var(--bds-border_radius--s)`</td><td>Toast corner radius.</td></tr>
83
+ <tr><td>`--toast_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Toast elevation shadow.</td></tr>
81
84
  </tbody>
82
85
  </table>
83
86
 
@@ -21,15 +21,26 @@
21
21
  over per-variant defaults. Variants write to the inner --toast_*--variant slot. */
22
22
  background-color: var(--toast_color, var(--toast_color--variant, var(--bds-color_bg)));
23
23
  color: var(--toast_on-color, var(--toast_on-color--variant, var(--bds-color_on-bg)));
24
- border-radius: var(--bds-border_radius--s);
25
- box-shadow: var(--bds-shadow_s);
24
+ border-radius: var(--toast_radius, var(--bds-border_radius--s));
25
+ box-shadow: var(--toast_shadow, var(--bds-shadow_s));
26
26
  display: flex;
27
27
  align-items: center;
28
28
  gap: var(--bds-space_m);
29
- min-inline-size: 300px;
29
+ min-inline-size: min(300px, calc(100svw - 2 * var(--bds-space_xl)));
30
30
  animation: slideIn var(--bds-animation_duration--fast) var(--bds-animation_easing);
31
31
  }
32
32
 
33
+ /*
34
+ * translateX() is a physical transform (documented exception — see AGENTS.md).
35
+ * The container is anchored on the logical inline-end edge (inset-inline-end),
36
+ * which is the physical *left* edge in RTL, so the off-screen slide-in offset
37
+ * must flip too or the toast slides in from the wrong side. --toast_slide-offset
38
+ * is an internal implementation slot, not part of the public token surface.
39
+ */
40
+ .toast:dir(rtl) {
41
+ --toast_slide-offset: -100%;
42
+ }
43
+
33
44
  .toast.--variant_success {
34
45
  --toast_color--variant: var(--toast_color--success, var(--bds-color_success));
35
46
  --toast_on-color--variant: var(--toast_on-color--success, var(--bds-color_on-success));
@@ -55,6 +66,7 @@
55
66
  }
56
67
 
57
68
  .closeButton {
69
+ position: relative;
58
70
  display: flex;
59
71
  align-items: center;
60
72
  justify-content: center;
@@ -65,7 +77,19 @@
65
77
  background: none;
66
78
  border: none;
67
79
  cursor: pointer;
68
- color: var(--toast_on-color);
80
+ color: currentcolor;
81
+ }
82
+
83
+ /* Expands the hit area to >=44px (WCAG 2.5.5) without growing the visible icon. */
84
+ .closeButton::after {
85
+ content: '';
86
+ position: absolute;
87
+ inset: -0.75rem;
88
+ }
89
+
90
+ .closeButton:focus-visible {
91
+ outline: var(--bds-outline_default);
92
+ outline-offset: var(--bds-outline_offset);
69
93
  }
70
94
 
71
95
  .closeButton svg {
@@ -76,7 +100,7 @@
76
100
 
77
101
  @keyframes slideIn {
78
102
  from {
79
- transform: translateX(100%);
103
+ transform: translateX(var(--toast_slide-offset, 100%));
80
104
  opacity: 0;
81
105
  }
82
106
 
@@ -85,3 +109,9 @@
85
109
  opacity: 1;
86
110
  }
87
111
  }
112
+
113
+ @media (prefers-reduced-motion: reduce) {
114
+ .toast {
115
+ animation: none;
116
+ }
117
+ }
@@ -2,7 +2,13 @@ import { render, screen, act, fireEvent } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { ToastProvider, useToast } from './Toast';
4
4
 
5
- const ToastTrigger = ({ message = 'Test toast', variant = 'info' as const }) => {
5
+ const ToastTrigger = ({
6
+ message = 'Test toast',
7
+ variant = 'info' as const,
8
+ }: {
9
+ message?: string;
10
+ variant?: 'success' | 'error' | 'info' | 'warning';
11
+ }) => {
6
12
  const { showToast } = useToast();
7
13
  return <button onClick={() => showToast(message, variant)}>Show Toast</button>;
8
14
  };
@@ -32,6 +38,68 @@ describe('Toast', () => {
32
38
  vi.useRealTimers();
33
39
  });
34
40
 
41
+ it('auto-dismisses each toast on its own 5s schedule even when another toast arrives mid-stream', () => {
42
+ vi.useFakeTimers();
43
+ const TwoTriggers = () => {
44
+ const { showToast } = useToast();
45
+ return (
46
+ <>
47
+ <button onClick={() => showToast('First toast', 'info')}>First</button>
48
+ <button onClick={() => showToast('Second toast', 'info')}>Second</button>
49
+ </>
50
+ );
51
+ };
52
+ render(
53
+ <ToastProvider>
54
+ <TwoTriggers />
55
+ </ToastProvider>,
56
+ );
57
+
58
+ act(() => { fireEvent.click(screen.getByText('First')); });
59
+ act(() => { vi.advanceTimersByTime(3000); });
60
+ // Second toast arrives before the first's timer fires — under the prior bug
61
+ // this reset every in-flight toast's timer instead of just starting its own.
62
+ act(() => { fireEvent.click(screen.getByText('Second')); });
63
+ act(() => { vi.advanceTimersByTime(2100); }); // t=5100 since "First" mounted
64
+ expect(screen.queryByText('First toast')).not.toBeInTheDocument();
65
+ expect(screen.getByText('Second toast')).toBeInTheDocument();
66
+ vi.useRealTimers();
67
+ });
68
+
69
+ it('does not crash when another toast is queued while the popover is already open', async () => {
70
+ // jsdom has no Popover API — mock a spec-accurate showPopover/hidePopover that
71
+ // throws InvalidStateError when called in a redundant state, matching real browsers.
72
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
73
+ if (this.hasAttribute('data-popover-open')) {
74
+ throw new DOMException('Invalid state', 'InvalidStateError');
75
+ }
76
+ this.setAttribute('data-popover-open', 'true');
77
+ });
78
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
79
+ if (!this.hasAttribute('data-popover-open')) {
80
+ throw new DOMException('Invalid state', 'InvalidStateError');
81
+ }
82
+ this.removeAttribute('data-popover-open');
83
+ });
84
+
85
+ const user = userEvent.setup();
86
+ setup({ message: 'Queued toast' });
87
+ const button = screen.getByRole('button');
88
+ await user.click(button);
89
+ await user.click(button);
90
+ await user.click(button);
91
+
92
+ expect(screen.getAllByText('Queued toast')).toHaveLength(3);
93
+ });
94
+
95
+ it('accepts the warning variant', async () => {
96
+ const user = userEvent.setup();
97
+ setup({ message: 'Careful now', variant: 'warning' });
98
+ await user.click(screen.getByRole('button'));
99
+ const toastEl = screen.getByText('Careful now').closest('[role="status"]');
100
+ expect(toastEl?.className).toMatch(/--variant_warning/);
101
+ });
102
+
35
103
  it('renders the toast container as a manual popover', () => {
36
104
  render(
37
105
  <ToastProvider>