@boostdev/design-system-components 2.7.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/AGENTS.md +54 -46
  2. package/README.md +34 -20
  3. package/dist/client.cjs +1317 -779
  4. package/dist/client.css +1029 -812
  5. package/dist/client.d.cts +88 -26
  6. package/dist/client.d.ts +88 -26
  7. package/dist/client.js +1278 -732
  8. package/dist/index.cjs +1317 -779
  9. package/dist/index.css +1029 -812
  10. package/dist/index.d.cts +88 -26
  11. package/dist/index.d.ts +88 -26
  12. package/dist/index.js +1278 -732
  13. package/dist/web-components/chunk-2OLVQUBU.js +193 -0
  14. package/dist/web-components/chunk-3AY2YKCM.js +223 -0
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-CZBIVR4V.js → chunk-47AT5KZD.js} +88 -16
  17. package/dist/web-components/chunk-4A2YYTYN.js +93 -0
  18. package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
  19. package/dist/web-components/chunk-66M6ETWD.js +77 -0
  20. package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
  21. package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
  22. package/dist/web-components/{chunk-5R6IFELV.js → chunk-AIBOHNDU.js} +11 -1
  23. package/dist/web-components/{chunk-ITHLKR7Q.js → chunk-AP4RDUL2.js} +16 -5
  24. package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
  25. package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
  26. package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
  27. package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
  28. package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
  29. package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
  30. package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
  31. package/dist/web-components/chunk-FEDABOR5.js +248 -0
  32. package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
  33. package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
  34. package/dist/web-components/chunk-ITD2MV5K.js +392 -0
  35. package/dist/web-components/{chunk-CM3KYZ6Z.js → chunk-JH6LQKLK.js} +78 -6
  36. package/dist/web-components/chunk-JMCXAAFK.js +120 -0
  37. package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
  38. package/dist/web-components/chunk-JV5QT2OU.js +146 -0
  39. package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
  40. package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
  41. package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
  42. package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
  43. package/dist/web-components/chunk-M3ECULRH.js +174 -0
  44. package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
  45. package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
  46. package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
  47. package/dist/web-components/chunk-OY74D4X6.js +302 -0
  48. package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
  49. package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
  50. package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
  51. package/dist/web-components/chunk-QZAIRJ75.js +111 -0
  52. package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
  53. package/dist/web-components/chunk-RVF7W653.js +101 -0
  54. package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
  55. package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
  56. package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
  57. package/dist/web-components/chunk-TMWXRHGF.js +192 -0
  58. package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
  59. package/dist/web-components/chunk-UO7ROSHD.js +424 -0
  60. package/dist/web-components/chunk-VANAKB6D.js +152 -0
  61. package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
  62. package/dist/web-components/chunk-W6H5ID2U.js +161 -0
  63. package/dist/web-components/{chunk-57TRNCWQ.js → chunk-WFVKU4GX.js} +22 -11
  64. package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
  65. package/dist/web-components/chunk-XI7KRAH7.js +54 -0
  66. package/dist/web-components/{chunk-F5E3IT7G.js → chunk-XIZWAWMJ.js} +50 -11
  67. package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
  68. package/dist/web-components/globals.js +1 -1
  69. package/dist/web-components/index.d.ts +77 -707
  70. package/dist/web-components/index.js +339 -2066
  71. package/dist/web-components/interaction/bds-accordion-item.js +1 -1
  72. package/dist/web-components/interaction/bds-accordion.js +1 -1
  73. package/dist/web-components/interaction/bds-button.d.ts +10 -2
  74. package/dist/web-components/interaction/bds-button.js +1 -1
  75. package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
  76. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  77. package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
  78. package/dist/web-components/interaction/bds-dialog.js +1 -1
  79. package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
  80. package/dist/web-components/interaction/bds-drawer.js +1 -1
  81. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
  82. package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
  83. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
  84. package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
  85. package/dist/web-components/interaction/bds-popover.d.ts +14 -3
  86. package/dist/web-components/interaction/bds-popover.js +1 -1
  87. package/dist/web-components/interaction/bds-skip-link.js +1 -1
  88. package/dist/web-components/interaction/bds-tab-panel.js +1 -1
  89. package/dist/web-components/interaction/bds-tab.js +1 -1
  90. package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
  91. package/dist/web-components/interaction/bds-tabs.js +1 -1
  92. package/dist/web-components/interaction/bds-toast-provider.js +1 -1
  93. package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
  94. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  95. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
  96. package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
  97. package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
  98. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  99. package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
  100. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  101. package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
  102. package/dist/web-components/interaction/form/bds-field-group.js +7 -0
  103. package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
  104. package/dist/web-components/interaction/form/bds-file-input.js +1 -1
  105. package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
  106. package/dist/web-components/interaction/form/bds-form-input.js +7 -0
  107. package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
  108. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  109. package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
  110. package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
  111. package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
  112. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  113. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
  114. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  115. package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
  116. package/dist/web-components/interaction/form/bds-select.js +1 -1
  117. package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
  118. package/dist/web-components/interaction/form/bds-slider.js +1 -1
  119. package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
  120. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  121. package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
  122. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  123. package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
  124. package/dist/web-components/layout/bds-grid-item.js +7 -0
  125. package/dist/web-components/layout/bds-grid.d.ts +108 -0
  126. package/dist/web-components/layout/bds-grid.js +7 -0
  127. package/dist/web-components/ui/bds-alert.d.ts +16 -2
  128. package/dist/web-components/ui/bds-alert.js +1 -1
  129. package/dist/web-components/ui/bds-avatar.d.ts +2 -1
  130. package/dist/web-components/ui/bds-avatar.js +1 -1
  131. package/dist/web-components/ui/bds-badge.js +1 -1
  132. package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
  133. package/dist/web-components/ui/bds-breadcrumb.js +1 -1
  134. package/dist/web-components/ui/bds-button-container.d.ts +52 -0
  135. package/dist/web-components/ui/bds-button-container.js +7 -0
  136. package/dist/web-components/ui/bds-button-group.d.ts +21 -0
  137. package/dist/web-components/ui/bds-button-group.js +8 -0
  138. package/dist/web-components/ui/bds-calendar.d.ts +121 -0
  139. package/dist/web-components/ui/bds-calendar.js +7 -0
  140. package/dist/web-components/ui/bds-card.d.ts +12 -3
  141. package/dist/web-components/ui/bds-card.js +1 -1
  142. package/dist/web-components/ui/bds-carousel.d.ts +98 -0
  143. package/dist/web-components/ui/bds-carousel.js +7 -0
  144. package/dist/web-components/ui/bds-description-list.js +1 -1
  145. package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
  146. package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
  147. package/dist/web-components/ui/bds-link.js +1 -1
  148. package/dist/web-components/ui/bds-loading.js +1 -1
  149. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  150. package/dist/web-components/ui/bds-pagination.d.ts +47 -0
  151. package/dist/web-components/ui/bds-pagination.js +7 -0
  152. package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
  153. package/dist/web-components/ui/bds-progress-circle.js +1 -1
  154. package/dist/web-components/ui/bds-progress.d.ts +3 -1
  155. package/dist/web-components/ui/bds-progress.js +1 -1
  156. package/dist/web-components/ui/bds-rating.js +1 -1
  157. package/dist/web-components/ui/bds-section-header.d.ts +18 -4
  158. package/dist/web-components/ui/bds-section-header.js +1 -1
  159. package/dist/web-components/ui/bds-separator.js +1 -1
  160. package/dist/web-components/ui/bds-skeleton.js +1 -1
  161. package/dist/web-components/ui/bds-table.d.ts +52 -0
  162. package/dist/web-components/ui/bds-table.js +7 -0
  163. package/dist/web-components/ui/bds-typography.d.ts +1 -0
  164. package/dist/web-components/ui/bds-typography.js +1 -1
  165. package/package.json +60 -3
  166. package/src/components/interaction/Button/Button.module.css +23 -14
  167. package/src/components/interaction/Button/Button.spec.tsx +20 -0
  168. package/src/components/interaction/Button/Button.tsx +2 -2
  169. package/src/components/interaction/Command/Command.mdx +5 -2
  170. package/src/components/interaction/Command/Command.module.css +23 -7
  171. package/src/components/interaction/Command/Command.spec.tsx +35 -0
  172. package/src/components/interaction/Command/Command.tsx +15 -4
  173. package/src/components/interaction/Command/index.ts +1 -1
  174. package/src/components/interaction/Dialog/Dialog.mdx +4 -4
  175. package/src/components/interaction/Dialog/Dialog.module.css +20 -0
  176. package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
  177. package/src/components/interaction/Dialog/Dialog.tsx +8 -3
  178. package/src/components/interaction/Dialog/index.ts +1 -0
  179. package/src/components/interaction/Drawer/Drawer.mdx +5 -2
  180. package/src/components/interaction/Drawer/Drawer.module.css +34 -3
  181. package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
  182. package/src/components/interaction/Drawer/Drawer.tsx +30 -12
  183. package/src/components/interaction/Drawer/index.ts +1 -0
  184. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
  185. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
  186. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
  187. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  188. package/src/components/interaction/Popover/Popover.mdx +6 -2
  189. package/src/components/interaction/Popover/Popover.module.css +26 -18
  190. package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
  191. package/src/components/interaction/Popover/Popover.tsx +19 -5
  192. package/src/components/interaction/Popover/index.ts +1 -0
  193. package/src/components/interaction/Rating/Rating.mdx +1 -1
  194. package/src/components/interaction/Rating/Rating.module.css +2 -2
  195. package/src/components/interaction/Rating/Rating.tsx +1 -1
  196. package/src/components/interaction/Rating/index.ts +1 -0
  197. package/src/components/interaction/Toast/Toast.mdx +3 -0
  198. package/src/components/interaction/Toast/Toast.module.css +35 -5
  199. package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
  200. package/src/components/interaction/Toast/Toast.tsx +15 -9
  201. package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
  202. package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
  203. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
  204. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  205. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
  206. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
  207. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
  208. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
  209. package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
  210. package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
  211. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
  212. package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
  213. package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
  214. package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
  215. package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
  216. package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
  217. package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
  218. package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
  219. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
  220. package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
  221. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
  222. package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
  223. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
  224. package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
  225. package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
  226. package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
  227. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
  228. package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
  229. package/src/components/interaction/form/Radio/Radio.mdx +8 -1
  230. package/src/components/interaction/form/Radio/Radio.module.css +20 -7
  231. package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
  232. package/src/components/interaction/form/Radio/Radio.tsx +10 -4
  233. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
  234. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
  235. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
  236. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
  237. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
  238. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
  239. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
  240. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
  241. package/src/components/interaction/form/Select/Select.mdx +10 -4
  242. package/src/components/interaction/form/Select/Select.module.css +4 -4
  243. package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
  244. package/src/components/interaction/form/Select/Select.tsx +21 -9
  245. package/src/components/interaction/form/Slider/Slider.mdx +4 -1
  246. package/src/components/interaction/form/Slider/Slider.module.css +32 -8
  247. package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
  248. package/src/components/interaction/form/Slider/Slider.tsx +9 -7
  249. package/src/components/interaction/form/Switch/Switch.mdx +2 -2
  250. package/src/components/interaction/form/Switch/Switch.module.css +12 -13
  251. package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
  252. package/src/components/interaction/form/Switch/Switch.tsx +2 -1
  253. package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
  254. package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
  255. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
  256. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
  257. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  258. package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
  259. package/src/components/interaction/form/atoms/Message.tsx +1 -1
  260. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
  261. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
  262. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
  263. package/src/components/layout/Card/Card.mdx +6 -1
  264. package/src/components/layout/Card/Card.module.css +26 -5
  265. package/src/components/layout/Card/Card.spec.tsx +7 -0
  266. package/src/components/layout/Card/Card.tsx +4 -3
  267. package/src/components/layout/Grid/Grid.spec.tsx +44 -0
  268. package/src/components/layout/Grid/Grid.tsx +5 -5
  269. package/src/components/layout/Grid/GridItem.tsx +6 -6
  270. package/src/components/layout/Grid/masonry.ts +31 -5
  271. package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
  272. package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
  273. package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
  274. package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
  275. package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
  276. package/src/components/ui/Accordion/Accordion.mdx +4 -1
  277. package/src/components/ui/Accordion/Accordion.module.css +2 -1
  278. package/src/components/ui/Alert/Alert.mdx +21 -4
  279. package/src/components/ui/Alert/Alert.module.css +9 -4
  280. package/src/components/ui/Alert/Alert.spec.tsx +20 -0
  281. package/src/components/ui/Avatar/Avatar.mdx +8 -3
  282. package/src/components/ui/Avatar/Avatar.module.css +13 -8
  283. package/src/components/ui/Badge/Badge.mdx +3 -1
  284. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
  285. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
  286. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
  287. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
  288. package/src/components/ui/Calendar/Calendar.mdx +6 -0
  289. package/src/components/ui/Calendar/Calendar.module.css +68 -19
  290. package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
  291. package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
  292. package/src/components/ui/Calendar/Calendar.tsx +143 -154
  293. package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
  294. package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
  295. package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
  296. package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
  297. package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
  298. package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
  299. package/src/components/ui/Calendar/calendar-core.ts +164 -0
  300. package/src/components/ui/Calendar/index.ts +1 -0
  301. package/src/components/ui/Carousel/Carousel.mdx +8 -3
  302. package/src/components/ui/Carousel/Carousel.module.css +7 -5
  303. package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
  304. package/src/components/ui/Carousel/Carousel.tsx +19 -7
  305. package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
  306. package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
  307. package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
  308. package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
  309. package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
  310. package/src/components/ui/Link/Link.mdx +4 -2
  311. package/src/components/ui/Loading/Loading.mdx +6 -3
  312. package/src/components/ui/Loading/Loading.module.css +7 -7
  313. package/src/components/ui/Loading/Loading.spec.tsx +12 -0
  314. package/src/components/ui/Loading/Loading.tsx +8 -3
  315. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
  316. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
  317. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
  318. package/src/components/ui/Pagination/Pagination.mdx +29 -7
  319. package/src/components/ui/Pagination/Pagination.module.css +15 -14
  320. package/src/components/ui/Progress/Progress.mdx +6 -2
  321. package/src/components/ui/Progress/Progress.module.css +1 -1
  322. package/src/components/ui/Progress/Progress.spec.tsx +17 -0
  323. package/src/components/ui/Progress/Progress.tsx +2 -2
  324. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
  325. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
  326. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
  327. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
  328. package/src/components/ui/Separator/Separator.mdx +7 -0
  329. package/src/components/ui/Separator/Separator.module.css +1 -0
  330. package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
  331. package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
  332. package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
  333. package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
  334. package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
  335. package/src/components/ui/Table/Table.mdx +14 -0
  336. package/src/components/ui/Table/Table.module.css +13 -8
  337. package/src/components/ui/Table/Table.spec.tsx +56 -0
  338. package/src/components/ui/Table/Table.tsx +44 -6
  339. package/src/components/ui/Tabs/Tabs.mdx +16 -0
  340. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  341. package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
  342. package/src/components/ui/Tabs/Tabs.tsx +5 -1
  343. package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
  344. package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
  345. package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
  346. package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
  347. package/src/components/ui/Typography/Typography.mdx +7 -0
  348. package/src/components/ui/Typography/Typography.module.css +5 -5
  349. package/src/index.ts +7 -3
  350. package/src/stories/DesignSystem/Borders.mdx +14 -9
  351. package/src/stories/DesignSystem/Colors.mdx +43 -41
  352. package/src/stories/DesignSystem/Overview.mdx +4 -3
  353. package/src/stories/Introduction.mdx +5 -5
  354. package/src/web-components/globals.ts +7 -0
  355. package/src/web-components/index.ts +2 -0
  356. package/src/web-components/interaction/BdsAccordion.mdx +10 -1
  357. package/src/web-components/interaction/BdsButton.mdx +15 -4
  358. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  359. package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
  360. package/src/web-components/interaction/BdsDialog.mdx +12 -7
  361. package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
  362. package/src/web-components/interaction/BdsDrawer.mdx +9 -6
  363. package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
  364. package/src/web-components/interaction/BdsPopover.mdx +7 -3
  365. package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
  366. package/src/web-components/interaction/BdsTabs.mdx +18 -1
  367. package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
  368. package/src/web-components/interaction/BdsTooltip.mdx +4 -4
  369. package/src/web-components/interaction/bds-accordion-item.ts +10 -0
  370. package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
  371. package/src/web-components/interaction/bds-accordion.ts +28 -2
  372. package/src/web-components/interaction/bds-button.spec.ts +81 -0
  373. package/src/web-components/interaction/bds-button.ts +60 -13
  374. package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
  375. package/src/web-components/interaction/bds-collapsible.ts +49 -7
  376. package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
  377. package/src/web-components/interaction/bds-dialog.ts +68 -11
  378. package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
  379. package/src/web-components/interaction/bds-drawer.ts +54 -21
  380. package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
  381. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
  382. package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
  383. package/src/web-components/interaction/bds-popover.spec.ts +80 -6
  384. package/src/web-components/interaction/bds-popover.ts +83 -32
  385. package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
  386. package/src/web-components/interaction/bds-skip-link.ts +29 -8
  387. package/src/web-components/interaction/bds-tab-panel.ts +10 -0
  388. package/src/web-components/interaction/bds-tab.spec.ts +25 -0
  389. package/src/web-components/interaction/bds-tab.ts +20 -5
  390. package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
  391. package/src/web-components/interaction/bds-tabs.ts +42 -1
  392. package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
  393. package/src/web-components/interaction/bds-toast-provider.ts +50 -21
  394. package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
  395. package/src/web-components/interaction/bds-tooltip.ts +100 -5
  396. package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
  397. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
  398. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
  399. package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
  400. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
  401. package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
  402. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  403. package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
  404. package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
  405. package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
  406. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
  407. package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
  408. package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
  409. package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
  410. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
  411. package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
  412. package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
  413. package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
  414. package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
  415. package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
  416. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
  417. package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
  418. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
  419. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
  420. package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
  421. package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
  422. package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
  423. package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
  424. package/src/web-components/interaction/form/bds-combobox.ts +102 -17
  425. package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
  426. package/src/web-components/interaction/form/bds-field-group.ts +167 -23
  427. package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
  428. package/src/web-components/interaction/form/bds-file-input.ts +71 -34
  429. package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
  430. package/src/web-components/interaction/form/bds-form-input.ts +27 -16
  431. package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
  432. package/src/web-components/interaction/form/bds-number-input.ts +118 -29
  433. package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
  434. package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
  435. package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
  436. package/src/web-components/interaction/form/bds-radio.ts +136 -23
  437. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
  438. package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
  439. package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
  440. package/src/web-components/interaction/form/bds-select.ts +96 -8
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +181 -33
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +142 -41
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +109 -8
  447. package/src/web-components/layout/BdsGrid.mdx +6 -2
  448. package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
  449. package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
  450. package/src/web-components/layout/bds-grid-item.ts +17 -3
  451. package/src/web-components/layout/bds-grid.spec.ts +98 -0
  452. package/src/web-components/layout/bds-grid.ts +139 -19
  453. package/src/web-components/ui/BdsAlert.mdx +6 -5
  454. package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
  455. package/src/web-components/ui/BdsAvatar.mdx +4 -2
  456. package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
  457. package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
  458. package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
  459. package/src/web-components/ui/BdsCalendar.mdx +29 -4
  460. package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
  461. package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
  462. package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
  463. package/src/web-components/ui/BdsCard.mdx +7 -3
  464. package/src/web-components/ui/BdsCard.stories.tsx +17 -1
  465. package/src/web-components/ui/BdsCarousel.mdx +40 -12
  466. package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
  467. package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
  468. package/src/web-components/ui/BdsLink.mdx +14 -2
  469. package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
  470. package/src/web-components/ui/BdsPagination.mdx +40 -1
  471. package/src/web-components/ui/BdsProgress.mdx +8 -2
  472. package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
  473. package/src/web-components/ui/BdsRating.mdx +7 -0
  474. package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
  475. package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
  476. package/src/web-components/ui/BdsTable.mdx +23 -2
  477. package/src/web-components/ui/BdsTable.stories.tsx +8 -2
  478. package/src/web-components/ui/BdsTypography.mdx +17 -1
  479. package/src/web-components/ui/bds-alert.spec.ts +17 -2
  480. package/src/web-components/ui/bds-alert.ts +31 -5
  481. package/src/web-components/ui/bds-avatar.spec.ts +29 -0
  482. package/src/web-components/ui/bds-avatar.ts +30 -12
  483. package/src/web-components/ui/bds-badge.spec.ts +5 -0
  484. package/src/web-components/ui/bds-badge.ts +11 -1
  485. package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
  486. package/src/web-components/ui/bds-breadcrumb.ts +26 -4
  487. package/src/web-components/ui/bds-button-container.spec.ts +39 -0
  488. package/src/web-components/ui/bds-button-container.ts +123 -0
  489. package/src/web-components/ui/bds-button-group.spec.ts +14 -20
  490. package/src/web-components/ui/bds-button-group.ts +13 -64
  491. package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
  492. package/src/web-components/ui/bds-calendar-range.ts +307 -0
  493. package/src/web-components/ui/bds-calendar.spec.ts +176 -0
  494. package/src/web-components/ui/bds-calendar.ts +186 -344
  495. package/src/web-components/ui/bds-card.spec.ts +32 -0
  496. package/src/web-components/ui/bds-card.ts +34 -9
  497. package/src/web-components/ui/bds-carousel.spec.ts +147 -0
  498. package/src/web-components/ui/bds-carousel.ts +113 -36
  499. package/src/web-components/ui/bds-description-list.spec.ts +15 -0
  500. package/src/web-components/ui/bds-description-list.ts +37 -3
  501. package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
  502. package/src/web-components/ui/bds-icon-wrapper.ts +16 -15
  503. package/src/web-components/ui/bds-link.spec.ts +17 -0
  504. package/src/web-components/ui/bds-link.ts +26 -11
  505. package/src/web-components/ui/bds-loading.ts +11 -1
  506. package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
  507. package/src/web-components/ui/bds-notification-banner.ts +18 -1
  508. package/src/web-components/ui/bds-pagination.spec.ts +44 -0
  509. package/src/web-components/ui/bds-pagination.ts +39 -19
  510. package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
  511. package/src/web-components/ui/bds-progress-circle.ts +17 -5
  512. package/src/web-components/ui/bds-progress.spec.ts +21 -0
  513. package/src/web-components/ui/bds-progress.ts +19 -6
  514. package/src/web-components/ui/bds-rating.spec.ts +6 -0
  515. package/src/web-components/ui/bds-rating.ts +12 -2
  516. package/src/web-components/ui/bds-section-header.spec.ts +39 -8
  517. package/src/web-components/ui/bds-section-header.ts +47 -13
  518. package/src/web-components/ui/bds-separator.ts +10 -0
  519. package/src/web-components/ui/bds-skeleton.ts +10 -0
  520. package/src/web-components/ui/bds-table.spec.ts +63 -0
  521. package/src/web-components/ui/bds-table.ts +88 -28
  522. package/src/web-components/ui/bds-typography.spec.ts +47 -0
  523. package/src/web-components/ui/bds-typography.ts +28 -10
  524. package/src/web-components/ui/calendar-styles.ts +94 -0
  525. package/dist/web-components/chunk-5TFWOPLH.js +0 -49
  526. package/dist/web-components/chunk-7FMR53BW.js +0 -127
  527. package/dist/web-components/chunk-AGKJTMJI.js +0 -56
  528. package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
  529. package/dist/web-components/chunk-IYGF6CD5.js +0 -118
  530. package/dist/web-components/chunk-QBB6ZX4R.js +0 -107
  531. package/dist/web-components/chunk-Y3POAKGS.js +0 -133
  532. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  533. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  534. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  535. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -25,8 +25,27 @@ The link is visually hidden until focused. Press **Tab** on the preview below to
