@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,157 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-radio>` — form-associated Radio custom element.
5
+ *
6
+ * Participates in HTML forms via the Form-Associated Custom Elements API.
7
+ * Use multiple `<bds-radio>` elements with the same `name` to form a group.
8
+ *
9
+ * Attributes:
10
+ * name — form field name (groups radio buttons)
11
+ * value — submitted value when selected
12
+ * checked — boolean; whether this option is selected
13
+ * required — boolean
14
+ * disabled — boolean
15
+ *
16
+ * Slots:
17
+ * (default) — label text
18
+ *
19
+ * @example
20
+ * <bds-radio name="size" value="s">Small</bds-radio>
21
+ * <bds-radio name="size" value="m" checked>Medium</bds-radio>
22
+ * <bds-radio name="size" value="l">Large</bds-radio>
23
+ */
24
+ export class BdsRadio extends LitElement {
25
+ static formAssociated = true;
26
+
27
+ static styles = css`
28
+ :host {
29
+ display: inline-flex;
30
+ align-items: flex-start;
31
+ gap: var(--bds-space_xs);
32
+ cursor: pointer;
33
+ font-size: var(--bds-font_size--body);
34
+ font-family: var(--bds-font_family--body);
35
+ color: var(--bds-color_on-bg);
36
+ }
37
+
38
+ :host([disabled]) {
39
+ opacity: 0.5;
40
+ cursor: not-allowed;
41
+ pointer-events: none;
42
+ }
43
+
44
+ .input {
45
+ --inputSize: calc(var(--bds-space_s) * 2);
46
+ margin-block-start: 0.25em;
47
+ inline-size: var(--inputSize);
48
+ block-size: var(--inputSize);
49
+ flex-shrink: 0;
50
+ --bdc_color: currentcolor;
51
+ --bdc-outline_radius: 50%;
52
+
53
+ outline: var(--bdc-outline_width) solid var(--bdc_color);
54
+ outline-offset: var(--bdc-outline_offset);
55
+ border-radius: 50%;
56
+ box-shadow: var(--radio_shadow, var(--bdc-outline_shadow));
57
+ border: none;
58
+ appearance: none;
59
+ background-color: var(--radio_color_bg, var(--bds-color_bg));
60
+ cursor: pointer;
61
+ position: relative;
62
+ transition: --bdc_color 0.3s ease, background-color 0.3s ease;
63
+ }
64
+
65
+ .input:checked {
66
+ background-color: var(--radio_color-active, var(--bdc-color_active));
67
+ --bdc_color: var(--radio_color-active, var(--bdc-color_active));
68
+ }
69
+
70
+ .input:checked::after {
71
+ content: '';
72
+ position: absolute;
73
+ inset: 25%;
74
+ border-radius: 50%;
75
+ background-color: var(--radio_color-on-active, var(--bdc-color_on-active));
76
+ }
77
+
78
+ .input:focus-visible {
79
+ outline: var(--bds-outline_default);
80
+ outline-offset: var(--bds-outline_offset);
81
+ }
82
+
83
+ label {
84
+ cursor: pointer;
85
+ flex: 1;
86
+ }
87
+ `;
88
+
89
+ static properties = {
90
+ name: { type: String, reflect: true },
91
+ value: { type: String },
92
+ checked: { type: Boolean, reflect: true },
93
+ required: { type: Boolean, reflect: true },
94
+ disabled: { type: Boolean, reflect: true },
95
+ };
96
+
97
+ declare name: string;
98
+ declare value: string;
99
+ declare checked: boolean;
100
+ declare required: boolean;
101
+ declare disabled: boolean;
102
+
103
+ private _internals!: ElementInternals;
104
+ private readonly _uid = Math.random().toString(36).slice(2, 8);
105
+
106
+ constructor() {
107
+ super();
108
+ this._internals = this.attachInternals();
109
+ this.name = '';
110
+ this.value = '';
111
+ this.checked = false;
112
+ this.required = false;
113
+ this.disabled = false;
114
+ }
115
+
116
+ updated(changed: Map<string, unknown>) {
117
+ super.updated(changed);
118
+ if (changed.has('checked') || changed.has('value')) {
119
+ this._internals.setFormValue?.(this.checked ? this.value : null);
120
+ }
121
+ }
122
+
123
+ formResetCallback() {
124
+ this.checked = false;
125
+ this._internals.setFormValue?.(null);
126
+ }
127
+
128
+ private _handleChange(e: Event) {
129
+ this.checked = (e.target as HTMLInputElement).checked;
130
+ }
131
+
132
+ render() {
133
+ const id = `bds-radio-${this._uid}`;
134
+ return html`
135
+ <input
136
+ id=${id}
137
+ type="radio"
138
+ class="input"
139
+ name=${this.name}
140
+ value=${this.value}
141
+ .checked=${this.checked}
142
+ ?required=${this.required}
143
+ ?disabled=${this.disabled}
144
+ @change=${this._handleChange}
145
+ />
146
+ <label for=${id}><slot></slot></label>
147
+ `;
148
+ }
149
+ }
150
+
151
+ customElements.define('bds-radio', BdsRadio);
152
+
153
+ declare global {
154
+ interface HTMLElementTagNameMap {
155
+ 'bds-radio': BdsRadio;
156
+ }
157
+ }
@@ -0,0 +1,74 @@
1
+ import { fixture, cleanup } from '../../test/helpers';
2
+ import { BdsSegmentedControl } from './bds-segmented-control';
3
+
4
+ const OPTIONS = JSON.stringify([
5
+ { value: 'list', label: 'List' },
6
+ { value: 'grid', label: 'Grid' },
7
+ { value: 'table', label: 'Table', disabled: true },
8
+ ]);
9
+
10
+ describe('bds-segmented-control', () => {
11
+ it('is registered as a custom element', () => {
12
+ expect(customElements.get('bds-segmented-control')).toBe(BdsSegmentedControl);
13
+ });
14
+
15
+ it('renders radio inputs for each option', async () => {
16
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
17
+ const radios = el.shadowRoot!.querySelectorAll('input[type="radio"]');
18
+ expect(radios.length).toBe(3);
19
+ cleanup(el);
20
+ });
21
+
22
+ it('renders a group role wrapper', async () => {
23
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
24
+ expect(el.shadowRoot!.querySelector('[role="group"]')).not.toBeNull();
25
+ cleanup(el);
26
+ });
27
+
28
+ it('marks the selected option as checked', async () => {
29
+ const el = await fixture(`<bds-segmented-control name="view" value="grid" options='${OPTIONS}'></bds-segmented-control>`);
30
+ await el.updateComplete;
31
+ const radios = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="radio"]')];
32
+ const gridRadio = radios.find(r => r.value === 'grid')!;
33
+ expect(gridRadio.checked).toBe(true);
34
+ cleanup(el);
35
+ });
36
+
37
+ it('marks a disabled option as disabled', async () => {
38
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
39
+ const radios = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="radio"]')];
40
+ const tableRadio = radios.find(r => r.value === 'table')!;
41
+ expect(tableRadio.disabled).toBe(true);
42
+ cleanup(el);
43
+ });
44
+
45
+ it('updates value property when a radio is changed', async () => {
46
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`) as BdsSegmentedControl;
47
+ const radios = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="radio"]')];
48
+ const gridRadio = radios.find(r => r.value === 'grid')!;
49
+ gridRadio.checked = true;
50
+ gridRadio.dispatchEvent(new Event('change', { bubbles: true }));
51
+ expect(el.value).toBe('grid');
52
+ cleanup(el);
53
+ });
54
+
55
+ it('fires a change event when an option is selected', async () => {
56
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
57
+ const handler = vi.fn();
58
+ el.addEventListener('change', handler);
59
+ const radios = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="radio"]')];
60
+ radios[0].dispatchEvent(new Event('change', { bubbles: true }));
61
+ expect(handler).toHaveBeenCalledOnce();
62
+ cleanup(el);
63
+ });
64
+
65
+ it('renders a sliding thumb element', async () => {
66
+ const el = await fixture(`<bds-segmented-control name="view" options='${OPTIONS}'></bds-segmented-control>`);
67
+ expect(el.shadowRoot!.querySelector('.thumb')).not.toBeNull();
68
+ cleanup(el);
69
+ });
70
+
71
+ it('is form-associated', () => {
72
+ expect(BdsSegmentedControl.formAssociated).toBe(true);
73
+ });
74
+ });
@@ -0,0 +1,211 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ export interface SegmentedOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ /**
10
+ * `<bds-segmented-control>` — form-associated Segmented Control custom element.
11
+ *
12
+ * Renders a group of radio buttons styled as a unified segmented control.
13
+ * Passes a JSON `options` attribute because reactive slot content can't be
14
+ * easily converted to radio groups.
15
+ *
16
+ * Attributes:
17
+ * name — form field name (also used for the internal radio group)
18
+ * value — currently selected value
19
+ * options — JSON array of `{ value, label, disabled? }` option objects
20
+ * disabled — boolean; disables all options
21
+ *
22
+ * Events:
23
+ * change — fired when a new segment is selected
24
+ *
25
+ * @example
26
+ * <bds-segmented-control
27
+ * name="view"
28
+ * value="list"
29
+ * options='[{"value":"list","label":"List"},{"value":"grid","label":"Grid"}]'
30
+ * ></bds-segmented-control>
31
+ */
32
+ export class BdsSegmentedControl extends LitElement {
33
+ static formAssociated = true;
34
+
35
+ static styles = css`
36
+ :host {
37
+ display: inline-flex;
38
+ }
39
+
40
+ :host([disabled]) {
41
+ opacity: 0.5;
42
+ pointer-events: none;
43
+ }
44
+
45
+ .control {
46
+ display: inline-flex;
47
+ position: relative;
48
+ border-radius: var(--bds-border_radius--s);
49
+ background-color: var(--segmented_color_bg, var(--bds-color_bg--subtle));
50
+ padding: 0.25rem;
51
+ gap: 0;
52
+ }
53
+
54
+ /* Sliding thumb */
55
+ .thumb {
56
+ position: absolute;
57
+ inset-block: 0.25rem;
58
+ border-radius: calc(var(--bds-border_radius--s) - 2px);
59
+ background-color: var(--segmented_thumb-color, var(--bds-color_bg));
60
+ box-shadow: var(--bds-shadow_m);
61
+ transition: inset-inline-start var(--bds-animation_transition-duration) var(--bds-animation_easing),
62
+ inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
63
+ pointer-events: none;
64
+ /* Position set via JS in updated() */
65
+ }
66
+
67
+ .radio {
68
+ position: absolute;
69
+ opacity: 0;
70
+ inline-size: 0;
71
+ block-size: 0;
72
+ margin: 0;
73
+ }
74
+
75
+ .item {
76
+ position: relative;
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ padding: var(--bds-space_xs) var(--bds-space_m);
81
+ font-size: var(--bds-font_size--body--s);
82
+ font-family: var(--bds-font_family--body);
83
+ font-weight: var(--bds-font_weight--semibold);
84
+ color: var(--segmented_color, var(--bds-color_on-bg));
85
+ cursor: pointer;
86
+ border-radius: calc(var(--bds-border_radius--s) - 2px);
87
+ transition: color var(--bds-animation_transition-duration) var(--bds-animation_easing);
88
+ user-select: none;
89
+ white-space: nowrap;
90
+ }
91
+
92
+ .item.--active {
93
+ color: var(--segmented_color--active, var(--bds-color_interactive_on-bg));
94
+ }
95
+
96
+ .item.--disabled {
97
+ opacity: 0.4;
98
+ cursor: not-allowed;
99
+ }
100
+
101
+ .radio:focus-visible + .item-label {
102
+ outline: var(--bds-outline_default);
103
+ outline-offset: var(--bds-outline_offset);
104
+ }
105
+ `;
106
+
107
+ static properties = {
108
+ name: { type: String, reflect: true },
109
+ value: { type: String },
110
+ options: { type: String },
111
+ disabled: { type: Boolean, reflect: true },
112
+ };
113
+
114
+ declare name: string;
115
+ declare value: string;
116
+ declare options: string;
117
+ declare disabled: boolean;
118
+
119
+ private _internals!: ElementInternals;
120
+ private readonly _uid = Math.random().toString(36).slice(2, 8);
121
+
122
+ constructor() {
123
+ super();
124
+ this._internals = this.attachInternals();
125
+ this.name = '';
126
+ this.value = '';
127
+ this.options = '[]';
128
+ this.disabled = false;
129
+ }
130
+
131
+ private get _options(): SegmentedOption[] {
132
+ try {
133
+ return JSON.parse(this.options) as SegmentedOption[];
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
138
+
139
+ updated(changed: Map<string, unknown>) {
140
+ super.updated(changed);
141
+ if (changed.has('value') || changed.has('options')) {
142
+ this._internals.setFormValue?.(this.value || null);
143
+ this._updateThumb();
144
+ }
145
+ }
146
+
147
+ private _updateThumb() {
148
+ const opts = this._options;
149
+ const selectedIndex = Math.max(0, opts.findIndex(o => o.value === this.value));
150
+ const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.item');
151
+ const thumb = this.shadowRoot?.querySelector<HTMLElement>('.thumb');
152
+ if (!items || !thumb || items.length === 0) return;
153
+ const item = items[selectedIndex];
154
+ if (!item) return;
155
+ const controlRect = (item.parentElement as HTMLElement).getBoundingClientRect();
156
+ const itemRect = item.getBoundingClientRect();
157
+ thumb.style.insetInlineStart = `${itemRect.left - controlRect.left}px`;
158
+ thumb.style.inlineSize = `${itemRect.width}px`;
159
+ }
160
+
161
+ formResetCallback() {
162
+ const firstOption = this._options[0];
163
+ this.value = firstOption?.value ?? '';
164
+ this._internals.setFormValue?.(this.value || null);
165
+ }
166
+
167
+ private _handleChange(optionValue: string) {
168
+ this.value = optionValue;
169
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
170
+ }
171
+
172
+ render() {
173
+ const opts = this._options;
174
+ return html`
175
+ <div class="control" role="group">
176
+ <span class="thumb" aria-hidden="true"></span>
177
+ ${opts.map((opt, i) => {
178
+ const id = `bds-seg-${this._uid}-${i}`;
179
+ const isActive = opt.value === this.value;
180
+ const isDisabled = this.disabled || opt.disabled;
181
+ return html`
182
+ <label
183
+ class="item ${isActive ? '--active' : ''} ${isDisabled ? '--disabled' : ''}"
184
+ for=${id}
185
+ >
186
+ <input
187
+ id=${id}
188
+ type="radio"
189
+ class="radio"
190
+ name=${this.name || this._uid}
191
+ value=${opt.value}
192
+ .checked=${isActive}
193
+ ?disabled=${isDisabled}
194
+ @change=${() => this._handleChange(opt.value)}
195
+ />
196
+ ${opt.label}
197
+ </label>
198
+ `;
199
+ })}
200
+ </div>
201
+ `;
202
+ }
203
+ }
204
+
205
+ customElements.define('bds-segmented-control', BdsSegmentedControl);
206
+
207
+ declare global {
208
+ interface HTMLElementTagNameMap {
209
+ 'bds-segmented-control': BdsSegmentedControl;
210
+ }
211
+ }
@@ -0,0 +1,74 @@
1
+ import { fixture, cleanup } from '../../test/helpers';
2
+ import { BdsSelect } from './bds-select';
3
+
4
+ const OPTIONS = JSON.stringify([
5
+ { value: 'nl', label: 'Netherlands' },
6
+ { value: 'be', label: 'Belgium' },
7
+ { value: 'de', label: 'Germany', disabled: true },
8
+ ]);
9
+
10
+ describe('bds-select', () => {
11
+ it('is registered as a custom element', () => {
12
+ expect(customElements.get('bds-select')).toBe(BdsSelect);
13
+ });
14
+
15
+ it('renders a <select> element in shadow DOM', async () => {
16
+ const el = await fixture(`<bds-select name="country" options='${OPTIONS}'></bds-select>`);
17
+ expect(el.shadowRoot!.querySelector('select')).not.toBeNull();
18
+ cleanup(el);
19
+ });
20
+
21
+ it('renders options from the options attribute', async () => {
22
+ const el = await fixture(`<bds-select name="country" options='${OPTIONS}'></bds-select>`);
23
+ const select = el.shadowRoot!.querySelector('select')!;
24
+ const options = [...select.querySelectorAll('option')];
25
+ expect(options.length).toBe(3);
26
+ expect(options[0].value).toBe('nl');
27
+ expect(options[1].value).toBe('be');
28
+ cleanup(el);
29
+ });
30
+
31
+ it('renders a placeholder option when placeholder is set', async () => {
32
+ const el = await fixture(`<bds-select name="country" placeholder="Choose..." options='${OPTIONS}'></bds-select>`);
33
+ const select = el.shadowRoot!.querySelector('select')!;
34
+ const placeholder = select.querySelector<HTMLOptionElement>('option[value=""]');
35
+ expect(placeholder).not.toBeNull();
36
+ expect(placeholder!.textContent?.trim()).toBe('Choose...');
37
+ cleanup(el);
38
+ });
39
+
40
+ it('marks a disabled option as disabled', async () => {
41
+ const el = await fixture(`<bds-select name="country" options='${OPTIONS}'></bds-select>`);
42
+ const select = el.shadowRoot!.querySelector('select')!;
43
+ const deOption = select.querySelector<HTMLOptionElement>('option[value="de"]');
44
+ expect(deOption!.disabled).toBe(true);
45
+ cleanup(el);
46
+ });
47
+
48
+ it('select is disabled when disabled attribute is set', async () => {
49
+ const el = await fixture(`<bds-select name="country" disabled options='${OPTIONS}'></bds-select>`);
50
+ const select = el.shadowRoot!.querySelector<HTMLSelectElement>('select')!;
51
+ expect(select.disabled).toBe(true);
52
+ cleanup(el);
53
+ });
54
+
55
+ it('updates value property on change', async () => {
56
+ const el = await fixture(`<bds-select name="country" options='${OPTIONS}'></bds-select>`) as BdsSelect;
57
+ const select = el.shadowRoot!.querySelector<HTMLSelectElement>('select')!;
58
+ select.value = 'nl';
59
+ select.dispatchEvent(new Event('change', { bubbles: true }));
60
+ expect(el.value).toBe('nl');
61
+ cleanup(el);
62
+ });
63
+
64
+ it('handles invalid JSON options gracefully', async () => {
65
+ const el = await fixture(`<bds-select name="country" options='not-json'></bds-select>`);
66
+ const select = el.shadowRoot!.querySelector('select')!;
67
+ expect([...select.querySelectorAll('option')].length).toBe(0);
68
+ cleanup(el);
69
+ });
70
+
71
+ it('is form-associated', () => {
72
+ expect(BdsSelect.formAssociated).toBe(true);
73
+ });
74
+ });
@@ -0,0 +1,190 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ export interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ /**
10
+ * `<bds-select>` — form-associated Select custom element.
11
+ *
12
+ * Uses a JSON `options` attribute because slotting `<option>` elements into a
13
+ * shadow-DOM `<select>` is not supported cross-browser.
14
+ *
15
+ * Attributes:
16
+ * name — form field name
17
+ * value — currently selected value
18
+ * options — JSON array of `{ value, label, disabled? }` option objects
19
+ * placeholder — placeholder option label (disabled, empty value)
20
+ * required — boolean
21
+ * disabled — boolean
22
+ *
23
+ * Events:
24
+ * change — native change event (forwarded from inner select)
25
+ *
26
+ * @example
27
+ * <bds-select
28
+ * name="country"
29
+ * placeholder="Choose…"
30
+ * options='[{"value":"nl","label":"Netherlands"},{"value":"be","label":"Belgium"}]'
31
+ * ></bds-select>
32
+ */
33
+ export class BdsSelect extends LitElement {
34
+ static formAssociated = true;
35
+
36
+ static styles = css`
37
+ :host {
38
+ display: block;
39
+ }
40
+
41
+ :host([disabled]) {
42
+ opacity: 0.5;
43
+ pointer-events: none;
44
+ }
45
+
46
+ .wrapper {
47
+ position: relative;
48
+ display: block;
49
+ }
50
+
51
+ .select {
52
+ inline-size: 100%;
53
+ font-family: var(--bds-font_family--body);
54
+ font-size: var(--bds-font_size--body);
55
+ padding: var(--bds-space_s);
56
+ padding-inline-end: var(--bds-space_xl);
57
+ border: none;
58
+ --bdc_color: currentcolor;
59
+ --bdc-outline_radius: var(--bds-border_radius--xs);
60
+
61
+ outline: var(--bdc-outline_width) solid var(--bdc_color);
62
+ outline-offset: var(--bdc-outline_offset);
63
+ border-radius: var(--select_radius, var(--bdc-outline_radius));
64
+ box-shadow: var(--select_shadow, var(--bdc-outline_shadow));
65
+ background-color: var(--select_color_bg, var(--bds-color_bg));
66
+ color: var(--select_color, var(--bds-color_on-bg));
67
+ appearance: none;
68
+ cursor: pointer;
69
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing);
70
+ }
71
+
72
+ .select:focus {
73
+ --bdc_color: var(--bdc_color--focus);
74
+ }
75
+
76
+ .select:focus-visible {
77
+ outline: var(--bds-outline_default);
78
+ outline-offset: var(--bds-outline_offset);
79
+ }
80
+
81
+ .chevron {
82
+ position: absolute;
83
+ inset-inline-end: var(--bds-space_s);
84
+ inset-block-start: 50%;
85
+ transform: translateY(-50%);
86
+ pointer-events: none;
87
+ inline-size: 1rem;
88
+ block-size: 1rem;
89
+ color: currentcolor;
90
+ }
91
+
92
+ .chevron svg {
93
+ inline-size: 100%;
94
+ block-size: 100%;
95
+ }
96
+ `;
97
+
98
+ static properties = {
99
+ name: { type: String, reflect: true },
100
+ value: { type: String },
101
+ options: { type: String },
102
+ placeholder: { type: String },
103
+ required: { type: Boolean, reflect: true },
104
+ disabled: { type: Boolean, reflect: true },
105
+ };
106
+
107
+ declare name: string;
108
+ declare value: string;
109
+ declare options: string;
110
+ declare placeholder: string;
111
+ declare required: boolean;
112
+ declare disabled: boolean;
113
+
114
+ private _internals!: ElementInternals;
115
+
116
+ constructor() {
117
+ super();
118
+ this._internals = this.attachInternals();
119
+ this.name = '';
120
+ this.value = '';
121
+ this.options = '[]';
122
+ this.placeholder = '';
123
+ this.required = false;
124
+ this.disabled = false;
125
+ }
126
+
127
+ private get _options(): SelectOption[] {
128
+ try {
129
+ return JSON.parse(this.options) as SelectOption[];
130
+ } catch {
131
+ return [];
132
+ }
133
+ }
134
+
135
+ updated(changed: Map<string, unknown>) {
136
+ super.updated(changed);
137
+ if (changed.has('value') || changed.has('name')) {
138
+ this._internals.setFormValue?.(this.value || null);
139
+ }
140
+ }
141
+
142
+ formResetCallback() {
143
+ this.value = '';
144
+ this._internals.setFormValue?.(null);
145
+ }
146
+
147
+ private _handleChange(e: Event) {
148
+ this.value = (e.target as HTMLSelectElement).value;
149
+ }
150
+
151
+ render() {
152
+ const opts = this._options;
153
+ return html`
154
+ <div class="wrapper">
155
+ <select
156
+ class="select"
157
+ name=${this.name}
158
+ .value=${this.value}
159
+ ?required=${this.required}
160
+ ?disabled=${this.disabled}
161
+ @change=${this._handleChange}
162
+ >
163
+ ${this.placeholder ? html`
164
+ <option value="" disabled ?selected=${!this.value}>${this.placeholder}</option>
165
+ ` : ''}
166
+ ${opts.map(opt => html`
167
+ <option
168
+ value=${opt.value}
169
+ ?disabled=${opt.disabled}
170
+ ?selected=${opt.value === this.value}
171
+ >${opt.label}</option>
172
+ `)}
173
+ </select>
174
+ <span class="chevron" aria-hidden="true">
175
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
176
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
177
+ </svg>
178
+ </span>
179
+ </div>
180
+ `;
181
+ }
182
+ }
183
+
184
+ customElements.define('bds-select', BdsSelect);
185
+
186
+ declare global {
187
+ interface HTMLElementTagNameMap {
188
+ 'bds-select': BdsSelect;
189
+ }
190
+ }