@boostdev/design-system-components 1.1.2 → 1.2.1

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 (213) hide show
  1. package/AGENTS.md +38 -14
  2. package/dist/client.cjs +85 -103
  3. package/dist/client.css +610 -601
  4. package/dist/client.d.cts +13 -16
  5. package/dist/client.d.ts +13 -16
  6. package/dist/client.js +95 -113
  7. package/dist/index.cjs +85 -103
  8. package/dist/index.css +610 -601
  9. package/dist/index.d.cts +13 -16
  10. package/dist/index.d.ts +13 -16
  11. package/dist/index.js +95 -113
  12. package/dist/native/index.cjs +9 -8
  13. package/dist/native/index.d.cts +1 -1
  14. package/dist/native/index.d.ts +1 -1
  15. package/dist/native/index.js +9 -8
  16. package/dist/web-components/{chunk-K5CE6HSB.js → chunk-2FGATTGT.js} +79 -17
  17. package/dist/web-components/{chunk-O4UFI2RX.js → chunk-3REOIRDW.js} +13 -11
  18. package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
  19. package/dist/web-components/{chunk-VM3YHGQ2.js → chunk-AJSXNDAP.js} +1 -2
  20. package/dist/web-components/{chunk-LSC4N56Q.js → chunk-BX3IPVOB.js} +1 -2
  21. package/dist/web-components/{chunk-CAPI25CU.js → chunk-DZRSJGPB.js} +3 -3
  22. package/dist/web-components/{chunk-VSS5ITNE.js → chunk-EBJM3VD4.js} +1 -3
  23. package/dist/web-components/{chunk-O7I63SQX.js → chunk-HHHRF2PS.js} +6 -12
  24. package/dist/web-components/{chunk-OTX4COSV.js → chunk-JUKB3BUP.js} +3 -4
  25. package/dist/web-components/{chunk-IQGLIQSO.js → chunk-N6IMUOY4.js} +2 -5
  26. package/dist/web-components/{chunk-CJ5ZOO42.js → chunk-OCODKRVZ.js} +68 -44
  27. package/dist/web-components/{chunk-QDRH46TF.js → chunk-QLZ2362S.js} +1 -3
  28. package/dist/web-components/{chunk-IK3PTFZ4.js → chunk-T6AETZRP.js} +1 -3
  29. package/dist/web-components/{chunk-4BX6AWXX.js → chunk-X3FKVHLK.js} +2 -4
  30. package/dist/web-components/{chunk-A67LCT7C.js → chunk-XNA6WTXG.js} +3 -4
  31. package/dist/web-components/{chunk-XIENXR7H.js → chunk-YRXCVKHV.js} +1 -3
  32. package/dist/web-components/{chunk-6T3RKUKG.js → chunk-ZTC6GRP7.js} +1 -2
  33. package/dist/web-components/globals.js +1 -1
  34. package/dist/web-components/index.d.ts +571 -1
  35. package/dist/web-components/index.js +1515 -18
  36. package/dist/web-components/interaction/bds-accordion.js +1 -1
  37. package/dist/web-components/interaction/bds-button.d.ts +2 -2
  38. package/dist/web-components/interaction/bds-button.js +1 -1
  39. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  40. package/dist/web-components/interaction/bds-dialog.js +1 -1
  41. package/dist/web-components/interaction/bds-drawer.js +1 -1
  42. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  43. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  44. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  45. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  46. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +20 -3
  47. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  48. package/dist/web-components/interaction/form/bds-select.js +1 -1
  49. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  50. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  51. package/dist/web-components/ui/bds-alert.js +1 -1
  52. package/dist/web-components/ui/bds-card.js +1 -1
  53. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  54. package/package.json +2 -2
  55. package/src/components/interaction/Button/Button.module.css +12 -11
  56. package/src/components/interaction/Button/Button.native.tsx +10 -9
  57. package/src/components/interaction/Button/Button.tsx +1 -1
  58. package/src/components/interaction/Command/Command.module.css +3 -7
  59. package/src/components/interaction/Dialog/Dialog.module.css +7 -5
  60. package/src/components/interaction/Drawer/Drawer.module.css +48 -25
  61. package/src/components/interaction/Drawer/Drawer.tsx +21 -17
  62. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -3
  63. package/src/components/interaction/Popover/Popover.module.css +1 -3
  64. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -3
  65. package/src/components/interaction/form/Combobox/Combobox.module.css +2 -6
  66. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -3
  67. package/src/components/interaction/form/NumberInput/NumberInput.module.css +16 -4
  68. package/src/components/interaction/form/Radio/Radio.module.css +6 -16
  69. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +74 -25
  70. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +65 -36
  71. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +127 -56
  72. package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +89 -72
  73. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +40 -62
  74. package/src/components/interaction/form/SegmentedControl/index.ts +1 -1
  75. package/src/components/interaction/form/Select/Select.module.css +2 -4
  76. package/src/components/interaction/form/Switch/Switch.module.css +5 -7
  77. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +9 -9
  78. package/src/components/interaction/form/Switch/Switch.native.tsx +2 -2
  79. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -3
  80. package/src/components/interaction/form/atoms/InputContainer.stories.tsx +64 -0
  81. package/src/components/interaction/form/atoms/Label.stories.tsx +33 -0
  82. package/src/components/interaction/form/atoms/Message.stories.tsx +33 -0
  83. package/src/components/layout/Card/Card.module.css +1 -3
  84. package/src/components/ui/Accordion/Accordion.module.css +1 -2
  85. package/src/components/ui/Alert/Alert.module.css +1 -2
  86. package/src/components/ui/Calendar/Calendar.module.css +2 -5
  87. package/src/components/ui/Carousel/Carousel.module.css +1 -3
  88. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -2
  89. package/src/components/ui/Pagination/Pagination.module.css +1 -2
  90. package/src/components/ui/SkipLink/SkipLink.module.css +1 -2
  91. package/src/components/ui/Table/Table.module.css +1 -2
  92. package/src/css/bdc.css +16 -11
  93. package/src/index.ts +1 -1
  94. package/src/stories/Introduction.mdx +2 -1
  95. package/src/web-components/globals.ts +3 -3
  96. package/src/web-components/index.ts +12 -0
  97. package/src/web-components/interaction/BdsAccordion.mdx +103 -0
  98. package/src/web-components/interaction/BdsAccordion.stories.tsx +94 -0
  99. package/src/web-components/interaction/BdsCollapsible.mdx +116 -0
  100. package/src/web-components/interaction/BdsCollapsible.stories.tsx +68 -0
  101. package/src/web-components/interaction/BdsDialog.mdx +110 -0
  102. package/src/web-components/interaction/BdsDialog.stories.tsx +157 -0
  103. package/src/web-components/interaction/BdsDrawer.mdx +108 -0
  104. package/src/web-components/interaction/BdsDrawer.stories.tsx +196 -0
  105. package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
  106. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
  107. package/src/web-components/interaction/BdsSkipLink.mdx +90 -0
  108. package/src/web-components/interaction/BdsSkipLink.stories.tsx +68 -0
  109. package/src/web-components/interaction/BdsTabs.mdx +117 -0
  110. package/src/web-components/interaction/BdsTabs.stories.tsx +94 -0
  111. package/src/web-components/interaction/BdsTooltip.mdx +104 -0
  112. package/src/web-components/interaction/BdsTooltip.stories.tsx +87 -0
  113. package/src/web-components/interaction/bds-accordion.ts +1 -2
  114. package/src/web-components/interaction/bds-button.ts +15 -13
  115. package/src/web-components/interaction/bds-collapsible.ts +1 -0
  116. package/src/web-components/interaction/bds-dialog.ts +2 -5
  117. package/src/web-components/interaction/bds-drawer.ts +70 -44
  118. package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
  119. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
  120. package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
  121. package/src/web-components/interaction/form/BdsCheckbox.mdx +76 -0
  122. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +72 -0
  123. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
  124. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
  125. package/src/web-components/interaction/form/BdsCombobox.mdx +71 -0
  126. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +93 -0
  127. package/src/web-components/interaction/form/BdsFileInput.mdx +79 -0
  128. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +69 -0
  129. package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
  130. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
  131. package/src/web-components/interaction/form/BdsNumberInput.mdx +70 -0
  132. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +68 -0
  133. package/src/web-components/interaction/form/BdsRadio.mdx +72 -0
  134. package/src/web-components/interaction/form/BdsRadio.stories.tsx +58 -0
  135. package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
  136. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
  137. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +95 -0
  138. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +102 -0
  139. package/src/web-components/interaction/form/BdsSelect.mdx +76 -0
  140. package/src/web-components/interaction/form/BdsSelect.stories.tsx +84 -0
  141. package/src/web-components/interaction/form/BdsSlider.mdx +77 -0
  142. package/src/web-components/interaction/form/BdsSlider.stories.tsx +68 -0
  143. package/src/web-components/interaction/form/BdsSwitch.mdx +72 -0
  144. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +58 -0
  145. package/src/web-components/interaction/form/BdsTextarea.mdx +73 -0
  146. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +70 -0
  147. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
  148. package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
  149. package/src/web-components/interaction/form/bds-checkbox.ts +1 -3
  150. package/src/web-components/interaction/form/bds-combobox.ts +1 -3
  151. package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
  152. package/src/web-components/interaction/form/bds-form-input.ts +268 -0
  153. package/src/web-components/interaction/form/bds-number-input.ts +3 -4
  154. package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
  155. package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
  156. package/src/web-components/interaction/form/bds-radio.ts +6 -12
  157. package/src/web-components/interaction/form/bds-segmented-control.ts +96 -20
  158. package/src/web-components/interaction/form/bds-select.ts +2 -4
  159. package/src/web-components/interaction/form/bds-switch.ts +3 -4
  160. package/src/web-components/interaction/form/bds-textarea.ts +1 -3
  161. package/src/web-components/ui/BdsAvatar.mdx +76 -0
  162. package/src/web-components/ui/BdsAvatar.stories.tsx +48 -0
  163. package/src/web-components/ui/BdsBreadcrumb.mdx +66 -0
  164. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +49 -0
  165. package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
  166. package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
  167. package/src/web-components/ui/BdsCalendar.mdx +93 -0
  168. package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
  169. package/src/web-components/ui/BdsCard.mdx +88 -0
  170. package/src/web-components/ui/BdsCard.stories.tsx +67 -0
  171. package/src/web-components/ui/BdsCarousel.mdx +83 -0
  172. package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
  173. package/src/web-components/ui/BdsDescriptionList.mdx +64 -0
  174. package/src/web-components/ui/BdsDescriptionList.stories.tsx +64 -0
  175. package/src/web-components/ui/BdsIconWrapper.mdx +70 -0
  176. package/src/web-components/ui/BdsIconWrapper.stories.tsx +81 -0
  177. package/src/web-components/ui/BdsLink.mdx +79 -0
  178. package/src/web-components/ui/BdsLink.stories.tsx +63 -0
  179. package/src/web-components/ui/BdsLoading.mdx +58 -0
  180. package/src/web-components/ui/BdsLoading.stories.tsx +35 -0
  181. package/src/web-components/ui/BdsNotificationBanner.mdx +85 -0
  182. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +84 -0
  183. package/src/web-components/ui/BdsPagination.mdx +74 -0
  184. package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
  185. package/src/web-components/ui/BdsProgress.mdx +70 -0
  186. package/src/web-components/ui/BdsProgress.stories.tsx +96 -0
  187. package/src/web-components/ui/BdsProgressCircle.mdx +70 -0
  188. package/src/web-components/ui/BdsProgressCircle.stories.tsx +57 -0
  189. package/src/web-components/ui/BdsRating.mdx +61 -0
  190. package/src/web-components/ui/BdsRating.stories.tsx +45 -0
  191. package/src/web-components/ui/BdsSectionHeader.mdx +72 -0
  192. package/src/web-components/ui/BdsSectionHeader.stories.tsx +54 -0
  193. package/src/web-components/ui/BdsSeparator.mdx +77 -0
  194. package/src/web-components/ui/BdsSeparator.stories.tsx +55 -0
  195. package/src/web-components/ui/BdsSkeleton.mdx +61 -0
  196. package/src/web-components/ui/BdsSkeleton.stories.tsx +50 -0
  197. package/src/web-components/ui/BdsTable.mdx +81 -0
  198. package/src/web-components/ui/BdsTable.stories.tsx +83 -0
  199. package/src/web-components/ui/BdsTypography.mdx +74 -0
  200. package/src/web-components/ui/BdsTypography.stories.tsx +52 -0
  201. package/src/web-components/ui/bds-alert.ts +1 -2
  202. package/src/web-components/ui/bds-button-group.spec.ts +40 -0
  203. package/src/web-components/ui/bds-button-group.ts +78 -0
  204. package/src/web-components/ui/bds-calendar.spec.ts +91 -0
  205. package/src/web-components/ui/bds-calendar.ts +427 -0
  206. package/src/web-components/ui/bds-card.ts +1 -3
  207. package/src/web-components/ui/bds-carousel.spec.ts +64 -0
  208. package/src/web-components/ui/bds-carousel.ts +296 -0
  209. package/src/web-components/ui/bds-notification-banner.ts +1 -2
  210. package/src/web-components/ui/bds-pagination.spec.ts +67 -0
  211. package/src/web-components/ui/bds-pagination.ts +197 -0
  212. package/src/web-components/ui/bds-table.spec.ts +45 -0
  213. package/src/web-components/ui/bds-table.ts +96 -0