25
25
 
26
26
  ## Props
27
27
 
28
+ > **HTML attributes** — all standard HTML attributes for the root element are forwarded via `{...rest}` (`id`, `className`, `style`, `data-*`, `aria-*`, event handlers, etc.). Component-controlled attributes take precedence.
29
+
28
30
  <ArgTypes of={Stories} />
29
31
 
32
+ ## CSS variables
33
+
34
+ <table>
35
+ <thead>
36
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
37
+ </thead>
38
+ <tbody>
39
+ <tr><td>`--skip-link_z-index`</td><td>`var(--bds-z-index_xl)`</td><td>Stacking order. Defaults to the "critical overlay" tier so the focused link paints above sticky headers/navigation.</td></tr>
40
+ <tr><td>`--skip-link_inset`</td><td>`var(--bds-space_s)`</td><td>Distance from the block-start and inline-start edges of the viewport when focused.</td></tr>
41
+ <tr><td>`--skip-link_padding`</td><td>`var(--bds-space_xs) var(--bds-space_m)`</td><td>Inner padding of the link.</td></tr>
42
+ <tr><td>`--skip-link_font-size`</td><td>`var(--bds-font_size--body)`</td><td>Link text size.</td></tr>
43
+ <tr><td>`--skip-link_radius`</td><td>`var(--bds-border_radius--s)`</td><td>Corner radius.</td></tr>
44
+ <tr><td>`--skip-link_border`</td><td>`none`</td><td>Border shorthand, in addition to the focus-colour inset frame.</td></tr>
45
+ <tr><td>`--skip-link_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Extra shadow layered after the inset frame border (bdc formula's shadow slot).</td></tr>
46
+ </tbody>
47
+ </table>
48
+
30
49
  ## Accessibility
31
50
 
32
51
  - Renders as a standard `<a>` — no special ARIA needed
@@ -1,21 +1,22 @@
1
1
  @layer boostdev.component {
2
2
  .skipLink {
3
3
  position: absolute;
4
- inset-block-start: var(--bds-space_s);
5
- inset-inline-start: var(--bds-space_s);
6
- z-index: var(--z_overlay);
7
- padding: var(--bds-space_xs) var(--bds-space_m);
4
+ inset-block-start: var(--skip-link_inset, var(--bds-space_s));
5
+ inset-inline-start: var(--skip-link_inset, var(--bds-space_s));
6
+ z-index: var(--skip-link_z-index, var(--bds-z-index_xl));
7
+ padding: var(--skip-link_padding, var(--bds-space_xs) var(--bds-space_m));
8
8
  background-color: var(--bds-color_bg);
9
9
  color: var(--bds-color_on-bg);
10
- font-size: var(--bds-font_size--body);
10
+ font-size: var(--skip-link_font-size, var(--bds-font_size--body));
11
11
  font-weight: var(--bds-font_weight--semibold);
12
- border-radius: var(--bds-border_radius--s);
12
+ border-radius: var(--skip-link_radius, var(--bds-border_radius--s));
13
13
  text-decoration: none;
14
14
 
15
15
  --bdc_color: var(--color_focus, var(--bds-color_interactive));
16
16
 
17
17
  border: var(--skip-link_border, none);
18
- box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
18
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color),
19
+ var(--skip-link_shadow, var(--bdc-outline_shadow));
19
20
  transform: translateY(-200%);
20
21
  transition: transform var(--bds-animation_transition-duration) var(--bds-animation_easing);
21
22
  }
@@ -1,6 +1,13 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { render, screen } from '@testing-library/react';
2
4
  import { SkipLink } from './SkipLink';
3
5
 
6
+ const cssSource = readFileSync(
7
+ resolve(process.cwd(), 'src/components/ui/SkipLink/SkipLink.module.css'),
8
+ 'utf8',
9
+ );
10
+
4
11
  describe('SkipLink', () => {
5
12
  it('renders a link with default text', () => {
6
13
  render(<SkipLink />);
@@ -21,4 +28,25 @@ describe('SkipLink', () => {
21
28
  render(<SkipLink href="#content" />);
22
29
  expect(screen.getByRole('link')).toHaveAttribute('href', '#content');
23
30
  });
31
+
32
+ it('resolves z-index from a real token with a fallback so the focused link rises above sticky headers', () => {
33
+ // jsdom has no layout engine, so the winning stacking context can't be
34
+ // asserted via computed style; pin the contract in the CSS source
35
+ // instead (same approach as NotificationBanner.spec.tsx). Previously
36
+ // this referenced the undefined --z_overlay token, which resolves to
37
+ // `auto` and defeats the skip link's entire purpose.
38
+ const ruleMatch = cssSource.match(/\.skipLink\s*{[^}]*}/);
39
+ expect(ruleMatch).not.toBeNull();
40
+ const rule = ruleMatch?.[0] ?? '';
41
+
42
+ expect(rule).not.toMatch(/--z_overlay/);
43
+ expect(rule).toMatch(/z-index:\s*var\(--skip-link_z-index,\s*var\(--bds-z-index_xl\)\)/);
44
+ });
45
+
46
+ it('layers a shadow slot after the bdc inset frame border', () => {
47
+ const ruleMatch = cssSource.match(/\.skipLink\s*{[^}]*}/);
48
+ const rule = ruleMatch?.[0] ?? '';
49
+
50
+ expect(rule).toMatch(/var\(--skip-link_shadow,\s*var\(--bdc-outline_shadow\)\)/);
51
+ });
24
52
  });
@@ -28,6 +28,8 @@ Data table with optional sortable columns and caption.
28
28
 
29
29
  ## Props
30
30
 
31
+ > **HTML attributes** — all standard HTML attributes for the root element are forwarded via `{...rest}` (`id`, `className`, `style`, `data-*`, `aria-*`, event handlers, etc.). Component-controlled attributes take precedence.
32
+
31
33
  <ArgTypes of={Stories} />
32
34
 
33
35
  ## CSS variables
@@ -40,15 +42,27 @@ Data table with optional sortable columns and caption.
40
42
  <tbody>
41
43
  <tr><td>`--table_border`</td><td>`none`</td><td>Container border</td></tr>
42
44
  <tr><td>`--table_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Container corner radius</td></tr>
