@boostdev/design-system-components 1.1.0 → 1.1.2

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 (269) hide show
  1. package/AGENTS.md +46 -5
  2. package/dist/client.cjs +206 -113
  3. package/dist/client.css +664 -624
  4. package/dist/client.d.cts +9 -3
  5. package/dist/client.d.ts +9 -3
  6. package/dist/client.js +241 -149
  7. package/dist/index.cjs +206 -113
  8. package/dist/index.css +664 -624
  9. package/dist/index.d.cts +9 -3
  10. package/dist/index.d.ts +9 -3
  11. package/dist/index.js +241 -149
  12. package/dist/web-components/chunk-2QA62WCQ.js +48 -0
  13. package/dist/web-components/chunk-4BX6AWXX.js +146 -0
  14. package/dist/web-components/chunk-57TRNCWQ.js +93 -0
  15. package/dist/web-components/chunk-5IPHEONG.js +139 -0
  16. package/dist/web-components/chunk-5R6IFELV.js +63 -0
  17. package/dist/web-components/chunk-5TFWOPLH.js +49 -0
  18. package/dist/web-components/chunk-6I3CFC26.js +89 -0
  19. package/dist/web-components/chunk-6T3RKUKG.js +65 -0
  20. package/dist/web-components/chunk-7FMR53BW.js +127 -0
  21. package/dist/web-components/chunk-A67LCT7C.js +134 -0
  22. package/dist/web-components/chunk-AGKJTMJI.js +56 -0
  23. package/dist/web-components/chunk-BQLZ7I4G.js +56 -0
  24. package/dist/web-components/chunk-CAPI25CU.js +53 -0
  25. package/dist/web-components/chunk-CJ5ZOO42.js +229 -0
  26. package/dist/web-components/chunk-CR5PGUHV.js +109 -0
  27. package/dist/web-components/chunk-ECW3HHWA.js +185 -0
  28. package/dist/web-components/chunk-HGBNDR22.js +98 -0
  29. package/dist/web-components/chunk-IK3PTFZ4.js +133 -0
  30. package/dist/web-components/chunk-IQGLIQSO.js +188 -0
  31. package/dist/web-components/chunk-ITHLKR7Q.js +105 -0
  32. package/dist/web-components/chunk-K5CE6HSB.js +169 -0
  33. package/dist/web-components/chunk-L3XHOMN6.js +111 -0
  34. package/dist/{web-components.js → web-components/chunk-LGEZYFUU.js} +62 -834
  35. package/dist/web-components/chunk-LSC4N56Q.js +168 -0
  36. package/dist/web-components/chunk-LY62SAVN.js +64 -0
  37. package/dist/web-components/chunk-O4UFI2RX.js +244 -0
  38. package/dist/web-components/chunk-O7I63SQX.js +124 -0
  39. package/dist/web-components/chunk-OHUTLSIQ.js +69 -0
  40. package/dist/web-components/chunk-OQTRTMAB.js +27 -0
  41. package/dist/web-components/chunk-OTX4COSV.js +187 -0
  42. package/dist/web-components/chunk-PY62FTRW.js +179 -0
  43. package/dist/web-components/chunk-Q4T5BJT6.js +94 -0
  44. package/dist/web-components/chunk-QDRH46TF.js +281 -0
  45. package/dist/web-components/chunk-QDST57JC.js +119 -0
  46. package/dist/web-components/chunk-QPOKHLMQ.js +45 -0
  47. package/dist/web-components/chunk-SOQQ7RVJ.js +88 -0
  48. package/dist/web-components/chunk-UFFCKVZE.js +149 -0
  49. package/dist/web-components/chunk-VM3YHGQ2.js +152 -0
  50. package/dist/web-components/chunk-VMMVWLXL.js +53 -0
  51. package/dist/web-components/chunk-VSS5ITNE.js +109 -0
  52. package/dist/web-components/chunk-XIENXR7H.js +118 -0
  53. package/dist/web-components/chunk-Y4MAXTMA.js +80 -0
  54. package/dist/web-components/chunk-YLVTEY23.js +165 -0
  55. package/dist/web-components/globals.d.ts +2 -0
  56. package/dist/web-components/globals.js +1 -0
  57. package/dist/web-components/index.d.ts +41 -0
  58. package/dist/web-components/index.js +165 -0
  59. package/dist/web-components/interaction/bds-accordion-item.d.ts +54 -0
  60. package/dist/web-components/interaction/bds-accordion-item.js +7 -0
  61. package/dist/web-components/interaction/bds-accordion.d.ts +50 -0
  62. package/dist/web-components/interaction/bds-accordion.js +7 -0
  63. package/dist/web-components/interaction/bds-button.d.ts +100 -0
  64. package/dist/web-components/interaction/bds-button.js +7 -0
  65. package/dist/web-components/interaction/bds-collapsible.d.ts +52 -0
  66. package/dist/web-components/interaction/bds-collapsible.js +7 -0
  67. package/dist/web-components/interaction/bds-dialog.d.ts +56 -0
  68. package/dist/web-components/interaction/bds-dialog.js +7 -0
  69. package/dist/web-components/interaction/bds-drawer.d.ts +64 -0
  70. package/dist/web-components/interaction/bds-drawer.js +7 -0
  71. package/dist/web-components/interaction/bds-popover.d.ts +62 -0
  72. package/dist/web-components/interaction/bds-popover.js +7 -0
  73. package/dist/web-components/interaction/bds-skip-link.d.ts +36 -0
  74. package/dist/web-components/interaction/bds-skip-link.js +7 -0
  75. package/dist/web-components/interaction/bds-tab-panel.d.ts +26 -0
  76. package/dist/web-components/interaction/bds-tab-panel.js +7 -0
  77. package/dist/web-components/interaction/bds-tab.d.ts +45 -0
  78. package/dist/web-components/interaction/bds-tab.js +7 -0
  79. package/dist/web-components/interaction/bds-tabs.d.ts +46 -0
  80. package/dist/web-components/interaction/bds-tabs.js +7 -0
  81. package/dist/web-components/interaction/bds-toast-provider.d.ts +57 -0
  82. package/dist/web-components/interaction/bds-toast-provider.js +7 -0
  83. package/dist/web-components/interaction/bds-tooltip.d.ts +50 -0
  84. package/dist/web-components/interaction/bds-tooltip.js +7 -0
  85. package/dist/web-components/interaction/form/bds-checkbox.d.ts +70 -0
  86. package/dist/web-components/interaction/form/bds-checkbox.js +7 -0
  87. package/dist/web-components/interaction/form/bds-combobox.d.ts +84 -0
  88. package/dist/web-components/interaction/form/bds-combobox.js +7 -0
  89. package/dist/web-components/interaction/form/bds-file-input.d.ts +62 -0
  90. package/dist/web-components/interaction/form/bds-file-input.js +7 -0
  91. package/dist/web-components/interaction/form/bds-number-input.d.ts +68 -0
  92. package/dist/web-components/interaction/form/bds-number-input.js +7 -0
  93. package/dist/web-components/interaction/form/bds-radio.d.ts +68 -0
  94. package/dist/web-components/interaction/form/bds-radio.js +7 -0
  95. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +71 -0
  96. package/dist/web-components/interaction/form/bds-segmented-control.js +7 -0
  97. package/dist/web-components/interaction/form/bds-select.d.ts +79 -0
  98. package/dist/web-components/interaction/form/bds-select.js +7 -0
  99. package/dist/web-components/interaction/form/bds-slider.d.ts +70 -0
  100. package/dist/web-components/interaction/form/bds-slider.js +7 -0
  101. package/dist/web-components/interaction/form/bds-switch.d.ts +67 -0
  102. package/dist/web-components/interaction/form/bds-switch.js +7 -0
  103. package/dist/web-components/interaction/form/bds-textarea.d.ts +70 -0
  104. package/dist/web-components/interaction/form/bds-textarea.js +7 -0
  105. package/dist/web-components/ui/bds-alert.d.ts +66 -0
  106. package/dist/web-components/ui/bds-alert.js +7 -0
  107. package/dist/web-components/ui/bds-avatar.d.ts +50 -0
  108. package/dist/web-components/ui/bds-avatar.js +7 -0
  109. package/dist/web-components/ui/bds-badge.d.ts +35 -0
  110. package/dist/web-components/ui/bds-badge.js +7 -0
  111. package/dist/web-components/ui/bds-breadcrumb.d.ts +35 -0
  112. package/dist/web-components/ui/bds-breadcrumb.js +7 -0
  113. package/dist/web-components/ui/bds-card.d.ts +72 -0
  114. package/dist/web-components/ui/bds-card.js +7 -0
  115. package/dist/web-components/ui/bds-description-list.d.ts +42 -0
  116. package/dist/web-components/ui/bds-description-list.js +7 -0
  117. package/dist/web-components/ui/bds-icon-wrapper.d.ts +39 -0
  118. package/dist/web-components/ui/bds-icon-wrapper.js +7 -0
  119. package/dist/web-components/ui/bds-link.d.ts +59 -0
  120. package/dist/web-components/ui/bds-link.js +7 -0
  121. package/dist/web-components/ui/bds-loading.d.ts +32 -0
  122. package/dist/web-components/ui/bds-loading.js +7 -0
  123. package/dist/web-components/ui/bds-notification-banner.d.ts +61 -0
  124. package/dist/web-components/ui/bds-notification-banner.js +7 -0
  125. package/dist/web-components/ui/bds-progress-circle.d.ts +53 -0
  126. package/dist/web-components/ui/bds-progress-circle.js +7 -0
  127. package/dist/web-components/ui/bds-progress.d.ts +54 -0
  128. package/dist/web-components/ui/bds-progress.js +7 -0
  129. package/dist/web-components/ui/bds-rating.d.ts +36 -0
  130. package/dist/web-components/ui/bds-rating.js +7 -0
  131. package/dist/web-components/ui/bds-section-header.d.ts +53 -0
  132. package/dist/web-components/ui/bds-section-header.js +8 -0
  133. package/dist/web-components/ui/bds-separator.d.ts +32 -0
  134. package/dist/web-components/ui/bds-separator.js +7 -0
  135. package/dist/web-components/ui/bds-skeleton.d.ts +22 -0
  136. package/dist/web-components/ui/bds-skeleton.js +7 -0
  137. package/dist/web-components/ui/bds-typography.d.ts +44 -0
  138. package/dist/web-components/ui/bds-typography.js +8 -0
  139. package/package.json +169 -4
  140. package/src/components/interaction/Button/Button.module.css +1 -1
  141. package/src/components/interaction/Button/Button.native.mdx +72 -0
  142. package/src/components/interaction/Command/Command.module.css +7 -7
  143. package/src/components/interaction/Command/Command.spec.tsx +37 -0
  144. package/src/components/interaction/Command/Command.tsx +37 -2
  145. package/src/components/interaction/Dialog/Dialog.module.css +10 -13
  146. package/src/components/interaction/Dialog/Dialog.spec.tsx +53 -1
  147. package/src/components/interaction/Dialog/Dialog.stories.tsx +17 -0
  148. package/src/components/interaction/Dialog/Dialog.tsx +50 -16
  149. package/src/components/interaction/Drawer/Drawer.module.css +11 -11
  150. package/src/components/interaction/Drawer/Drawer.spec.tsx +48 -1
  151. package/src/components/interaction/Drawer/Drawer.tsx +38 -4
  152. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
  153. package/src/components/interaction/Popover/Popover.module.css +74 -15
  154. package/src/components/interaction/Popover/Popover.spec.tsx +94 -59
  155. package/src/components/interaction/Popover/Popover.tsx +44 -60
  156. package/src/components/interaction/Toast/Toast.module.css +7 -7
  157. package/src/components/interaction/form/Checkbox/Checkbox.module.css +6 -6
  158. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
  159. package/src/components/interaction/form/Combobox/Combobox.module.css +7 -8
  160. package/src/components/interaction/form/FileInput/FileInput.module.css +4 -4
  161. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  162. package/src/components/interaction/form/Radio/Radio.module.css +6 -6
  163. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
  164. package/src/components/interaction/form/Select/Select.module.css +3 -3
  165. package/src/components/interaction/form/Slider/Slider.module.css +6 -6
  166. package/src/components/interaction/form/Switch/Switch.module.css +8 -8
  167. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
  168. package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
  169. package/src/components/ui/Accordion/Accordion.module.css +5 -5
  170. package/src/components/ui/Alert/Alert.native.mdx +62 -0
  171. package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
  172. package/src/components/ui/Badge/Badge.native.mdx +54 -0
  173. package/src/components/ui/Calendar/Calendar.module.css +7 -7
  174. package/src/components/ui/Carousel/Carousel.module.css +5 -5
  175. package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
  176. package/src/components/ui/Link/Link.module.css +2 -2
  177. package/src/components/ui/Loading/Loading.module.css +7 -7
  178. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
  179. package/src/components/ui/Pagination/Pagination.module.css +6 -6
  180. package/src/components/ui/Progress/Progress.module.css +5 -5
  181. package/src/components/ui/Separator/Separator.module.css +3 -3
  182. package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
  183. package/src/components/ui/Table/Table.module.css +9 -9
  184. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  185. package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
  186. package/src/components/ui/Typography/Typography.native.mdx +56 -0
  187. package/src/polyfill-invoker-commands.ts +68 -0
  188. package/src/react-augment.d.ts +19 -0
  189. package/src/stories/ReactNative.mdx +121 -0
  190. package/src/web-components/index.ts +47 -0
  191. package/src/web-components/interaction/BdsButton.mdx +106 -0
  192. package/src/web-components/interaction/BdsPopover.mdx +120 -0
  193. package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
  194. package/src/web-components/interaction/bds-accordion-item.spec.ts +75 -0
  195. package/src/web-components/interaction/bds-accordion-item.ts +181 -0
  196. package/src/web-components/interaction/bds-accordion.spec.ts +73 -0
  197. package/src/web-components/interaction/bds-accordion.ts +99 -0
  198. package/src/web-components/interaction/bds-collapsible.spec.ts +56 -0
  199. package/src/web-components/interaction/bds-collapsible.ts +170 -0
  200. package/src/web-components/interaction/bds-dialog.spec.ts +119 -0
  201. package/src/web-components/interaction/bds-dialog.ts +224 -0
  202. package/src/web-components/interaction/bds-drawer.spec.ts +113 -0
  203. package/src/web-components/interaction/bds-drawer.ts +266 -0
  204. package/src/web-components/interaction/bds-popover.spec.ts +49 -1
  205. package/src/web-components/interaction/bds-popover.ts +12 -0
  206. package/src/web-components/interaction/bds-skip-link.spec.ts +42 -0
  207. package/src/web-components/interaction/bds-skip-link.ts +74 -0
  208. package/src/web-components/interaction/bds-tab-panel.spec.ts +32 -0
  209. package/src/web-components/interaction/bds-tab-panel.ts +38 -0
  210. package/src/web-components/interaction/bds-tab.spec.ts +71 -0
  211. package/src/web-components/interaction/bds-tab.ts +110 -0
  212. package/src/web-components/interaction/bds-tabs.spec.ts +120 -0
  213. package/src/web-components/interaction/bds-tabs.ts +152 -0
  214. package/src/web-components/interaction/bds-tooltip.spec.ts +49 -0
  215. package/src/web-components/interaction/bds-tooltip.ts +127 -0
  216. package/src/web-components/interaction/form/bds-checkbox.spec.ts +63 -0
  217. package/src/web-components/interaction/form/bds-checkbox.ts +168 -0
  218. package/src/web-components/interaction/form/bds-combobox.spec.ts +117 -0
  219. package/src/web-components/interaction/form/bds-combobox.ts +336 -0
  220. package/src/web-components/interaction/form/bds-file-input.spec.ts +58 -0
  221. package/src/web-components/interaction/form/bds-file-input.ts +209 -0
  222. package/src/web-components/interaction/form/bds-number-input.spec.ts +87 -0
  223. package/src/web-components/interaction/form/bds-number-input.ts +221 -0
  224. package/src/web-components/interaction/form/bds-radio.spec.ts +70 -0
  225. package/src/web-components/interaction/form/bds-radio.ts +157 -0
  226. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +74 -0
  227. package/src/web-components/interaction/form/bds-segmented-control.ts +211 -0
  228. package/src/web-components/interaction/form/bds-select.spec.ts +74 -0
  229. package/src/web-components/interaction/form/bds-select.ts +190 -0
  230. package/src/web-components/interaction/form/bds-slider.spec.ts +41 -0
  231. package/src/web-components/interaction/form/bds-slider.ts +162 -0
  232. package/src/web-components/interaction/form/bds-switch.spec.ts +62 -0
  233. package/src/web-components/interaction/form/bds-switch.ts +166 -0
  234. package/src/web-components/interaction/form/bds-textarea.spec.ts +55 -0
  235. package/src/web-components/interaction/form/bds-textarea.ts +143 -0
  236. package/src/web-components/test/helpers.ts +8 -0
  237. package/src/web-components/ui/BdsAlert.mdx +90 -0
  238. package/src/web-components/ui/BdsBadge.mdx +74 -0
  239. package/src/web-components/ui/bds-avatar.spec.ts +75 -0
  240. package/src/web-components/ui/bds-avatar.ts +122 -0
  241. package/src/web-components/ui/bds-breadcrumb.spec.ts +55 -0
  242. package/src/web-components/ui/bds-breadcrumb.ts +114 -0
  243. package/src/web-components/ui/bds-card.spec.ts +70 -0
  244. package/src/web-components/ui/bds-card.ts +156 -0
  245. package/src/web-components/ui/bds-description-list.spec.ts +36 -0
  246. package/src/web-components/ui/bds-description-list.ts +79 -0
  247. package/src/web-components/ui/bds-icon-wrapper.spec.ts +21 -0
  248. package/src/web-components/ui/bds-icon-wrapper.ts +68 -0
  249. package/src/web-components/ui/bds-link.spec.ts +72 -0
  250. package/src/web-components/ui/bds-link.ts +138 -0
  251. package/src/web-components/ui/bds-loading.spec.ts +38 -0
  252. package/src/web-components/ui/bds-loading.ts +76 -0
  253. package/src/web-components/ui/bds-notification-banner.spec.ts +82 -0
  254. package/src/web-components/ui/bds-notification-banner.ts +185 -0
  255. package/src/web-components/ui/bds-progress-circle.spec.ts +58 -0
  256. package/src/web-components/ui/bds-progress-circle.ts +142 -0
  257. package/src/web-components/ui/bds-progress.spec.ts +66 -0
  258. package/src/web-components/ui/bds-progress.ts +127 -0
  259. package/src/web-components/ui/bds-rating.spec.ts +70 -0
  260. package/src/web-components/ui/bds-rating.ts +80 -0
  261. package/src/web-components/ui/bds-section-header.spec.ts +57 -0
  262. package/src/web-components/ui/bds-section-header.ts +96 -0
  263. package/src/web-components/ui/bds-separator.spec.ts +50 -0
  264. package/src/web-components/ui/bds-separator.ts +66 -0
  265. package/src/web-components/ui/bds-skeleton.spec.ts +22 -0
  266. package/src/web-components/ui/bds-skeleton.ts +55 -0
  267. package/src/web-components/ui/bds-typography.spec.ts +76 -0
  268. package/src/web-components/ui/bds-typography.ts +112 -0
  269. package/dist/web-components.d.ts +0 -303