@@ -0,0 +1,142 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-radio-group>` — groups related radio buttons with a shared label and name.
5
+ *
6
+ * Renders a `<fieldset>` with a `<legend>`. Propagates the `name` attribute to
7
+ * all slotted `<bds-radio>` elements so radios form a mutually-exclusive set.
8
+ *
9
+ * Attributes:
10
+ * label — visible group label (rendered as `<legend>`)
11
+ * name — shared name forwarded to all slotted `<bds-radio>` elements
12
+ * error — error message shown below items
13
+ * hint — hint text shown below items
14
+ * disabled — disables the entire fieldset
15
+ * required — marks the group as required
16
+ *
17
+ * Slots:
18
+ * (default) — `<bds-radio>` elements
19
+ *
20
+ * @example
21
+ * <bds-radio-group label="Preferred contact" name="contact">
22
+ * <bds-radio value="email">Email</bds-radio>
23
+ * <bds-radio value="phone">Phone</bds-radio>
24
+ * </bds-radio-group>
25
+ */
26
+ export class BdsRadioGroup extends LitElement {
27
+ static styles = css`
28
+ :host {
29
+ display: block;
30
+ }
31
+
32
+ fieldset {
33
+ border: none;
34
+ margin: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ .legend {
39
+ font-size: var(--bds-font_size--body--s);
40
+ font-family: var(--bds-font_family--body);
41
+ font-weight: var(--bds-font_weight--semibold);
42
+ color: var(--bds-color_on-bg);
43
+ margin-block-end: var(--bds-space_xs);
44
+ }
45
+
46
+ .required {
47
+ color: var(--bds-color_error);
48
+ margin-inline-start: var(--bds-space_xxxs);
49
+ }
50
+
51
+ .items {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: var(--bds-space_xs);
55
+ }
56
+
57
+ .message {
58
+ margin-block-start: var(--bds-space_xs);
59
+ font-size: var(--bds-font_size--body--s);
60
+ font-family: var(--bds-font_family--body);
61
+ }
62
+
63
+ .message--error {
64
+ color: var(--bds-color_error);
65
+ }
66
+
67
+ .message--hint {
68
+ color: var(--bds-color_on-bg--subtle);
69
+ }
70
+ `;
71
+
72
+ static properties = {
73
+ label: { type: String },
74
+ name: { type: String, reflect: true },
75
+ error: { type: String },
76
+ hint: { type: String },
77
+ disabled: { type: Boolean, reflect: true },
78
+ required: { type: Boolean, reflect: true },
79
+ };
80
+
81
+ declare label: string;
82
+ declare name: string;
83
+ declare error: string | undefined;
84
+ declare hint: string | undefined;
85
+ declare disabled: boolean;
86
+ declare required: boolean;
87
+
88
+ constructor() {
89
+ super();
90
+ this.label = '';
91
+ this.name = '';
92
+ this.disabled = false;
93
+ this.required = false;
94
+ }
95
+
96
+ private _propagateName = () => {
97
+ if (!this.name) return;
98
+ this.querySelectorAll('bds-radio').forEach(radio => {
99
+ (radio as HTMLElement).setAttribute('name', this.name);
100
+ });
101
+ };
102
+
103
+ connectedCallback() {
104
+ super.connectedCallback();
105
+ // Propagate name after slot content has been parsed
106
+ Promise.resolve().then(this._propagateName);
107
+ }
108
+
109
+ updated(changed: Map<string, unknown>) {
110
+ super.updated(changed);
111
+ if (changed.has('name')) {
112
+ this._propagateName();
113
+ }
114
+ }
115
+
116
+ render() {
117
+ return html`
118
+ <fieldset ?disabled=${this.disabled}>
119
+ <legend class="legend">
120
+ ${this.label}
121
+ ${this.required ? html`<span class="required" aria-hidden="true"> *</span>` : ''}
122
+ </legend>
123
+ <div class="items">
124
+ <slot @slotchange=${this._propagateName}></slot>
125
+ </div>
126
+ ${this.error
127
+ ? html`<p class="message message--error" role="alert">${this.error}</p>`
128
+ : this.hint
129
+ ? html`<p class="message message--hint">${this.hint}</p>`
130
+ : ''}
131
+ </fieldset>
132
+ `;
133
+ }
134
+ }
135
+
136
+ customElements.define('bds-radio-group', BdsRadioGroup);
137
+
138
+ declare global {
139
+ interface HTMLElementTagNameMap {
140
+ 'bds-radio-group': BdsRadioGroup;
141
+ }
142
+ }
@@ -50,10 +50,8 @@ export class BdsRadio extends LitElement {
50
50
  --bdc_color: currentcolor;
51
51
  --bdc-outline_radius: 50%;
52
52
 
53
- outline: var(--bdc-outline_width) solid var(--bdc_color);
54
- outline-offset: var(--bdc-outline_offset);
55
53
  border-radius: 50%;
56
- box-shadow: var(--radio_shadow, var(--bdc-outline_shadow));
54
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--radio_shadow, var(--bdc-outline_shadow));
57
55
  border: none;
58
56
  appearance: none;
59
57
  background-color: var(--radio_color_bg, var(--bds-color_bg));
@@ -63,18 +61,14 @@ export class BdsRadio extends LitElement {
63
61
  }
64
62
 
65
63
  .input:checked {
66
- background-color: var(--radio_color-active, var(--bdc-color_active));
64
+ background: radial-gradient(
65
+ circle,
66
+ var(--radio_color-active, var(--bdc-color_active)) 35%,
67
+ var(--radio_color_bg, var(--bds-color_bg)) 42%
68
+ );
67
69
  --bdc_color: var(--radio_color-active, var(--bdc-color_active));
68
70
  }
69
71
 
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
72
  .input:focus-visible {
79
73
  outline: var(--bds-outline_default);
80
74
  outline-offset: var(--bds-outline_offset);
@@ -1,26 +1,29 @@
1
- import { LitElement, css, html } from 'lit';
1
+ import { LitElement, css, html, nothing } from 'lit';
2
2
 
3
3
  export interface SegmentedOption {
4
4
  value: string;
5
5
  label: string;
6
6
  disabled?: boolean;
7
+ /** When set, renders the option as an <a> link instead of a radio button */
8
+ href?: string;
7
9
  }
8
10
 
9
11
  /**
10
12
  * `<bds-segmented-control>` — form-associated Segmented Control custom element.
11
13
  *
12
- * Renders a group of radio buttons styled as a unified segmented control.
14
+ * Renders a group of radio buttons (or links) styled as a unified segmented control.
13
15
  * Passes a JSON `options` attribute because reactive slot content can't be
14
16
  * easily converted to radio groups.
15
17
  *
16
18
  * Attributes:
17
19
  * name — form field name (also used for the internal radio group)
18
20
  * value — currently selected value
19
- * options — JSON array of `{ value, label, disabled? }` option objects
21
+ * options — JSON array of `{ value, label, disabled?, href? }` option objects
20
22
  * disabled — boolean; disables all options
23
+ * variant — "outline" (inset border on active item, default) | "filled" (sliding thumb)
21
24
  *
22
25
  * Events:
23
- * change — fired when a new segment is selected
26
+ * change — fired when a new segment is selected (radio mode only)
24
27
  *
25
28
  * @example
26
29
  * <bds-segmented-control
@@ -28,6 +31,13 @@ export interface SegmentedOption {
28
31
  * value="list"
29
32
  * options='[{"value":"list","label":"List"},{"value":"grid","label":"Grid"}]'
30
33
  * ></bds-segmented-control>
34
+ *
35
+ * @example Outline variant with links
36
+ * <bds-segmented-control
37
+ * variant="outline"
38
+ * value="home"
39
+ * options='[{"value":"home","label":"Home","href":"/"},{"value":"about","label":"About","href":"/about"}]'
40
+ * ></bds-segmented-control>
31
41
  */
32
42
  export class BdsSegmentedControl extends LitElement {
33
43
  static formAssociated = true;
@@ -56,7 +66,7 @@ export class BdsSegmentedControl extends LitElement {
56
66
  position: absolute;
57
67
  inset-block: 0.25rem;
58
68
  border-radius: calc(var(--bds-border_radius--s) - 2px);
59
- background-color: var(--segmented_thumb-color, var(--bds-color_bg));
69
+ background-color: var(--segmented_thumb-color, var(--bds-color_interactive));
60
70
  box-shadow: var(--bds-shadow_m);
61
71
  transition: inset-inline-start var(--bds-animation_transition-duration) var(--bds-animation_easing),
62
72
  inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
@@ -87,10 +97,11 @@ export class BdsSegmentedControl extends LitElement {
87
97
  transition: color var(--bds-animation_transition-duration) var(--bds-animation_easing);
88
98
  user-select: none;
89
99
  white-space: nowrap;
100
+ text-decoration: none;
90
101
  }
91
102
 
92
103
  .item.--active {
93
- color: var(--segmented_color--active, var(--bds-color_interactive_on-bg));
104
+ color: var(--segmented_color--active, var(--bds-color_on-interactive));
94
105
  }
95
106
 
96
107
  .item.--disabled {
@@ -102,6 +113,44 @@ export class BdsSegmentedControl extends LitElement {
102
113
  outline: var(--bds-outline_default);
103
114
  outline-offset: var(--bds-outline_offset);
104
115
  }
116
+
117
+ /* Outline variant: sliding inset-border indicator (positioned via JS like .thumb) */
118
+ .indicator {
119
+ position: absolute;
120
+ inset-block: 0.25rem;
121
+ border-radius: calc(var(--bds-border_radius--s) - 2px);
122
+ pointer-events: none;
123
+ z-index: 2;
124
+ display: none;
125
+ transition: inset-inline-start var(--bds-animation_transition-duration) var(--bds-animation_easing),
126
+ inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
127
+ }
128
+
129
+ /* Outline variant — no thumb, sliding inset-border indicator, interactive active color */
130
+ :host([variant='outline']) .thumb {
131
+ display: none;
132
+ }
133
+
134
+ :host([variant='outline']) .item {
135
+ background-color: var(--segmented_item-bg, transparent);
136
+ }
137
+
138
+ :host([variant='outline']) .item.--active {
139
+ color: var(--segmented_color--active, var(--bds-color_interactive));
140
+ background-color: var(--segmented_item-bg--active, var(--bds-color_bg));
141
+ }
142
+
143
+ :host([variant='outline']) .indicator {
144
+ display: block;
145
+ box-shadow: inset 0 0 0 2px var(--bds-color_interactive);
146
+ }
147
+
148
+ @media (prefers-reduced-motion: reduce) {
149
+ .thumb,
150
+ .indicator {
151
+ transition: none;
152
+ }
153
+ }
105
154
  `;
106
155
 
107
156
  static properties = {
@@ -109,12 +158,14 @@ export class BdsSegmentedControl extends LitElement {
109
158
  value: { type: String },
110
159
  options: { type: String },
111
160
  disabled: { type: Boolean, reflect: true },
161
+ variant: { type: String, reflect: true },
112
162
  };
113
163
 
114
164
  declare name: string;
115
165
  declare value: string;
116
166
  declare options: string;
117
167
  declare disabled: boolean;
168
+ declare variant: 'outline' | 'filled';
118
169
 
119
170
  private _internals!: ElementInternals;
120
171
  private readonly _uid = Math.random().toString(36).slice(2, 8);
@@ -126,6 +177,7 @@ export class BdsSegmentedControl extends LitElement {
126
177
  this.value = '';
127
178
  this.options = '[]';
128
179
  this.disabled = false;
180
+ this.variant = 'outline';
129
181
  }
130
182
 
131
183
  private get _options(): SegmentedOption[] {
@@ -140,22 +192,35 @@ export class BdsSegmentedControl extends LitElement {
140
192
  super.updated(changed);
141
193
  if (changed.has('value') || changed.has('options')) {
142
194
  this._internals.setFormValue?.(this.value || null);
143
- this._updateThumb();
195
+ if (this.variant === 'filled') this._updateThumb();
196
+ if (this.variant === 'outline') this._updateIndicator();
197
+ }
198
+ if (changed.has('variant')) {
199
+ if (this.variant === 'filled') this._updateThumb();
200
+ if (this.variant === 'outline') this._updateIndicator();
144
201
  }
145
202
  }
146
203
 
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;
204
+ private _positionSlider(el: HTMLElement | null | undefined, items: NodeListOf<HTMLElement>, selectedIndex: number) {
205
+ if (!el || items.length === 0) return;
153
206
  const item = items[selectedIndex];
154
207
  if (!item) return;
155
208
  const controlRect = (item.parentElement as HTMLElement).getBoundingClientRect();
156
209
  const itemRect = item.getBoundingClientRect();
157
- thumb.style.insetInlineStart = `${itemRect.left - controlRect.left}px`;
158
- thumb.style.inlineSize = `${itemRect.width}px`;
210
+ el.style.insetInlineStart = `${itemRect.left - controlRect.left}px`;
211
+ el.style.inlineSize = `${itemRect.width}px`;
212
+ }
213
+
214
+ private _updateThumb() {
215
+ const selectedIndex = Math.max(0, this._options.findIndex(o => o.value === this.value));
216
+ const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.item');
217
+ this._positionSlider(this.shadowRoot?.querySelector<HTMLElement>('.thumb'), items!, selectedIndex);
218
+ }
219
+
220
+ private _updateIndicator() {
221
+ const selectedIndex = Math.max(0, this._options.findIndex(o => o.value === this.value));
222
+ const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.item');
223
+ this._positionSlider(this.shadowRoot?.querySelector<HTMLElement>('.indicator'), items!, selectedIndex);
159
224
  }
160
225
 
161
226
  formResetCallback() {
@@ -174,15 +239,26 @@ export class BdsSegmentedControl extends LitElement {
174
239
  return html`
175
240
  <div class="control" role="group">
176
241
  <span class="thumb" aria-hidden="true"></span>
242
+ <span class="indicator" aria-hidden="true"></span>
177
243
  ${opts.map((opt, i) => {
178
- const id = `bds-seg-${this._uid}-${i}`;
179
244
  const isActive = opt.value === this.value;
180
245
  const isDisabled = this.disabled || opt.disabled;
246
+ const itemClass = `item ${isActive ? '--active' : ''} ${isDisabled ? '--disabled' : ''}`.trim();
247
+
248
+ if (opt.href) {
249
+ return html`
250
+ <a
251
+ class=${itemClass}
252
+ href=${isDisabled ? nothing : opt.href}
253
+ aria-current=${isActive ? 'page' : nothing}
254
+ ?aria-disabled=${isDisabled}
255
+ >${opt.label}</a>
256
+ `;
257
+ }
258
+
259
+ const id = `bds-seg-${this._uid}-${i}`;
181
260
  return html`
182
- <label
183
- class="item ${isActive ? '--active' : ''} ${isDisabled ? '--disabled' : ''}"
184
- for=${id}
185
- >
261
+ <label class=${itemClass} for=${id}>
186
262
  <input
187
263
  id=${id}
188
264
  type="radio"
@@ -58,10 +58,8 @@ export class BdsSelect extends LitElement {
58
58
  --bdc_color: currentcolor;
59
59
  --bdc-outline_radius: var(--bds-border_radius--xs);
60
60
 
61
- outline: var(--bdc-outline_width) solid var(--bdc_color);
62
- outline-offset: var(--bdc-outline_offset);
63
61
  border-radius: var(--select_radius, var(--bdc-outline_radius));
64
- box-shadow: var(--select_shadow, var(--bdc-outline_shadow));
62
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--select_shadow, var(--bdc-outline_shadow));
65
63
  background-color: var(--select_color_bg, var(--bds-color_bg));
66
64
  color: var(--select_color, var(--bds-color_on-bg));
67
65
  appearance: none;
@@ -86,7 +84,7 @@ export class BdsSelect extends LitElement {
86
84
  pointer-events: none;
87
85
  inline-size: 1rem;
88
86
  block-size: 1rem;
89
- color: currentcolor;
87
+ color: var(--select_chevron-color, var(--bds-color_interactive));
90
88
  }
91
89
 
92
90
  .chevron svg {
@@ -48,8 +48,7 @@ export class BdsSwitch extends LitElement {
48
48
  background-color: var(--switch_color_bg, var(--bds-color_bg--subtle));
49
49
  --bdc_color: currentcolor;
50
50
 
51
- outline: var(--bdc-outline_width) solid var(--bdc_color);
52
- outline-offset: var(--bdc-outline_offset);
51
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
53
52
  transition: background-color 0.2s ease;
54
53
  flex-shrink: 0;
55
54
  }
@@ -61,7 +60,7 @@ export class BdsSwitch extends LitElement {
61
60
  inline-size: 1.1rem;
62
61
  block-size: 1.1rem;
63
62
  border-radius: 50%;
64
- background-color: var(--switch_thumb_color, var(--bds-color_bg));
63
+ background-color: var(--switch_thumb_color, var(--bds-color_interactive));
65
64
  transition: inset-inline-start 0.2s ease;
66
65
  }
67
66
 
@@ -74,7 +73,7 @@ export class BdsSwitch extends LitElement {
74
73
  }
75
74
 
76
75
  .input:checked + .track {
77
- background-color: var(--switch_color-active, var(--bdc-color_active));
76
+ background-color: var(--switch_color-active, var(--bdc-color_active--subtle));
78
77
  }
79
78
 
80
79
  .input:checked + .track .thumb {
@@ -44,10 +44,8 @@ export class BdsTextarea extends LitElement {
44
44
  --bdc_color: currentcolor;
45
45
  --bdc-outline_radius: var(--bds-border_radius--xs);
46
46
 
47
- outline: var(--bdc-outline_width) solid var(--bdc_color);
48
- outline-offset: var(--bdc-outline_offset);
49
47
  border-radius: var(--textarea_radius, var(--bdc-outline_radius));
50
- box-shadow: var(--textarea_shadow, var(--bdc-outline_shadow));
48
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--textarea_shadow, var(--bdc-outline_shadow));
51
49
  background-color: var(--textarea_color_bg, var(--bds-color_bg));
52
50
  color: var(--textarea_color, var(--bds-color_on-bg));
53
51
  resize: vertical;
@@ -0,0 +1,76 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsAvatar.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-avatar&gt;
7
+
8
+ Framework-agnostic Avatar custom element. Displays a user photo when `src` is provided, or falls back to initials derived from the `name` attribute.
9
+
10
+ > **Status: experimental** — API may change before stable release.
11
+
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ - As a user profile image in navigation bars, comment threads, and team lists
21
+ - As an initials fallback when a photo is unavailable
22
+
23
+ ## Examples
24
+
25
+ ### All sizes
26
+ <Canvas of={Stories.AllSizes} />
27
+
28
+ ### Default (initials)
29
+ <Canvas of={Stories.Default} />
30
+
31
+ ### With image
32
+ <Canvas of={Stories.WithImage} />
33
+
34
+ ### Small
35
+ <Canvas of={Stories.Small} />
36
+
37
+ ### Large
38
+ <Canvas of={Stories.Large} />
39
+
40
+ ## Props
41
+
42
+ <ArgTypes of={Stories} />
43
+
44
+ ## Attributes
45
+
46
+ | Attribute | Type | Default | Description |
47
+ |-----------|------|---------|-------------|
48
+ | `src` | string | — | Photo URL; when set the image is shown |
49
+ | `alt` | string | `name` or `"User avatar"` | Alt text for the `<img>` element |
50
+ | `name` | string | — | Full name; used to compute up to two initials and as `aria-label` |
51
+ | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Diameter scale |
52
+
53
+ ## CSS Custom Properties
54
+
55
+ | Property | Description |
56
+ |----------|-------------|
57
+ | `--avatar_bg` | Background colour of the fallback initials circle |
58
+ | `--avatar_text` | Text colour of the initials |
59
+
60
+ ## Usage in plain HTML
61
+
62
+ ```html
63
+ <!-- With photo -->
64
+ <bds-avatar src="/photo.jpg" name="Jane Doe"></bds-avatar>
65
+
66
+ <!-- Initials only -->
67
+ <bds-avatar name="Jane Doe" size="large"></bds-avatar>
68
+
69
+ <!-- Small -->
70
+ <bds-avatar name="JD" size="small"></bds-avatar>
71
+ ```
72
+
73
+ ## Accessibility
74
+
75
+ - Without `src`: renders `role="img"` with `aria-label` set to `name` (or "User avatar")
76
+ - With `src`: renders `<img>` with `alt` set to `alt`, `name`, or "User avatar"
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import '../index'; // auto-registers all custom elements
4
+
5
+ function BdsAvatar({
6
+ src,
7
+ alt,
8
+ name,
9
+ size = 'medium',
10
+ }: {
11
+ src?: string;
12
+ alt?: string;
13
+ name?: string;
14
+ size?: 'small' | 'medium' | 'large';
15
+ }) {
16
+ return React.createElement('bds-avatar', { src, alt, name, size });
17
+ }
18
+
19
+ const meta = {
20
+ title: 'Web Components/UI/Avatar',
21
+ component: BdsAvatar,
22
+ tags: ['!stable', 'experimental'],
23
+ parameters: { layout: 'centered' },
24
+ argTypes: {
25
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
26
+ },
27
+ } satisfies Meta<typeof BdsAvatar>;
28
+
29
+ export default meta;
30
+ type Story = StoryObj<typeof meta>;
31
+
32
+ export const Default: Story = { args: { name: 'Jane Doe', size: 'medium' } };
33
+ export const WithImage: Story = {
34
+ args: { src: 'https://i.pravatar.cc/150?img=47', name: 'Jane Doe', size: 'medium' },
35
+ };
36
+ export const Small: Story = { args: { name: 'Jane Doe', size: 'small' } };
37
+ export const Large: Story = { args: { name: 'Jane Doe', size: 'large' } };
38
+ export const InitialsOnly: Story = { args: { name: 'John Smith', size: 'medium' } };
39
+
40
+ export const AllSizes: Story = {
41
+ render: () => (
42
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
43
+ {React.createElement('bds-avatar', { name: 'Alice Brown', size: 'small' })}
44
+ {React.createElement('bds-avatar', { name: 'Alice Brown', size: 'medium' })}
45
+ {React.createElement('bds-avatar', { name: 'Alice Brown', size: 'large' })}
46
+ </div>
47
+ ),
48
+ };
@@ -0,0 +1,66 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsBreadcrumb.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-breadcrumb&gt;
7
+
8
+ Framework-agnostic Breadcrumb custom element. Renders a horizontal navigation trail from a JSON array of `{ label, href? }` items. The last item is treated as the current page and rendered without a link.
9
+
10
+ > **Status: experimental** — API may change before stable release.
11
+
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ - On pages more than one level deep in a hierarchy to help users understand their location
21
+ - Always include a link back to the root/home as the first item
22
+
23
+ ## Examples
24
+
25
+ ### Default
26
+ <Canvas of={Stories.Default} />
27
+
28
+ ### Short (two levels)
29
+ <Canvas of={Stories.Short} />
30
+
31
+ ### Long (five levels)
32
+ <Canvas of={Stories.Long} />
33
+
34
+ ## Props
35
+
36
+ <ArgTypes of={Stories} />
37
+
38
+ ## Attributes
39
+
40
+ | Attribute | Type | Default | Description |
41
+ |-----------|------|---------|-------------|
42
+ | `items` | `Array<{ label: string; href?: string }>` | `[]` | Breadcrumb items as a JSON-serialisable array. The last item is always the current page. |
43
+
44
+ ## Usage in plain HTML
45
+
46
+ ```html
47
+ <bds-breadcrumb
48
+ items='[{"label":"Home","href":"/"},{"label":"Products","href":"/products"},{"label":"Item"}]'>
49
+ </bds-breadcrumb>
50
+ ```
51
+
52
+ Or set the property in JavaScript:
53
+
54
+ ```js
55
+ document.querySelector('bds-breadcrumb').items = [
56
+ { label: 'Home', href: '/' },
57
+ { label: 'Products', href: '/products' },
58
+ { label: 'Item' },
59
+ ];
60
+ ```
61
+
62
+ ## Accessibility
63
+
64
+ - Wrapped in `<nav aria-label="Breadcrumb">` with an `<ol>` list structure
65
+ - The current page item carries `aria-current="page"`
66
+ - Separator characters are `aria-hidden="true"` to avoid screen reader noise