45
+ <tr><td>`--table_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Container frame shadow (appended to the inset border box-shadow)</td></tr>
46
+ <tr><td>`--table_cell-padding`</td><td>`var(--bds-space_s) var(--bds-space_m)`</td><td>Padding shared by the caption, header cells and body cells</td></tr>
47
+ <tr><td>`--table_font-size`</td><td>`var(--bds-font_size--body)`</td><td>Body cell font size</td></tr>
48
+ <tr><td>`--table_header-font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Header cell / sort button font size</td></tr>
43
49
  <tr><td>`--table_sortBtn-color--hover`</td><td>`var(--bds-color_on-bg)`</td><td>Sort button text colour on hover</td></tr>
44
50
  <tr><td>`--table_row-bg--hover`</td><td>`var(--bds-color_bg--subtle)`</td><td>Table row background on hover</td></tr>
45
51
  </tbody>
46
52
  </table>
47
53
 
48
54
 
55
+ ## Notes
56
+
57
+ - `rowKey?: (row: Row, index: number) => string` — derives a stable React key per row. Defaults to the row index when omitted; use this when your row objects may be structurally identical or non-serializable.
58
+ - `TableColumn.sortLabel?: string` — accessible label used for a sortable column's sort button. Provide this when `header` is not a plain string (e.g. an icon or custom `ReactNode`) so the sort button gets a meaningful accessible name instead of falling back to its visible content.
59
+ - `Table` extends `HTMLAttributes<HTMLDivElement>` and spreads unrecognised props onto the scrollable wrapper `<div>` (e.g. `data-testid`, `aria-label`).
60
+
49
61
  ## Accessibility