@@ -0,0 +1,110 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-tab>` — child element of `<bds-tabs>`.
5
+ *
6
+ * Represents a single tab button. State (`selected`, `tabIndex`, `disabled`)
7
+ * is managed by the parent `<bds-tabs>` element.
8
+ *
9
+ * Slots:
10
+ * (default) — tab label
11
+ *
12
+ * Events:
13
+ * bds-tab-select — fired (bubbles, composed) when the tab is clicked
14
+ *
15
+ * @example
16
+ * <bds-tab>Overview</bds-tab>
17
+ */
18
+ export class BdsTab extends LitElement {
19
+ static styles = css`
20
+ :host {
21
+ display: inline-flex;
22
+ }
23
+
24
+ .tab {
25
+ all: unset;
26
+ display: inline-flex;
27
+ align-items: center;
28
+ padding: var(--bds-space_s) var(--bds-space_m);
29
+ font-family: var(--bds-font_family--body);
30
+ font-size: var(--bds-font_size--body);
31
+ font-weight: var(--bds-font_weight--semibold);
32
+ color: var(--tab_color, var(--bds-color_on-bg));
33
+ border-block-end: 2px solid var(--tab_border-color, transparent);
34
+ margin-block-end: -1px;
35
+ cursor: pointer;
36
+ transition: var(--bds-animation_transition);
37
+ white-space: nowrap;
38
+ }
39
+
40
+ :host([selected]) .tab {
41
+ --tab_color: var(--bds-color_interactive_on-bg);
42
+ --tab_border-color: var(--bds-color_interactive);
43
+ }
44
+
45
+ .tab:disabled {
46
+ opacity: 0.4;
47
+ cursor: not-allowed;
48
+ }
49
+
50
+ .tab:focus-visible {
51
+ outline: var(--bds-outline_default);
52
+ outline-offset: var(--bds-outline_offset);
53
+ border-radius: var(--bds-border_radius--xs);
54
+ }
55
+
56
+ @media (hover: hover) and (pointer: fine) {
57
+ .tab:not(:disabled, [aria-selected='true']):hover {
58
+ --tab_color: var(--bds-color_interactive_on-bg);
59
+ }
60
+ }
61
+ `;
62
+
63
+ static properties = {
64
+ selected: { type: Boolean, reflect: true },
65
+ disabled: { type: Boolean, reflect: true },
66
+ };
67
+
68
+ declare selected: boolean;
69
+ declare disabled: boolean;
70
+
71
+ constructor() {
72
+ super();
73
+ this.selected = false;
74
+ this.disabled = false;
75
+ }
76
+
77
+ /** Called by `bds-tabs` to focus the inner button for keyboard navigation. */
78
+ focusButton() {
79
+ this.shadowRoot?.querySelector<HTMLButtonElement>('button')?.focus();
80
+ }
81
+
82
+ private _handleClick() {
83
+ if (this.disabled) return;
84
+ this.dispatchEvent(new CustomEvent('bds-tab-select', { bubbles: true, composed: true }));
85
+ }
86
+
87
+ render() {
88
+ return html`
89
+ <button
90
+ type="button"
91
+ class="tab"
92
+ role="tab"
93
+ aria-selected=${this.selected}
94
+ tabindex=${this.selected ? 0 : -1}
95
+ ?disabled=${this.disabled}
96
+ @click=${this._handleClick}
97
+ >
98
+ <slot></slot>
99
+ </button>
100
+ `;
101
+ }
102
+ }
103
+
104
+ customElements.define('bds-tab', BdsTab);
105
+
106
+ declare global {
107
+ interface HTMLElementTagNameMap {
108
+ 'bds-tab': BdsTab;
109
+ }
110
+ }
@@ -0,0 +1,120 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsTabs } from './bds-tabs';
3
+ import { BdsTab } from './bds-tab';
4
+ import { BdsTabPanel } from './bds-tab-panel';
5
+
6
+ const html = `
7
+ <bds-tabs>
8
+ <bds-tab slot="tab">Tab 1</bds-tab>
9
+ <bds-tab slot="tab">Tab 2</bds-tab>
10
+ <bds-tab slot="tab" disabled>Tab 3</bds-tab>
11
+ <bds-tab-panel slot="panel">Panel 1</bds-tab-panel>
12
+ <bds-tab-panel slot="panel">Panel 2</bds-tab-panel>
13
+ <bds-tab-panel slot="panel">Panel 3</bds-tab-panel>
14
+ </bds-tabs>
15
+ `;
16
+
17
+ describe('bds-tabs', () => {
18
+ it('is registered as a custom element', () => {
19
+ expect(customElements.get('bds-tabs')).toBe(BdsTabs);
20
+ });
21
+
22
+ it('renders a tablist div with role="tablist"', async () => {
23
+ const el = await fixture(html);
24
+ const tablist = el.shadowRoot!.querySelector('[role="tablist"]');
25
+ expect(tablist).not.toBeNull();
26
+ cleanup(el);
27
+ });
28
+
29
+ it('selects the first tab by default', async () => {
30
+ const el = await fixture(html);
31
+ // Wait for deferred _applyState
32
+ await Promise.resolve();
33
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
34
+ expect(tabs[0].selected).toBe(true);
35
+ expect(tabs[1].selected).toBe(false);
36
+ cleanup(el);
37
+ });
38
+
39
+ it('first panel is visible, others are hidden by default', async () => {
40
+ const el = await fixture(html);
41
+ await Promise.resolve();
42
+ const panels = [...el.querySelectorAll<BdsTabPanel>('bds-tab-panel')];
43
+ expect(panels[0].hasAttribute('hidden')).toBe(false);
44
+ expect(panels[1].hasAttribute('hidden')).toBe(true);
45
+ cleanup(el);
46
+ });
47
+
48
+ it('switches to tab on bds-tab-select event', async () => {
49
+ const el = await fixture(html);
50
+ await Promise.resolve();
51
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
52
+ const panels = [...el.querySelectorAll<BdsTabPanel>('bds-tab-panel')];
53
+
54
+ tabs[1].dispatchEvent(new CustomEvent('bds-tab-select', { bubbles: true, composed: true }));
55
+
56
+ expect(tabs[0].selected).toBe(false);
57
+ expect(tabs[1].selected).toBe(true);
58
+ expect(panels[0].hasAttribute('hidden')).toBe(true);
59
+ expect(panels[1].hasAttribute('hidden')).toBe(false);
60
+ cleanup(el);
61
+ });
62
+
63
+ it('does not switch to a disabled tab on bds-tab-select', async () => {
64
+ const el = await fixture(html);
65
+ await Promise.resolve();
66
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
67
+ // Ensure the tab is marked disabled (Lit property may not reflect from HTML attribute in jsdom)
68
+ tabs[2].disabled = true;
69
+
70
+ tabs[2].dispatchEvent(new CustomEvent('bds-tab-select', { bubbles: true, composed: true }));
71
+
72
+ expect(tabs[0].selected).toBe(true);
73
+ expect(tabs[2].selected).toBe(false);
74
+ cleanup(el);
75
+ });
76
+
77
+ it('ArrowRight moves to next enabled tab', async () => {
78
+ const el = await fixture(html);
79
+ await Promise.resolve();
80
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
81
+ await Promise.resolve();
82
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
83
+ expect(tabs[1].selected).toBe(true);
84
+ cleanup(el);
85
+ });
86
+
87
+ it('ArrowLeft wraps around to last enabled tab', async () => {
88
+ const el = await fixture(html);
89
+ await Promise.resolve();
90
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
91
+ tabs[2].disabled = true; // ensure disabled is set (Lit property may not reflect from HTML attribute in jsdom)
92
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }));
93
+ // disabled tab is skipped; enabled tabs are 0 and 1 → wraps to last enabled (index 1)
94
+ expect(tabs[1].selected).toBe(true);
95
+ cleanup(el);
96
+ });
97
+
98
+ it('Home moves to the first enabled tab', async () => {
99
+ const el = await fixture(html);
100
+ await Promise.resolve();
101
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
102
+ await Promise.all(tabs.map(t => t.updateComplete));
103
+ // Navigate to tab 1 first
104
+ tabs[1].dispatchEvent(new CustomEvent('bds-tab-select', { bubbles: true, composed: true }));
105
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
106
+ expect(tabs[0].selected).toBe(true);
107
+ cleanup(el);
108
+ });
109
+
110
+ it('End moves to the last enabled tab', async () => {
111
+ const el = await fixture(html);
112
+ await Promise.resolve();
113
+ const tabs = [...el.querySelectorAll<BdsTab>('bds-tab')];
114
+ tabs[2].disabled = true; // ensure disabled is set
115
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
116
+ // Last enabled tab is index 1 (tab 3 is disabled)
117
+ expect(tabs[1].selected).toBe(true);
118
+ cleanup(el);
119
+ });
120
+ });
@@ -0,0 +1,152 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import type { BdsTab } from './bds-tab';
3
+ import type { BdsTabPanel } from './bds-tab-panel';
4
+
5
+ /**
6
+ * `<bds-tabs>` — framework-agnostic Tabs custom element.
7
+ *
8
+ * Contains `<bds-tab>` and `<bds-tab-panel>` children. Tabs and panels are
9
+ * matched by DOM order: the first tab corresponds to the first panel, etc.
10
+ *
11
+ * Use `slot="tab"` on `<bds-tab>` elements and `slot="panel"` on
12
+ * `<bds-tab-panel>` elements.
13
+ *
14
+ * Keyboard navigation: ArrowLeft/Right moves between tabs, Home/End jumps to
15
+ * first/last enabled tab.
16
+ *
17
+ * @example
18
+ * <bds-tabs>
19
+ * <bds-tab slot="tab">Overview</bds-tab>
20
+ * <bds-tab slot="tab">Details</bds-tab>
21
+ * <bds-tab slot="tab" disabled>Disabled</bds-tab>
22
+ * <bds-tab-panel slot="panel">Overview content</bds-tab-panel>
23
+ * <bds-tab-panel slot="panel">Details content</bds-tab-panel>
24
+ * <bds-tab-panel slot="panel">Disabled content</bds-tab-panel>
25
+ * </bds-tabs>
26
+ */
27
+ export class BdsTabs extends LitElement {
28
+ static styles = css`
29
+ :host {
30
+ display: flex;
31
+ flex-direction: column;
32
+ }
33
+
34
+ .tablist {
35
+ display: flex;
36
+ border-block-end: 1px solid var(--bds-color_bg--subtle);
37
+ gap: var(--bds-space_xxs);
38
+ }
39
+ `;
40
+
41
+ private _activeIndex = 0;
42
+ private _mo: MutationObserver | null = null;
43
+
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ this.addEventListener('bds-tab-select', this._handleTabSelect);
47
+ this.addEventListener('keydown', this._handleKeydown);
48
+ this._mo = new MutationObserver(() => { this._applyState(); });
49
+ this._mo.observe(this, { childList: true, subtree: false });
50
+ // Defer initial state so children have been upgraded
51
+ Promise.resolve().then(() => this._applyState());
52
+ }
53
+
54
+ disconnectedCallback() {
55
+ super.disconnectedCallback();
56
+ this.removeEventListener('bds-tab-select', this._handleTabSelect);
57
+ this.removeEventListener('keydown', this._handleKeydown);
58
+ this._mo?.disconnect();
59
+ this._mo = null;
60
+ }
61
+
62
+ private get _tabs(): BdsTab[] {
63
+ return [...this.querySelectorAll<BdsTab>('bds-tab')];
64
+ }
65
+
66
+ private get _panels(): BdsTabPanel[] {
67
+ return [...this.querySelectorAll<BdsTabPanel>('bds-tab-panel')];
68
+ }
69
+
70
+ private get _enabledIndexes(): number[] {
71
+ return this._tabs
72
+ .map((tab, i) => ({ tab, i }))
73
+ .filter(({ tab }) => !tab.disabled)
74
+ .map(({ i }) => i);
75
+ }
76
+
77
+ private _applyState() {
78
+ const tabs = this._tabs;
79
+ const panels = this._panels;
80
+
81
+ // Clamp active index if tabs were removed
82
+ if (this._activeIndex >= tabs.length) {
83
+ this._activeIndex = Math.max(0, tabs.length - 1);
84
+ }
85
+
86
+ tabs.forEach((tab, i) => {
87
+ tab.selected = (i === this._activeIndex);
88
+ });
89
+
90
+ panels.forEach((panel, i) => {
91
+ if (i === this._activeIndex) {
92
+ panel.removeAttribute('hidden');
93
+ } else {
94
+ panel.setAttribute('hidden', '');
95
+ }
96
+ });
97
+ }
98
+
99
+ private _handleTabSelect = (e: Event) => {
100
+ const tab = e.target as BdsTab;
101
+ const index = this._tabs.indexOf(tab);
102
+ if (index !== -1 && !tab.disabled) {
103
+ this._activeIndex = index;
104
+ this._applyState();
105
+ }
106
+ };
107
+
108
+ private _handleKeydown = (e: KeyboardEvent) => {
109
+ const enabled = this._enabledIndexes;
110
+ if (!enabled.length) return;
111
+
112
+ const pos = enabled.indexOf(this._activeIndex);
113
+
114
+ let next = -1;
115
+ if (e.key === 'ArrowRight') {
116
+ e.preventDefault();
117
+ next = enabled[(pos + 1) % enabled.length];
118
+ } else if (e.key === 'ArrowLeft') {
119
+ e.preventDefault();
120
+ next = enabled[(pos - 1 + enabled.length) % enabled.length];
121
+ } else if (e.key === 'Home') {
122
+ e.preventDefault();
123
+ next = enabled[0];
124
+ } else if (e.key === 'End') {
125
+ e.preventDefault();
126
+ next = enabled[enabled.length - 1];
127
+ }
128
+
129
+ if (next !== -1) {
130
+ this._activeIndex = next;
131
+ this._applyState();
132
+ this._tabs[next]?.focusButton?.();
133
+ }
134
+ };
135
+
136
+ render() {
137
+ return html`
138
+ <div class="tablist" role="tablist">
139
+ <slot name="tab"></slot>
140
+ </div>
141
+ <slot name="panel"></slot>
142
+ `;
143
+ }
144
+ }
145
+
146
+ customElements.define('bds-tabs', BdsTabs);
147
+
148
+ declare global {
149
+ interface HTMLElementTagNameMap {
150
+ 'bds-tabs': BdsTabs;
151
+ }
152
+ }
@@ -0,0 +1,49 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsTooltip } from './bds-tooltip';
3
+
4
+ describe('bds-tooltip', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-tooltip')).toBe(BdsTooltip);
7
+ });
8
+
9
+ it('defaults to placement="top"', async () => {
10
+ const el = await fixture('<bds-tooltip content="Tip"><button>?</button></bds-tooltip>');
11
+ expect(el.getAttribute('placement')).toBe('top');
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders a tooltip span with role="tooltip"', async () => {
16
+ const el = await fixture('<bds-tooltip content="Helpful text"><button>?</button></bds-tooltip>');
17
+ const tooltip = el.shadowRoot!.querySelector('[role="tooltip"]');
18
+ expect(tooltip).not.toBeNull();
19
+ cleanup(el);
20
+ });
21
+
22
+ it('renders the content text in the tooltip', async () => {
23
+ const el = await fixture('<bds-tooltip content="Helpful text"><button>?</button></bds-tooltip>');
24
+ const tooltip = el.shadowRoot!.querySelector('[role="tooltip"]');
25
+ expect(tooltip!.textContent?.trim()).toBe('Helpful text');
26
+ cleanup(el);
27
+ });
28
+
29
+ it('applies placement class to the tooltip', async () => {
30
+ const el = await fixture('<bds-tooltip content="Tip" placement="bottom"><button>?</button></bds-tooltip>');
31
+ const tooltip = el.shadowRoot!.querySelector('.tooltip');
32
+ expect(tooltip!.classList.contains('--placement_bottom')).toBe(true);
33
+ cleanup(el);
34
+ });
35
+
36
+ it('reflects placement property updates', async () => {
37
+ const el = await fixture('<bds-tooltip content="Tip"><button>?</button></bds-tooltip>') as BdsTooltip;
38
+ el.placement = 'right';
39
+ await el.updateComplete;
40
+ expect(el.getAttribute('placement')).toBe('right');
41
+ cleanup(el);
42
+ });
43
+
44
+ it('renders slotted trigger content', async () => {
45
+ const el = await fixture('<bds-tooltip content="Tip"><button type="button">Help</button></bds-tooltip>');
46
+ expect(el.querySelector('button')!.textContent).toBe('Help');
47
+ cleanup(el);
48
+ });
49
+ });
@@ -0,0 +1,127 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
4
+
5
+ /**
6
+ * `<bds-tooltip>` — framework-agnostic Tooltip custom element.
7
+ *
8
+ * Shows a tooltip panel on hover and keyboard focus using CSS-only visibility.
9
+ * The tooltip position is controlled via the `placement` attribute.
10
+ *
11
+ * Attributes:
12
+ * content — text to display in the tooltip
13
+ * placement — "top" (default) | "bottom" | "left" | "right"
14
+ *
15
+ * Slots:
16
+ * (default) — the trigger element (button, icon, etc.)
17
+ *
18
+ * Accessibility:
19
+ * The `aria-describedby` relationship between trigger and tooltip panel
20
+ * cannot cross shadow DOM boundaries; screen readers will announce the tooltip
21
+ * via role="tooltip" proximity when it becomes visible.
22
+ *
23
+ * @example
24
+ * <bds-tooltip content="More information" placement="bottom">
25
+ * <button type="button">?</button>
26
+ * </bds-tooltip>
27
+ */
28
+ export class BdsTooltip extends LitElement {
29
+ static styles = css`
30
+ :host {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ position: relative;
34
+ }
35
+
36
+ .tooltip {
37
+ position: absolute;
38
+ z-index: var(--bds-z-index_popover, 200);
39
+ padding: var(--bds-space_xxs) var(--bds-space_xs);
40
+ border-radius: var(--bds-border_radius--xs);
41
+ background-color: var(--tooltip_bg, var(--bds-color_on-bg));
42
+ color: var(--tooltip_text, var(--bds-color_bg));
43
+ font-size: var(--bds-font_size--body--s);
44
+ line-height: var(--bds-font_line-height--body);
45
+ white-space: nowrap;
46
+ pointer-events: none;
47
+
48
+ /* Hidden by default */
49
+ visibility: hidden;
50
+ opacity: 0;
51
+ transition: opacity var(--bds-animation_transition-duration) var(--bds-animation_easing);
52
+ }
53
+
54
+ /* Show on hover and keyboard focus */
55
+ :host(:hover) .tooltip,
56
+ :host(:focus-within) .tooltip {
57
+ visibility: visible;
58
+ opacity: 1;
59
+ }
60
+
61
+ /* Placement: top (default) */
62
+ .tooltip.--placement_top {
63
+ inset-block-end: calc(100% + var(--bds-space_xs));
64
+ inset-inline-start: 50%;
65
+ transform: translateX(-50%);
66
+ }
67
+
68
+ /* Placement: bottom */
69
+ .tooltip.--placement_bottom {
70
+ inset-block-start: calc(100% + var(--bds-space_xs));
71
+ inset-inline-start: 50%;
72
+ transform: translateX(-50%);
73
+ }
74
+
75
+ /* Placement: left */
76
+ .tooltip.--placement_left {
77
+ inset-inline-end: calc(100% + var(--bds-space_xs));
78
+ inset-block-start: 50%;
79
+ transform: translateY(-50%);
80
+ }
81
+
82
+ /* Placement: right */
83
+ .tooltip.--placement_right {
84
+ inset-inline-start: calc(100% + var(--bds-space_xs));
85
+ inset-block-start: 50%;
86
+ transform: translateY(-50%);
87
+ }
88
+
89
+ @media (prefers-reduced-motion: reduce) {
90
+ .tooltip {
91
+ transition: none;
92
+ }
93
+ }
94
+ `;
95
+
96
+ static properties = {
97
+ content: { type: String },
98
+ placement: { type: String, reflect: true },
99
+ };
100
+
101
+ declare content: string;
102
+ declare placement: TooltipPlacement;
103
+
104
+ constructor() {
105
+ super();
106
+ this.content = '';
107
+ this.placement = 'top';
108
+ }
109
+
110
+ render() {
111
+ return html`
112
+ <slot></slot>
113
+ <span
114
+ class="tooltip --placement_${this.placement}"
115
+ role="tooltip"
116
+ >${this.content}</span>
117
+ `;
118
+ }
119
+ }
120
+
121
+ customElements.define('bds-tooltip', BdsTooltip);
122
+
123
+ declare global {
124
+ interface HTMLElementTagNameMap {
125
+ 'bds-tooltip': BdsTooltip;
126
+ }
127
+ }
@@ -0,0 +1,63 @@
1
+ import { fixture, cleanup } from '../../test/helpers';
2
+ import { BdsCheckbox } from './bds-checkbox';
3
+
4
+ describe('bds-checkbox', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-checkbox')).toBe(BdsCheckbox);
7
+ });
8
+
9
+ it('renders a checkbox input in shadow DOM', async () => {
10
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
11
+ expect(el.shadowRoot!.querySelector('input[type="checkbox"]')).not.toBeNull();
12
+ cleanup(el);
13
+ });
14
+
15
+ it('is not checked by default', async () => {
16
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
17
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
18
+ expect(input.checked).toBe(false);
19
+ cleanup(el);
20
+ });
21
+
22
+ it('is checked when checked attribute is set', async () => {
23
+ const el = await fixture('<bds-checkbox name="agree" checked>Accept</bds-checkbox>');
24
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
25
+ expect(input.checked).toBe(true);
26
+ cleanup(el);
27
+ });
28
+
29
+ it('input is disabled when disabled attribute is set', async () => {
30
+ const el = await fixture('<bds-checkbox name="agree" disabled>Accept</bds-checkbox>');
31
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
32
+ expect(input.disabled).toBe(true);
33
+ cleanup(el);
34
+ });
35
+
36
+ it('renders a label linked to the input', async () => {
37
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
38
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
39
+ const label = el.shadowRoot!.querySelector('label')!;
40
+ expect(input.id).toBeTruthy();
41
+ expect(label.getAttribute('for')).toBe(input.id);
42
+ cleanup(el);
43
+ });
44
+
45
+ it('renders slotted label text', async () => {
46
+ const el = await fixture('<bds-checkbox name="agree">Accept terms</bds-checkbox>');
47
+ expect(el.textContent?.trim()).toBe('Accept terms');
48
+ cleanup(el);
49
+ });
50
+
51
+ it('updates checked property on change', async () => {
52
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>') as BdsCheckbox;
53
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
54
+ input.checked = true;
55
+ input.dispatchEvent(new Event('change', { bubbles: true }));
56
+ expect(el.checked).toBe(true);
57
+ cleanup(el);
58
+ });
59
+
60
+ it('is form-associated', () => {
61
+ expect(BdsCheckbox.formAssociated).toBe(true);
62
+ });
63
+ });