@boostdev/design-system-components 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/AGENTS.md +13 -10
  2. package/dist/client.cjs +77 -99
  3. package/dist/client.css +611 -580
  4. package/dist/client.d.cts +13 -16
  5. package/dist/client.d.ts +13 -16
  6. package/dist/client.js +87 -109
  7. package/dist/index.cjs +77 -99
  8. package/dist/index.css +611 -580
  9. package/dist/index.d.cts +13 -16
  10. package/dist/index.d.ts +13 -16
  11. package/dist/index.js +87 -109
  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-O4UFI2RX.js → chunk-3REOIRDW.js} +13 -11
  17. package/dist/web-components/{chunk-K5CE6HSB.js → chunk-6MH5UWUD.js} +74 -17
  18. package/dist/web-components/{chunk-VM3YHGQ2.js → chunk-AJSXNDAP.js} +1 -2
  19. package/dist/web-components/{chunk-LSC4N56Q.js → chunk-BX3IPVOB.js} +1 -2
  20. package/dist/web-components/{chunk-CJ5ZOO42.js → chunk-DI46Q2EA.js} +50 -1
  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-QDRH46TF.js → chunk-QLZ2362S.js} +1 -3
  27. package/dist/web-components/{chunk-IK3PTFZ4.js → chunk-T6AETZRP.js} +1 -3
  28. package/dist/web-components/{chunk-4BX6AWXX.js → chunk-X3FKVHLK.js} +2 -4
  29. package/dist/web-components/{chunk-A67LCT7C.js → chunk-XNA6WTXG.js} +3 -4
  30. package/dist/web-components/{chunk-XIENXR7H.js → chunk-YRXCVKHV.js} +1 -3
  31. package/dist/web-components/{chunk-6T3RKUKG.js → chunk-ZTC6GRP7.js} +1 -2
  32. package/dist/web-components/globals.js +1 -1
  33. package/dist/web-components/index.js +16 -16
  34. package/dist/web-components/interaction/bds-accordion.js +1 -1
  35. package/dist/web-components/interaction/bds-button.d.ts +2 -2
  36. package/dist/web-components/interaction/bds-button.js +1 -1
  37. package/dist/web-components/interaction/bds-dialog.js +1 -1
  38. package/dist/web-components/interaction/bds-drawer.js +1 -1
  39. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  40. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  41. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  42. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  43. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +20 -3
  44. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  45. package/dist/web-components/interaction/form/bds-select.js +1 -1
  46. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  47. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  48. package/dist/web-components/ui/bds-alert.js +1 -1
  49. package/dist/web-components/ui/bds-card.js +1 -1
  50. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  51. package/package.json +2 -2
  52. package/src/components/interaction/Button/Button.module.css +12 -11
  53. package/src/components/interaction/Button/Button.native.tsx +10 -9
  54. package/src/components/interaction/Button/Button.tsx +1 -1
  55. package/src/components/interaction/Command/Command.module.css +3 -7
  56. package/src/components/interaction/Dialog/Dialog.module.css +7 -5
  57. package/src/components/interaction/Drawer/Drawer.module.css +49 -1
  58. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -3
  59. package/src/components/interaction/Popover/Popover.module.css +1 -3
  60. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -3
  61. package/src/components/interaction/form/Combobox/Combobox.module.css +2 -6
  62. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -3
  63. package/src/components/interaction/form/NumberInput/NumberInput.module.css +16 -4
  64. package/src/components/interaction/form/Radio/Radio.module.css +6 -16
  65. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +70 -25
  66. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +55 -33
  67. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +127 -56
  68. package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +89 -72
  69. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +40 -62
  70. package/src/components/interaction/form/SegmentedControl/index.ts +1 -1
  71. package/src/components/interaction/form/Select/Select.module.css +2 -4
  72. package/src/components/interaction/form/Switch/Switch.module.css +5 -7
  73. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +9 -9
  74. package/src/components/interaction/form/Switch/Switch.native.tsx +2 -2
  75. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -3
  76. package/src/components/interaction/form/atoms/InputContainer.stories.tsx +64 -0
  77. package/src/components/interaction/form/atoms/Label.stories.tsx +33 -0
  78. package/src/components/interaction/form/atoms/Message.stories.tsx +33 -0
  79. package/src/components/layout/Card/Card.module.css +1 -3
  80. package/src/components/ui/Accordion/Accordion.module.css +1 -2
  81. package/src/components/ui/Alert/Alert.module.css +1 -2
  82. package/src/components/ui/Calendar/Calendar.module.css +2 -5
  83. package/src/components/ui/Carousel/Carousel.module.css +1 -3
  84. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -2
  85. package/src/components/ui/Pagination/Pagination.module.css +1 -2
  86. package/src/components/ui/SkipLink/SkipLink.module.css +1 -2
  87. package/src/components/ui/Table/Table.module.css +1 -2
  88. package/src/css/bdc.css +16 -11
  89. package/src/index.ts +1 -1
  90. package/src/web-components/globals.ts +3 -3
  91. package/src/web-components/interaction/BdsAccordion.mdx +51 -0
  92. package/src/web-components/interaction/BdsAccordion.stories.tsx +85 -0
  93. package/src/web-components/interaction/BdsCollapsible.mdx +46 -0
  94. package/src/web-components/interaction/BdsCollapsible.stories.tsx +87 -0
  95. package/src/web-components/interaction/BdsDialog.mdx +49 -0
  96. package/src/web-components/interaction/BdsDialog.stories.tsx +75 -0
  97. package/src/web-components/interaction/BdsDrawer.mdx +50 -0
  98. package/src/web-components/interaction/BdsDrawer.stories.tsx +66 -0
  99. package/src/web-components/interaction/BdsSkipLink.mdx +34 -0
  100. package/src/web-components/interaction/BdsSkipLink.stories.tsx +55 -0
  101. package/src/web-components/interaction/BdsTabs.mdx +47 -0
  102. package/src/web-components/interaction/BdsTabs.stories.tsx +80 -0
  103. package/src/web-components/interaction/BdsTooltip.mdx +38 -0
  104. package/src/web-components/interaction/BdsTooltip.stories.tsx +68 -0
  105. package/src/web-components/interaction/bds-accordion.ts +1 -2
  106. package/src/web-components/interaction/bds-button.ts +15 -13
  107. package/src/web-components/interaction/bds-dialog.ts +2 -5
  108. package/src/web-components/interaction/bds-drawer.ts +50 -1
  109. package/src/web-components/interaction/form/BdsCheckbox.mdx +39 -0
  110. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +73 -0
  111. package/src/web-components/interaction/form/BdsCombobox.mdx +52 -0
  112. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +91 -0
  113. package/src/web-components/interaction/form/BdsFileInput.mdx +44 -0
  114. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +84 -0
  115. package/src/web-components/interaction/form/BdsNumberInput.mdx +41 -0
  116. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +90 -0
  117. package/src/web-components/interaction/form/BdsRadio.mdx +41 -0
  118. package/src/web-components/interaction/form/BdsRadio.stories.tsx +90 -0
  119. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +42 -0
  120. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +92 -0
  121. package/src/web-components/interaction/form/BdsSelect.mdx +38 -0
  122. package/src/web-components/interaction/form/BdsSelect.stories.tsx +93 -0
  123. package/src/web-components/interaction/form/BdsSlider.mdx +49 -0
  124. package/src/web-components/interaction/form/BdsSlider.stories.tsx +89 -0
  125. package/src/web-components/interaction/form/BdsSwitch.mdx +39 -0
  126. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +73 -0
  127. package/src/web-components/interaction/form/BdsTextarea.mdx +41 -0
  128. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +79 -0
  129. package/src/web-components/interaction/form/bds-checkbox.ts +1 -3
  130. package/src/web-components/interaction/form/bds-combobox.ts +1 -3
  131. package/src/web-components/interaction/form/bds-number-input.ts +3 -4
  132. package/src/web-components/interaction/form/bds-radio.ts +6 -12
  133. package/src/web-components/interaction/form/bds-segmented-control.ts +91 -20
  134. package/src/web-components/interaction/form/bds-select.ts +2 -4
  135. package/src/web-components/interaction/form/bds-switch.ts +3 -4
  136. package/src/web-components/interaction/form/bds-textarea.ts +1 -3
  137. package/src/web-components/ui/BdsAvatar.mdx +67 -0
  138. package/src/web-components/ui/BdsAvatar.stories.tsx +62 -0
  139. package/src/web-components/ui/BdsBreadcrumb.mdx +63 -0
  140. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +59 -0
  141. package/src/web-components/ui/BdsCard.mdx +83 -0
  142. package/src/web-components/ui/BdsCard.stories.tsx +90 -0
  143. package/src/web-components/ui/BdsDescriptionList.mdx +67 -0
  144. package/src/web-components/ui/BdsDescriptionList.stories.tsx +79 -0
  145. package/src/web-components/ui/BdsIconWrapper.mdx +70 -0
  146. package/src/web-components/ui/BdsIconWrapper.stories.tsx +76 -0
  147. package/src/web-components/ui/BdsLink.mdx +75 -0
  148. package/src/web-components/ui/BdsLink.stories.tsx +64 -0
  149. package/src/web-components/ui/BdsLoading.mdx +62 -0
  150. package/src/web-components/ui/BdsLoading.stories.tsx +37 -0
  151. package/src/web-components/ui/BdsNotificationBanner.mdx +90 -0
  152. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +93 -0
  153. package/src/web-components/ui/BdsProgress.mdx +63 -0
  154. package/src/web-components/ui/BdsProgress.stories.tsx +70 -0
  155. package/src/web-components/ui/BdsProgressCircle.mdx +64 -0
  156. package/src/web-components/ui/BdsProgressCircle.stories.tsx +69 -0
  157. package/src/web-components/ui/BdsRating.mdx +61 -0
  158. package/src/web-components/ui/BdsRating.stories.tsx +39 -0
  159. package/src/web-components/ui/BdsSectionHeader.mdx +69 -0
  160. package/src/web-components/ui/BdsSectionHeader.stories.tsx +56 -0
  161. package/src/web-components/ui/BdsSeparator.mdx +63 -0
  162. package/src/web-components/ui/BdsSeparator.stories.tsx +47 -0
  163. package/src/web-components/ui/BdsSkeleton.mdx +67 -0
  164. package/src/web-components/ui/BdsSkeleton.stories.tsx +50 -0
  165. package/src/web-components/ui/BdsTypography.mdx +83 -0
  166. package/src/web-components/ui/BdsTypography.stories.tsx +51 -0
  167. package/src/web-components/ui/bds-alert.ts +1 -2
  168. package/src/web-components/ui/bds-card.ts +1 -3
  169. package/src/web-components/ui/bds-notification-banner.ts +1 -2