50
62
 
51
63
  - Wraps in `<table>` with `<thead>` and `<tbody>`
52
64
  - Column headers use `<th scope="col">`
53
65
  - Sortable columns set `aria-sort="ascending"` / `"descending"` / `"none"`
54
66
  - `caption` renders as `<caption>` — the accessible name of the table
67
+ - The horizontally scrollable wrapper is keyboard focusable (`tabIndex={0}`, overridable via a `tabIndex` prop) and, whenever it has an accessible name — from `caption`, or a consumer-supplied `aria-label`/`aria-labelledby` — exposes `role="region"` labelled accordingly, so keyboard users can scroll it and screen reader users can identify it
68
+ - Sort button accessible names use `col.sortLabel` when provided, otherwise `col.header` when it is a plain string; for non-string headers the button falls back to its visible text content instead of stringifying the `ReactNode`
@@ -7,20 +7,25 @@
7
7
  --bdc-outline_radius: var(--bds-border_radius--s);
8
8
 
9
9
  border-radius: var(--table_radius, var(--bdc-outline_radius));
10
- box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
10
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--table_shadow, var(--bdc-outline_shadow));
11
+ }
12
+
13
+ .wrapper:focus-visible {
14
+ outline: var(--bds-outline_default);
15
+ outline-offset: calc(var(--bds-outline_offset) * -1);
11
16
  }
12
17
 
13
18
  .table {
14
19
  inline-size: 100%;
15
20
  border-collapse: collapse;
16
- font-size: var(--bds-font_size--body);
21
+ font-size: var(--table_font-size, var(--bds-font_size--body));
17
22
  font-family: var(--bds-font_family--body);
18
23
  }
19
24
 
20
25
  .caption {
21
26
  caption-side: top;
22
27
  text-align: start;
23
- padding: var(--bds-space_s) var(--bds-space_m);
28
+ padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
24
29
  font-weight: var(--bds-font_weight--semibold);
25
30
  color: var(--bds-color_on-bg);
26
31
  }
@@ -30,10 +35,10 @@
30
35
  }
31
36
 
32
37
  .th {
33
- padding: var(--bds-space_s) var(--bds-space_m);
38
+ padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
34
39
  text-align: start;
35
40
  font-weight: var(--bds-font_weight--semibold);
36
- font-size: var(--bds-font_size--body--s);
41
+ font-size: var(--table_header-font-size, var(--bds-font_size--body--s));
37
42
  color: var(--bds-color_on-bg--subtle);
38
43
  white-space: nowrap;
39
44
  border-block-end: 1px solid var(--bds-color_bg--subtle);
@@ -48,12 +53,12 @@
48
53
  display: flex;
49
54
  align-items: center;
50
55
  gap: var(--bds-space_xxs);
51
- padding: var(--bds-space_s) var(--bds-space_m);
56
+ padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
52
57
  inline-size: 100%;
53
58
  cursor: pointer;
54
59
  color: var(--bds-color_on-bg--subtle);
55
60
  font-weight: var(--bds-font_weight--semibold);
56
- font-size: var(--bds-font_size--body--s);
61
+ font-size: var(--table_header-font-size, var(--bds-font_size--body--s));
57
62
  transition: var(--bds-animation_transition);
58
63
  box-sizing: border-box;
59
64
  }
@@ -102,7 +107,7 @@
102
107
  }
103
108
 
104
109
  .td {
105
- padding: var(--bds-space_s) var(--bds-space_m);
110
+ padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
106
111
  color: var(--bds-color_on-bg);
107
112
  vertical-align: middle;
108
113
  }
@@ -66,4 +66,60 @@ describe('Table', () => {
66
66
  render(<Table columns={columns} rows={rows} sortKey="role" sortDirection="asc" onSort={() => {}} />);
67
67
  expect(screen.getByRole('columnheader', { name: /Role/i })).toHaveAttribute('aria-sort', 'ascending');
68
68
  });