@@ -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,39 @@ 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.--active {
135
+ color: var(--segmented_color--active, var(--bds-color_interactive));
136
+ }
137
+
138
+ :host([variant='outline']) .indicator {
139
+ display: block;
140
+ box-shadow: inset 0 0 0 2px var(--bds-color_interactive);
141
+ }
142
+
143
+ @media (prefers-reduced-motion: reduce) {
144
+ .thumb,
145
+ .indicator {
146
+ transition: none;
147
+ }
148
+ }
105
149
  `;
106
150
 
107
151
  static properties = {
@@ -109,12 +153,14 @@ export class BdsSegmentedControl extends LitElement {
109
153
  value: { type: String },
110
154
  options: { type: String },
111
155
  disabled: { type: Boolean, reflect: true },
156
+ variant: { type: String, reflect: true },
112
157
  };
113
158
 
114
159
  declare name: string;
115
160
  declare value: string;
116
161
  declare options: string;
117
162
  declare disabled: boolean;
163
+ declare variant: 'outline' | 'filled';
118
164
 
119
165
  private _internals!: ElementInternals;
120
166
  private readonly _uid = Math.random().toString(36).slice(2, 8);
@@ -126,6 +172,7 @@ export class BdsSegmentedControl extends LitElement {
126
172
  this.value = '';
127
173
  this.options = '[]';
128
174
  this.disabled = false;
175
+ this.variant = 'outline';
129
176
  }
130
177
 
131
178
  private get _options(): SegmentedOption[] {
@@ -140,22 +187,35 @@ export class BdsSegmentedControl extends LitElement {
140
187
  super.updated(changed);
141
188
  if (changed.has('value') || changed.has('options')) {
142
189
  this._internals.setFormValue?.(this.value || null);
143
- this._updateThumb();
190
+ if (this.variant === 'filled') this._updateThumb();
191
+ if (this.variant === 'outline') this._updateIndicator();
192
+ }
193
+ if (changed.has('variant')) {
194
+ if (this.variant === 'filled') this._updateThumb();
195
+ if (this.variant === 'outline') this._updateIndicator();
144
196
  }
145
197
  }
146
198
 
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;
199
+ private _positionSlider(el: HTMLElement | null | undefined, items: NodeListOf<HTMLElement>, selectedIndex: number) {
200
+ if (!el || items.length === 0) return;
153
201
  const item = items[selectedIndex];
154
202
  if (!item) return;
155
203
  const controlRect = (item.parentElement as HTMLElement).getBoundingClientRect();
156
204
  const itemRect = item.getBoundingClientRect();
157
- thumb.style.insetInlineStart = `${itemRect.left - controlRect.left}px`;
158
- thumb.style.inlineSize = `${itemRect.width}px`;
205
+ el.style.insetInlineStart = `${itemRect.left - controlRect.left}px`;
206
+ el.style.inlineSize = `${itemRect.width}px`;
207
+ }
208
+
209
+ private _updateThumb() {
210
+ const selectedIndex = Math.max(0, this._options.findIndex(o => o.value === this.value));
211
+ const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.item');
212
+ this._positionSlider(this.shadowRoot?.querySelector<HTMLElement>('.thumb'), items!, selectedIndex);
213
+ }
214
+
215
+ private _updateIndicator() {
216
+ const selectedIndex = Math.max(0, this._options.findIndex(o => o.value === this.value));
217
+ const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.item');
218
+ this._positionSlider(this.shadowRoot?.querySelector<HTMLElement>('.indicator'), items!, selectedIndex);
159
219
  }
160
220
 
161
221
  formResetCallback() {
@@ -174,15 +234,26 @@ export class BdsSegmentedControl extends LitElement {
174
234
  return html`