69
+
70
+ it('does not stringify a ReactNode header into the sort button aria-label', () => {
71
+ const nodeColumns = [
72
+ ...columns,
73
+ { key: 'extra', header: <span>Extra</span>, sortable: true },
74
+ ];
75
+ render(<Table columns={nodeColumns} rows={rows} onSort={() => {}} />);
76
+ const extraButton = screen.getByRole('button', { name: 'Extra' });
77
+ expect(extraButton.hasAttribute('aria-label')).toBe(false);
78
+ });
79
+
80
+ it('uses sortLabel for the sort button aria-label when provided', () => {
81
+ const nodeColumns = [
82
+ ...columns,
83
+ { key: 'extra', header: <span>Extra</span>, sortable: true, sortLabel: 'Extra column' },
84
+ ];
85
+ render(<Table columns={nodeColumns} rows={rows} onSort={() => {}} />);
86
+ expect(screen.getByRole('button', { name: 'Sort by Extra column' })).toBeInTheDocument();
87
+ });
88
+
89
+ it('does not warn about duplicate keys when row objects are structurally identical', () => {
90
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
91
+ const duplicateRows = [rows[0], { ...rows[0] }];
92
+ render(<Table columns={columns} rows={duplicateRows} />);
93
+ const keyWarnings = consoleErrorSpy.mock.calls.filter(call => String(call[0]).includes('same key'));
94
+ expect(keyWarnings).toHaveLength(0);
95
+ consoleErrorSpy.mockRestore();
96
+ });
97
+
98
+ it('uses rowKey to derive the row key when provided', () => {
99
+ const rowKey = vi.fn((row: Record<string, unknown>) => String(row.name));
100
+ render(<Table columns={columns} rows={rows} rowKey={rowKey} />);
101
+ expect(rowKey).toHaveBeenCalledWith(rows[0], 0);
102
+ expect(rowKey).toHaveBeenCalledWith(rows[1], 1);
103
+ });
104
+
105
+ it('forwards additional HTML attributes to the scrollable wrapper', () => {
106
+ render(<Table columns={columns} rows={rows} data-testid="my-table" />);
107
+ expect(screen.getByTestId('my-table')).toBeInTheDocument();
108
+ });
109
+
110
+ it('exposes the scroll wrapper as a focusable labelled region when a caption is set', () => {
111
+ render(<Table columns={columns} rows={rows} caption="Team members" />);
112
+ const region = screen.getByRole('region', { name: 'Team members' });
113
+ expect(region).toHaveAttribute('tabindex', '0');
114
+ });
115
+
116
+ it('does not expose an unlabelled region role when there is no caption or aria-label', () => {
117
+ render(<Table columns={columns} rows={rows} />);
118
+ expect(screen.queryByRole('region')).not.toBeInTheDocument();
119
+ });
120
+
121
+ it('respects a consumer-supplied aria-label over the generated caption label', () => {
122
+ render(<Table columns={columns} rows={rows} caption="Team members" aria-label="Custom label" />);
123
+ expect(screen.getByRole('region', { name: 'Custom label' })).toBeInTheDocument();
124
+ });
69
125
  });
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { HTMLAttributes, ReactNode, useId } from 'react';
2
2
  import css from './Table.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
@@ -7,16 +7,29 @@ export interface TableColumn<Row> {
7
7
  key: string;
8
8
  header: ReactNode;
9
9
  sortable?: boolean;
10
+ /**
11
+ * Accessible label for the sort button, used instead of stringifying `header`.
12
+ * Required for a reliable accessible name when `header` is not plain text
13
+ * (e.g. an icon or custom ReactNode) — otherwise the button falls back to
14
+ * its visible text content.
15
+ */
16
+ sortLabel?: string;
10
17
  render?: (row: Row) => ReactNode;
11
18
  }
12
19
 
13
- interface TableProps<Row extends Record<string, unknown>> extends WithClassName {
20
+ interface TableProps<Row extends Record<string, unknown>>
21
+ extends WithClassName,
22
+ HTMLAttributes<HTMLDivElement> {
14
23
  columns: TableColumn<Row>[];
15
24
  rows: Row[];
16
25
  caption?: string;
17
26
  sortKey?: string;
18
27
  sortDirection?: 'asc' | 'desc';
19
28
  onSort?: (key: string, direction: 'asc' | 'desc') => void;
29
+ /**
30
+ * Derives a stable React key per row. Defaults to the row index when omitted.
31
+ */
32
+ rowKey?: (row: Row, index: number) => string;
20
33
  }
21
34
 
22
35
  export function Table<Row extends Record<string, unknown>>({
@@ -26,8 +39,15 @@ export function Table<Row extends Record<string, unknown>>({
26
39
  sortKey,
27
40
  sortDirection,
28
41
  onSort,
42
+ rowKey,
29
43
  className,
44
+ ...rest
30
45
  }: Readonly<TableProps<Row>>) {
46
+ const generatedId = useId();
47
+ const captionId = caption ? `${generatedId}-caption` : undefined;
48
+ const hasCustomLabel = 'aria-label' in rest || 'aria-labelledby' in rest;
49
+ const hasAccessibleName = Boolean(caption) || hasCustomLabel;
50
+
31
51
  const handleSort = (key: string) => {
32
52
  if (!onSort) return;
33
53
  const nextDirection = sortKey === key && sortDirection === 'asc' ? 'desc' : 'asc';
@@ -39,10 +59,28 @@ export function Table<Row extends Record<string, unknown>>({
39
59
  return sortDirection === 'asc' ? 'ascending' : 'descending';
40
60
  };
41
61
 
62
+ const getSortAriaLabel = (col: TableColumn<Row>): string | undefined => {
63
+ const label = col.sortLabel ?? (typeof col.header === 'string' ? col.header : undefined);
64
+ if (!label) return undefined;
65
+ const stateSuffix =
66
+ sortKey === col.key ? `, currently ${sortDirection === 'asc' ? 'ascending' : 'descending'}` : '';
67
+ return `Sort by ${label}${stateSuffix}`;
68
+ };
69
+
42
70
  return (
43
- <div className={cn(css.wrapper, className)}>
71
+ <div
72
+ {...rest}
73
+ className={cn(css.wrapper, className)}
74
+ tabIndex={rest.tabIndex ?? 0}
75
+ role={hasAccessibleName ? 'region' : undefined}
76
+ aria-labelledby={hasCustomLabel ? rest['aria-labelledby'] : captionId}
77
+ >
44
78
  <table className={css.table}>
45
- {caption && <caption className={css.caption}>{caption}</caption>}
79
+ {caption && (
80
+ <caption id={captionId} className={css.caption}>
81
+ {caption}
82
+ </caption>
83
+ )}
46
84
  <thead className={css.thead}>
47
85
  <tr>
48
86
  {columns.map(col => (
@@ -57,7 +95,7 @@ export function Table<Row extends Record<string, unknown>>({
57
95
  type="button"
58
96
  className={css.sortButton}
59
97
  onClick={() => handleSort(col.key)}
60
- aria-label={`Sort by ${String(col.header)}${sortKey === col.key ? `, currently ${sortDirection === 'asc' ? 'ascending' : 'descending'}` : ''}`}
98
+ aria-label={getSortAriaLabel(col)}
61
99
  >
62
100
  {col.header}
63
101
  <svg
@@ -84,7 +122,7 @@ export function Table<Row extends Record<string, unknown>>({
84
122
  </thead>
85
123
  <tbody className={css.tbody}>
86
124
  {rows.map((row, rowIndex) => (
87
- <tr key={JSON.stringify(row) || rowIndex} className={css.tr}>
125
+ <tr key={rowKey?.(row, rowIndex) ?? rowIndex} className={css.tr}>
88
126
  {columns.map(col => (
89
127
  <td key={col.key} className={css.td}>
90
128
  {col.render ? col.render(row) : String(row[col.key] ?? '')}
@@ -31,6 +31,8 @@ Organises content into discrete panels accessible via a horizontal tab list.
31
31
 
32
32
  ## Props
33
33
 
34
+ > **HTML attributes** — all standard HTML attributes for the root element are forwarded via `{...rest}` (`id`, `className`, `style`, `data-*`, `aria-*`, event handlers, etc.). Component-controlled attributes take precedence.
35
+
34
36
  <ArgTypes of={Stories} />
35
37
 
36
38
  ## CSS variables
@@ -64,9 +66,23 @@ The override surface is **layered**. Setting `--tab_color` / `--tab_border-color
64
66
 
65
67
  Defaults: hover/active text resolves to `--bds-color_interactive_on-bg`; active border to `--bds-color_interactive`.
66
68
 
69
+ ### Spacing tokens
70
+
71
+ <table>
72
+ <thead>
73
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
74
+ </thead>
75
+ <tbody>
76
+ <tr><td>`--tab_padding`</td><td>`var(--bds-space_s) var(--bds-space_m)`</td><td>Padding of each tab button.</td></tr>
77
+ <tr><td>`--tabs_gap`</td><td>`var(--bds-space_xxs)`</td><td>Gap between tab buttons in the tablist.</td></tr>
78
+ <tr><td>`--tabs_panel-padding`</td><td>`var(--bds-space_m)`</td><td>Block-start padding of the active panel.</td></tr>
79
+ </tbody>
80
+ </table>
67
81
 
68
82
  ## Accessibility
69
83
 
70
84
  - Implements ARIA tabs pattern: `role="tablist"`, `role="tab"`, `role="tabpanel"`
71
85
  - Arrow keys navigate between tabs; `Enter`/`Space` activate
72
86
  - Active tab uses `aria-selected="true"`; panel linked via `aria-labelledby`
87
+ - Tab panels are focusable (`tabIndex={0}`) so keyboard users can move from the active tab into its panel content
88
+ - The initially active tab always resolves to an enabled tab: `defaultTab` is honoured only when it points to a non-disabled tab, otherwise the first enabled tab is used — a disabled first tab (or an invalid `defaultTab`) never leaves the tablist keyboard-unreachable
@@ -7,14 +7,14 @@
7
7
  .tabList {
8
8
  display: flex;
9
9
  border-block-end: 1px solid var(--bds-color_bg--subtle);
10
- gap: var(--bds-space_xxs);
10
+ gap: var(--tabs_gap, var(--bds-space_xxs));
11
11
  }
12
12
 
13
13
  .tab {
14
14
  all: unset;
15
15
  display: inline-flex;
16
16
  align-items: center;
17
- padding: var(--bds-space_s) var(--bds-space_m);
17
+ padding: var(--tab_padding, var(--bds-space_s) var(--bds-space_m));
18
18
  font-family: var(--bds-font_family--body);
19
19
  font-size: var(--bds-font_size--body);
20
20
  font-weight: var(--bds-font_weight--semibold);
@@ -51,7 +51,7 @@
51
51
  }
52
52
 
53
53
  .panel {
54
- padding-block-start: var(--bds-space_m);
54
+ padding-block-start: var(--tabs_panel-padding, var(--bds-space_m));
55
55
  }
56
56
 
57
57
  .panel:focus-visible {
@@ -88,4 +88,24 @@ describe('Tabs', () => {
88
88
  const panelId = firstTab.getAttribute('aria-controls');
89
89
  expect(document.getElementById(panelId!)).toBeInTheDocument();
90
90
  });
91
+
92
+ it('activates the first enabled tab when the first tab is disabled and no defaultTab is given', () => {
93
+ const tabsWithDisabledFirst = [
94
+ { id: 'tab1', label: 'First', content: <p>First panel</p>, disabled: true },
95
+ { id: 'tab2', label: 'Second', content: <p>Second panel</p> },
96
+ ];
97
+ render(<Tabs tabs={tabsWithDisabledFirst} />);
98
+ expect(screen.getByRole('tab', { name: 'Second' })).toHaveAttribute('aria-selected', 'true');
99
+ expect(screen.getByRole('tab', { name: 'Second' })).toHaveAttribute('tabindex', '0');
100
+ });
101
+
102
+ it('falls back to the first enabled tab when defaultTab points to a disabled tab', () => {
103
+ render(<Tabs tabs={tabs} defaultTab="tab3" />);
104
+ expect(screen.getByRole('tab', { name: 'First' })).toHaveAttribute('aria-selected', 'true');
105
+ });
106
+
107
+ it('makes the active tab panel focusable', () => {
108
+ render(<Tabs tabs={tabs} />);
109
+ expect(screen.getByText('First panel').closest('[role="tabpanel"]')).toHaveAttribute('tabindex', '0');
110
+ });
91
111
  });
@@ -17,7 +17,10 @@ interface TabsProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
17
17
 
18
18
  export function Tabs({ tabs, defaultTab, className, ...rest }: Readonly<TabsProps>) {
19
19
  const baseId = useId();
20
- const [activeTab, setActiveTab] = useState(defaultTab ?? tabs[0]?.id);
20
+ const [activeTab, setActiveTab] = useState(() => {
21
+ const initialTab = tabs.find(tab => tab.id === defaultTab && !tab.disabled) ?? tabs.find(tab => !tab.disabled);
22
+ return initialTab?.id;
23
+ });
21
24
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
22
25
 
23
26
  const enabledIndexes = tabs
@@ -88,6 +91,7 @@ export function Tabs({ tabs, defaultTab, className, ...rest }: Readonly<TabsProp
88
91
  role="tabpanel"
89
92
  aria-labelledby={tabId}
90
93
  hidden={!isActive}
94
+ tabIndex={0}
91
95
  className={css.panel}
92
96
  >
93
97
  {tab.content}
@@ -34,6 +34,8 @@ Short contextual label that appears on hover or focus. Use to supplement icon bu
34
34
 
35
35
  ## Props
36
36
 
37
+ > **HTML attributes** — all standard HTML attributes for the root element are forwarded via `{...rest}` (`id`, `className`, `style`, `data-*`, `aria-*`, event handlers, etc.). Component-controlled attributes take precedence.
38
+
37
39
  <ArgTypes of={Stories} />
38
40
 
39
41
  ## CSS variables
@@ -46,6 +48,11 @@ Short contextual label that appears on hover or focus. Use to supplement icon bu
46
48
  <tbody>
47
49
  <tr><td>`--tooltip_bg`</td><td>`var(--bds-color_on-bg)`</td><td>Tooltip background colour</td></tr>
48
50
  <tr><td>`--tooltip_text`</td><td>`var(--bds-color_bg)`</td><td>Tooltip text colour</td></tr>
51
+ <tr><td>`--tooltip_radius`</td><td>`var(--bds-border_radius--full)`</td><td>Tooltip corner radius</td></tr>
52
+ <tr><td>`--tooltip_padding`</td><td>`var(--bds-space_xxs) var(--bds-space_xs)`</td><td>Tooltip padding</td></tr>
53
+ <tr><td>`--tooltip_font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Tooltip text size</td></tr>
54
+ <tr><td>`--tooltip_offset`</td><td>`var(--bds-space_xs)`</td><td>Gap between the trigger and the tooltip. Set to `0` to close the gap so the pointer can travel from the trigger onto the tooltip without a dead zone.</td></tr>
55
+ <tr><td>`--tooltip_max-inline-size`</td><td>`20rem`</td><td>Maximum inline size before content wraps</td></tr>
49
56
  </tbody>
50
57
  </table>
51
58
 
@@ -54,4 +61,6 @@ Short contextual label that appears on hover or focus. Use to supplement icon bu
54
61
 
55
62
  - Tooltip content is linked via `aria-describedby` on the trigger element
56
63
  - Shown on both `:hover` and `:focus` to be keyboard accessible
64
+ - Dismissible with the <kbd>Escape</kbd> key while the tooltip is visible (WCAG 1.4.13)
65
+ - Hoverable — moving the pointer from the trigger onto the tooltip itself keeps it open instead of dismissing it
57
66
  - Does not contain interactive content (use `Popover` for that)
@@ -6,16 +6,27 @@
6
6
  }
7
7
 
8
8
  .tooltip {
9
+ /* The `hidden` attribute (toggled by React state) is what makes the
10
+ tooltip's visibility genuinely testable and accessible - it removes
11
+ the element from the accessibility tree natively, matching the
12
+ convention used by Accordion/Tabs panels. Browsers default
13
+ `[hidden]` to `display: none`, which would skip the opacity
14
+ transition below, so it's overridden back to a normal display here;
15
+ showing/hiding stays driven entirely by visibility/opacity via the
16
+ --visible class. */
17
+ display: block;
9
18
  position: absolute;
10
19
  z-index: var(--bds-z-index_popover);
11
- padding: var(--bds-space_xxs) var(--bds-space_xs);
20
+ padding: var(--tooltip_padding, var(--bds-space_xxs) var(--bds-space_xs));
12
21
  border-radius: var(--tooltip_radius, var(--bds-border_radius--full));
13
22
  background-color: var(--tooltip_bg, var(--bds-color_on-bg));
14
23
  color: var(--tooltip_text, var(--bds-color_bg));
15
- font-size: var(--bds-font_size--body--s);
24
+ font-size: var(--tooltip_font-size, var(--bds-font_size--body--s));
16
25
  line-height: var(--bds-font_line-height--body);
17
- white-space: nowrap;
26
+ max-inline-size: var(--tooltip_max-inline-size, 20rem);
27
+ white-space: normal;
18
28
  pointer-events: none;
29
+ --tooltip_dir: 1;
19
30
 
20
31
  /* Hidden by default */
21
32
  visibility: hidden;
@@ -23,37 +34,46 @@
23
34
  transition: opacity var(--bds-animation_transition-duration) var(--bds-animation_easing);
24
35
  }
25
36
 
26
- /* Show on hover and keyboard focus */
27
- .wrapper:hover .tooltip,
28
- .wrapper:focus-within .tooltip {
37
+ [dir='rtl'] .tooltip,
38
+ .tooltip[dir='rtl'] {
39
+ --tooltip_dir: -1;
40
+ }
41
+
42
+ /* Shown when the component's isVisible state is true (hover, focus, or
43
+ keyboard-triggered) — driven by React state rather than pure CSS :hover
44
+ so an Escape keypress can dismiss it (WCAG 1.4.13) even while the
45
+ pointer is still over the trigger. pointer-events is enabled only while
46
+ visible so the tooltip content itself is hoverable. */
47
+ .--visible {
29
48
  visibility: visible;
30
49
  opacity: 1;
50
+ pointer-events: auto;
31
51
  }
32
52
 
33
53
  /* Placement: top (default) */
34
54
  .--placement_top {
35
- inset-block-end: calc(100% + var(--bds-space_xs));
55
+ inset-block-end: calc(100% + var(--tooltip_offset, var(--bds-space_xs)));
36
56
  inset-inline-start: 50%;
37
- transform: translateX(-50%);
57
+ transform: translateX(calc(-50% * var(--tooltip_dir, 1)));
38
58
  }
39
59
 
40
60
  /* Placement: bottom */
41
61
  .--placement_bottom {
42
- inset-block-start: calc(100% + var(--bds-space_xs));
62
+ inset-block-start: calc(100% + var(--tooltip_offset, var(--bds-space_xs)));
43
63
  inset-inline-start: 50%;
44
- transform: translateX(-50%);
64
+ transform: translateX(calc(-50% * var(--tooltip_dir, 1)));
45
65
  }
46
66
 
47
67
  /* Placement: left */
48
68
  .--placement_left {
49
- inset-inline-end: calc(100% + var(--bds-space_xs));
69
+ inset-inline-end: calc(100% + var(--tooltip_offset, var(--bds-space_xs)));
50
70
  inset-block-start: 50%;
51
71
  transform: translateY(-50%);
52
72
  }
53
73
 
54
74
  /* Placement: right */
55
75
  .--placement_right {
56
- inset-inline-start: calc(100% + var(--bds-space_xs));
76
+ inset-inline-start: calc(100% + var(--tooltip_offset, var(--bds-space_xs)));
57
77
  inset-block-start: 50%;
58
78
  transform: translateY(-50%);
59
79
  }
@@ -43,4 +43,60 @@ describe('Tooltip', () => {
43
43
  await user.tab();
44
44
  expect(screen.getByRole('tooltip')).toBeVisible();
45
45
  });
46
+
47
+ it('shows and hides the tooltip on hover', async () => {
48
+ const user = userEvent.setup();
49
+ render(
50
+ <Tooltip content="Hover text">
51
+ <button type="button">Hover target</button>
52
+ </Tooltip>
53
+ );
54
+ const trigger = screen.getByRole('button', { name: 'Hover target' });
55
+ const tooltip = screen.getByRole('tooltip', { hidden: true });
56
+ expect(tooltip).not.toBeVisible();
57
+
58
+ await user.hover(trigger);
59
+ expect(tooltip).toBeVisible();
60
+
61
+ await user.unhover(trigger);
62
+ expect(tooltip).not.toBeVisible();
63
+ });
64
+
65
+ it('dismisses the tooltip when Escape is pressed while visible', async () => {
66
+ const user = userEvent.setup();
67
+ render(
68
+ <Tooltip content="Focused">
69
+ <button type="button">Focus me</button>
70
+ </Tooltip>
71
+ );
72
+ await user.tab();
73
+ const tooltip = screen.getByRole('tooltip');
74
+ expect(tooltip).toBeVisible();
75
+
76
+ await user.keyboard('{Escape}');
77
+ expect(tooltip).not.toBeVisible();
78
+ });
79
+
80
+ it('keeps the tooltip visible when the pointer moves onto the tooltip content, and hides it once the pointer leaves', async () => {
81
+ const user = userEvent.setup();
82
+ render(
83
+ <Tooltip content="Hover text">
84
+ <button type="button">Hover target</button>
85
+ </Tooltip>
86
+ );
87
+ const trigger = screen.getByRole('button', { name: 'Hover target' });
88
+ const tooltip = screen.getByRole('tooltip', { hidden: true });
89
+
90
+ await user.hover(trigger);
91
+ expect(tooltip).toBeVisible();
92
+
93
+ // Moving the pointer onto the (now interactive) tooltip content must not
94
+ // dismiss it - this is only possible once pointer-events is enabled on
95
+ // the visible tooltip.
96
+ await user.hover(tooltip);
97
+ expect(tooltip).toBeVisible();
98
+
99
+ await user.unhover(tooltip);
100
+ expect(tooltip).not.toBeVisible();
101
+ });
46
102
  });