175
235
  <div class="control" role="group">
176
236
  <span class="thumb" aria-hidden="true"></span>
237
+ <span class="indicator" aria-hidden="true"></span>
177
238
  ${opts.map((opt, i) => {
178
- const id = `bds-seg-${this._uid}-${i}`;
179
239
  const isActive = opt.value === this.value;
180
240
  const isDisabled = this.disabled || opt.disabled;
241
+ const itemClass = `item ${isActive ? '--active' : ''} ${isDisabled ? '--disabled' : ''}`.trim();
242
+
243
+ if (opt.href) {
244
+ return html`
245
+ <a
246
+ class=${itemClass}
247
+ href=${isDisabled ? nothing : opt.href}
248
+ aria-current=${isActive ? 'page' : nothing}
249
+ ?aria-disabled=${isDisabled}
250
+ >${opt.label}</a>
251
+ `;
252
+ }
253
+
254
+ const id = `bds-seg-${this._uid}-${i}`;
181
255
  return html`
182
- <label
183
- class="item ${isActive ? '--active' : ''} ${isDisabled ? '--disabled' : ''}"
184
- for=${id}
185
- >
256
+ <label class=${itemClass} for=${id}>
186
257
  <input
187
258
  id=${id}
188
259
  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,67 @@
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 profile image when `src` is set; 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
+ ## Examples
19
+
20
+ ### All sizes
21
+ <Canvas of={Stories.AllSizes} />
22
+
23
+ ### With initials
24
+ <Canvas of={Stories.Default} />
25
+
26
+ ### All variants
27
+ <Canvas of={Stories.AllVariants} />
28
+
29
+ ## Props
30
+
31
+ <ArgTypes of={Stories} />
32
+
33
+ ## Attributes
34
+
35
+ | Attribute | Type | Default | Description |
36
+ |-----------|------|---------|-------------|
37
+ | `name` | string | — | Full name; used to compute initials and as `aria-label` fallback |
38
+ | `src` | string | — | Image URL; when set, shows the image instead of initials |
39
+ | `alt` | string | — | Alt text for the image (defaults to `name`) |
40
+ | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Avatar diameter (`2em` / `3em` / `4.5em`) |
41
+
42
+ ## CSS Custom Properties
43
+
44
+ | Property | Description |
45
+ |----------|-------------|
46
+ | `--avatar_size` | Override the diameter directly |
47
+ | `--avatar_bg` | Fallback background colour (defaults to `--bds-color_blue`) |
48
+ | `--avatar_text` | Initials text colour (defaults to `--bds-color_on-blue`) |
49
+
50
+ ## Usage in plain HTML
51
+
52
+ ```html
53
+ <!-- Initials fallback -->
54
+ <bds-avatar name="Jane Doe"></bds-avatar>
55
+
56
+ <!-- With image -->
57
+ <bds-avatar src="/path/to/photo.jpg" name="Jane Doe" alt="Jane Doe's avatar"></bds-avatar>
58
+
59
+ <!-- Large size -->
60
+ <bds-avatar name="John Smith" size="large"></bds-avatar>
61
+ ```
62
+
63
+ ## Accessibility
64
+
65
+ - When showing initials: renders `role="img"` with `aria-label` set to the `name` value
66
+ - When showing an image: renders a standard `<img>` with `alt` text
67
+ - Initials are hidden from assistive technology (`aria-hidden="true"`) — the label is on the container
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import '../index';
4
+
5
+ function BdsAvatar({
6
+ name,
7
+ src,
8
+ alt,
9
+ size,
10
+ }: {
11
+ name?: string;
12
+ src?: string;
13
+ alt?: string;
14
+ size?: 'small' | 'medium' | 'large';
15
+ }) {
16
+ return React.createElement('bds-avatar', { name, src, alt, 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' } };
33
+ export const SingleInitial: Story = { args: { name: 'Jane' } };
34
+ export const Small: Story = { args: { name: 'Jane Doe', size: 'small' } };
35
+ export const Medium: Story = { args: { name: 'Jane Doe', size: 'medium' } };
36
+ export const Large: Story = { args: { name: 'Jane Doe', size: 'large' } };
37
+
38
+ export const AllSizes: Story = {
39
+ render: () => (
40
+ <div style={{ display: 'flex', alignItems: 'center', gap: '24px' }}>
41
+ {(['small', 'medium', 'large'] as const).map(s => (
42
+ <div key={s} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
43
+ <BdsAvatar name="Jane Doe" size={s} />
44
+ <span style={{ fontSize: '0.75rem', color: '#888' }}>{s}</span>
45
+ </div>
46
+ ))}
47
+ </div>
48
+ ),
49
+ };
50
+
51
+ export const AllVariants: Story = {
52
+ render: () => (
53
+ <div style={{ display: 'flex', alignItems: 'center', gap: '16px', flexWrap: 'wrap' }}>
54
+ <BdsAvatar name="Jane Doe" />
55
+ <BdsAvatar name="John Smith" />
56
+ <BdsAvatar name="Alice" />
57
+ <BdsAvatar name="Bob" />
58
+ <BdsAvatar name="Jane Doe" size="small" />
59
+ <BdsAvatar name="Jane Doe" size="large" />
60
+ </div>
61
+ ),
62
+ };
@@ -0,0 +1,63 @@
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 navigation custom element. Renders an ordered list of links showing the user's location in the site hierarchy.
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
+ ## Examples
19
+
20
+ ### Default (3 levels)
21
+ <Canvas of={Stories.Default} />
22
+
23
+ ### Deep nested
24
+ <Canvas of={Stories.DeepNested} />
25
+
26
+ ### All variants
27
+ <Canvas of={Stories.AllVariants} />
28
+
29
+ ## Props
30
+
31
+ <ArgTypes of={Stories} />
32
+
33
+ ## Attributes
34
+
35
+ | Attribute | Type | Default | Description |
36
+ |-----------|------|---------|-------------|
37
+ | `items` | JSON string | `"[]"` | Array of `{ label: string, href?: string }` objects. The last item is the current page (no link). |
38
+
39
+ ## Usage in plain HTML
40
+
41
+ ```html
42
+ <bds-breadcrumb items='[
43
+ {"label":"Home","href":"/"},
44
+ {"label":"Products","href":"/products"},
45
+ {"label":"Detail"}
46
+ ]'></bds-breadcrumb>
47
+ ```
48
+
49
+ In JavaScript:
50
+ ```js
51
+ const crumb = document.querySelector('bds-breadcrumb');
52
+ crumb.items = [
53
+ { label: 'Home', href: '/' },
54
+ { label: 'Products', href: '/products' },
55
+ { label: 'Detail' },
56
+ ];
57
+ ```
58
+
59
+ ## Accessibility
60
+
61
+ - Renders as `<nav aria-label="Breadcrumb">` with an `<ol>` list
62
+ - The last item gets `aria-current="page"` — it is not a link
63
+ - Separators (`›`) are hidden from screen readers with `aria-hidden="true"`
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import type { BreadcrumbItem } from './bds-breadcrumb';
4
+ import '../index';
5
+
6
+ function BdsBreadcrumb({ items = [] }: { items?: BreadcrumbItem[] }) {
7
+ return React.createElement('bds-breadcrumb', { items: JSON.stringify(items) });
8
+ }
9
+
10
+ const meta = {
11
+ title: 'Web Components/UI/Breadcrumb',
12
+ component: BdsBreadcrumb,
13
+ tags: ['!stable', 'experimental'],
14
+ parameters: { layout: 'padded' },
15
+ } satisfies Meta<typeof BdsBreadcrumb>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ items: [
23
+ { label: 'Home', href: '/' },
24
+ { label: 'Products', href: '/products' },
25
+ { label: 'Current page' },
26
+ ],
27
+ },
28
+ };
29
+
30
+ export const TwoLevels: Story = {
31
+ args: {
32
+ items: [
33
+ { label: 'Home', href: '/' },
34
+ { label: 'Current page' },
35
+ ],
36
+ },
37
+ };
38
+
39
+ export const DeepNested: Story = {
40
+ args: {
41
+ items: [
42
+ { label: 'Home', href: '/' },
43
+ { label: 'Category', href: '/category' },
44
+ { label: 'Subcategory', href: '/category/sub' },
45
+ { label: 'Section', href: '/category/sub/section' },
46
+ { label: 'Current page' },
47
+ ],
48
+ },
49
+ };
50
+
51
+ export const AllVariants: Story = {
52
+ render: () => (
53
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
54
+ <BdsBreadcrumb items={[{ label: 'Home', href: '/' }, { label: 'Current' }]} />
55
+ <BdsBreadcrumb items={[{ label: 'Home', href: '/' }, { label: 'Products', href: '/products' }, { label: 'Detail' }]} />
56
+ <BdsBreadcrumb items={[{ label: 'Home', href: '/' }, { label: 'A', href: '/a' }, { label: 'B', href: '/a/b' }, { label: 'C', href: '/a/b/c' }, { label: 'Current' }]} />
57
+ </div>
58
+ ),
59
+ };
@@ -0,0 +1,83 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsCard.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-card&gt;
7
+
8
+ Framework-agnostic Card layout element. Groups related content in a contained surface with optional elevation or outline. Can be made clickable.
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
+ ## Examples
19
+
20
+ ### All variants
21
+ <Canvas of={Stories.AllVariants} />
22
+
23
+ ### Clickable
24
+ <Canvas of={Stories.Interactive} />
25
+
26
+ ## Props
27
+
28
+ <ArgTypes of={Stories} />
29
+
30
+ ## Attributes
31
+
32
+ | Attribute | Type | Default | Description |
33
+ |-----------|------|---------|-------------|
34
+ | `variant` | `"default" \| "elevated" \| "outlined"` | `"default"` | Visual style |
35
+ | `padding` | `"none" \| "small" \| "medium" \| "large"` | `"medium"` | Internal spacing |
36
+ | `text-align` | `"start" \| "center" \| "end"` | `"start"` | Content alignment |
37
+ | `clickable` | boolean | `false` | Renders as `<button>`, adds hover/active effects |
38
+ | `aria-label` | string | — | Accessible label (required when `clickable` and the card content is not descriptive enough) |
39
+
40
+ ## Slots
41
+
42
+ | Slot | Description |
43
+ |------|-------------|
44
+ | `(default)` | Card content |
45
+
46
+ ## Events
47
+
48
+ | Event | Detail | Bubbles | Composed | Description |
49
+ |-------|--------|---------|----------|-------------|
50
+ | `bds-click` | — | Yes | Yes | Fired when a clickable card is activated |
51
+
52
+ ## CSS Custom Properties
53
+
54
+ | Property | Description |
55
+ |----------|-------------|
56
+ | `--card_color` | Card background colour |
57
+ | `--card_on-color` | Card text colour |
58
+
59
+ ## Usage in plain HTML
60
+
61
+ ```html
62
+ <bds-card variant="elevated" padding="large">
63
+ <h3>Card title</h3>
64
+ <p>Card body text.</p>
65
+ </bds-card>
66
+
67
+ <bds-card clickable aria-label="View product details">
68
+ <strong>Clickable card</strong>
69
+ </bds-card>
70
+ ```
71
+
72
+ Listen for click:
73
+ ```js
74
+ document.querySelector('bds-card[clickable]').addEventListener('bds-click', () => {
75
+ console.log('card clicked');
76
+ });
77
+ ```
78
+
79
+ ## Accessibility
80
+
81
+ - Non-clickable cards are `<div>` elements — they have no interactive role
82
+ - Clickable cards render as `<button>` with keyboard and pointer support
83
+ - Always provide `aria-label` on clickable cards when the text content alone is not descriptive