@boostdev/design-system-components 1.2.0 → 1.2.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.
- package/AGENTS.md +46 -4
- package/README.md +15 -15
- package/dist/client.cjs +149 -124
- package/dist/client.css +503 -525
- package/dist/client.d.cts +71 -96
- package/dist/client.d.ts +71 -96
- package/dist/client.js +149 -124
- package/dist/index.cjs +149 -124
- package/dist/index.css +503 -525
- package/dist/index.d.cts +71 -96
- package/dist/index.d.ts +71 -96
- package/dist/index.js +149 -124
- package/dist/web-components/{chunk-6MH5UWUD.js → chunk-2FGATTGT.js} +5 -0
- package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
- package/dist/web-components/{chunk-DI46Q2EA.js → chunk-OCODKRVZ.js} +57 -82
- package/dist/web-components/index.d.ts +571 -1
- package/dist/web-components/index.js +1501 -4
- package/dist/web-components/interaction/bds-collapsible.js +1 -1
- package/dist/web-components/interaction/bds-drawer.js +1 -1
- package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
- package/package.json +1 -1
- package/src/components/interaction/Command/Command.tsx +4 -3
- package/src/components/interaction/Dialog/Dialog.tsx +4 -5
- package/src/components/interaction/Drawer/Drawer.module.css +37 -62
- package/src/components/interaction/Drawer/Drawer.spec.tsx +3 -3
- package/src/components/interaction/Drawer/Drawer.tsx +25 -24
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +4 -2
- package/src/components/interaction/Popover/Popover.tsx +4 -3
- package/src/components/interaction/Rating/Rating.tsx +4 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +4 -6
- package/src/components/interaction/form/Combobox/Combobox.tsx +4 -2
- package/src/components/interaction/form/FileInput/FileInput.tsx +4 -3
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +4 -3
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +4 -6
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +8 -4
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +13 -6
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -6
- package/src/components/interaction/form/atoms/InputContainer.tsx +2 -2
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +4 -6
- package/src/components/layout/Card/Card.tsx +4 -10
- package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -5
- package/src/components/layout/SectionHeader/SectionHeader.tsx +5 -4
- package/src/components/ui/Accordion/Accordion.tsx +4 -3
- package/src/components/ui/Alert/Alert.tsx +4 -3
- package/src/components/ui/Avatar/Avatar.tsx +5 -3
- package/src/components/ui/Badge/Badge.tsx +4 -5
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +4 -3
- package/src/components/ui/Calendar/Calendar.tsx +4 -4
- package/src/components/ui/Carousel/Carousel.tsx +4 -4
- package/src/components/ui/DescriptionList/DescriptionList.tsx +4 -4
- package/src/components/ui/Loading/Loading.tsx +4 -3
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +4 -2
- package/src/components/ui/Pagination/Pagination.tsx +4 -2
- package/src/components/ui/Progress/Progress.tsx +4 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +4 -1
- package/src/components/ui/Separator/Separator.tsx +5 -3
- package/src/components/ui/Skeleton/Skeleton.tsx +4 -3
- package/src/components/ui/SkipLink/SkipLink.tsx +4 -5
- package/src/components/ui/Tabs/Tabs.tsx +4 -4
- package/src/components/ui/Tooltip/Tooltip.tsx +4 -2
- package/src/components/ui/Typography/Typography.tsx +4 -5
- package/src/stories/DesignSystem/Grid.mdx +2 -0
- package/src/stories/Introduction.mdx +2 -1
- package/src/web-components/index.ts +12 -0
- package/src/web-components/interaction/BdsAccordion.mdx +80 -28
- package/src/web-components/interaction/BdsAccordion.stories.tsx +67 -58
- package/src/web-components/interaction/BdsCollapsible.mdx +93 -23
- package/src/web-components/interaction/BdsCollapsible.stories.tsx +29 -48
- package/src/web-components/interaction/BdsDialog.mdx +88 -27
- package/src/web-components/interaction/BdsDialog.stories.tsx +129 -47
- package/src/web-components/interaction/BdsDrawer.mdx +85 -27
- package/src/web-components/interaction/BdsDrawer.stories.tsx +161 -31
- package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
- package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
- package/src/web-components/interaction/BdsSkipLink.mdx +72 -16
- package/src/web-components/interaction/BdsSkipLink.stories.tsx +47 -34
- package/src/web-components/interaction/BdsTabs.mdx +97 -27
- package/src/web-components/interaction/BdsTabs.stories.tsx +69 -55
- package/src/web-components/interaction/BdsTooltip.mdx +84 -18
- package/src/web-components/interaction/BdsTooltip.stories.tsx +49 -30
- package/src/web-components/interaction/bds-collapsible.ts +1 -0
- package/src/web-components/interaction/bds-drawer.ts +59 -82
- package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
- package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
- package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
- package/src/web-components/interaction/form/BdsCheckbox.mdx +57 -20
- package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +35 -36
- package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
- package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
- package/src/web-components/interaction/form/BdsCombobox.mdx +52 -33
- package/src/web-components/interaction/form/BdsCombobox.stories.tsx +55 -53
- package/src/web-components/interaction/form/BdsFileInput.mdx +59 -24
- package/src/web-components/interaction/form/BdsFileInput.stories.tsx +38 -53
- package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
- package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
- package/src/web-components/interaction/form/BdsNumberInput.mdx +51 -22
- package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +24 -46
- package/src/web-components/interaction/form/BdsRadio.mdx +53 -22
- package/src/web-components/interaction/form/BdsRadio.stories.tsx +19 -51
- package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
- package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
- package/src/web-components/interaction/form/BdsSegmentedControl.mdx +76 -23
- package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +64 -54
- package/src/web-components/interaction/form/BdsSelect.mdx +59 -21
- package/src/web-components/interaction/form/BdsSelect.stories.tsx +47 -56
- package/src/web-components/interaction/form/BdsSlider.mdx +55 -27
- package/src/web-components/interaction/form/BdsSlider.stories.tsx +23 -44
- package/src/web-components/interaction/form/BdsSwitch.mdx +53 -20
- package/src/web-components/interaction/form/BdsSwitch.stories.tsx +22 -37
- package/src/web-components/interaction/form/BdsTextarea.mdx +54 -22
- package/src/web-components/interaction/form/BdsTextarea.stories.tsx +35 -44
- package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
- package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
- package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
- package/src/web-components/interaction/form/bds-form-input.ts +268 -0
- package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
- package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
- package/src/web-components/interaction/form/bds-segmented-control.ts +5 -0
- package/src/web-components/ui/BdsAvatar.mdx +29 -20
- package/src/web-components/ui/BdsAvatar.stories.tsx +14 -28
- package/src/web-components/ui/BdsBreadcrumb.mdx +22 -19
- package/src/web-components/ui/BdsBreadcrumb.stories.tsx +11 -21
- package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
- package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
- package/src/web-components/ui/BdsCalendar.mdx +93 -0
- package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
- package/src/web-components/ui/BdsCard.mdx +30 -25
- package/src/web-components/ui/BdsCard.stories.tsx +19 -42
- package/src/web-components/ui/BdsCarousel.mdx +83 -0
- package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
- package/src/web-components/ui/BdsDescriptionList.mdx +13 -16
- package/src/web-components/ui/BdsDescriptionList.stories.tsx +34 -49
- package/src/web-components/ui/BdsIconWrapper.mdx +20 -20
- package/src/web-components/ui/BdsIconWrapper.stories.tsx +46 -41
- package/src/web-components/ui/BdsLink.mdx +21 -17
- package/src/web-components/ui/BdsLink.stories.tsx +19 -20
- package/src/web-components/ui/BdsLoading.mdx +9 -13
- package/src/web-components/ui/BdsLoading.stories.tsx +8 -10
- package/src/web-components/ui/BdsNotificationBanner.mdx +32 -37
- package/src/web-components/ui/BdsNotificationBanner.stories.tsx +38 -47
- package/src/web-components/ui/BdsPagination.mdx +74 -0
- package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
- package/src/web-components/ui/BdsProgress.mdx +24 -17
- package/src/web-components/ui/BdsProgress.stories.tsx +55 -29
- package/src/web-components/ui/BdsProgressCircle.mdx +24 -18
- package/src/web-components/ui/BdsProgressCircle.stories.tsx +17 -29
- package/src/web-components/ui/BdsRating.mdx +22 -22
- package/src/web-components/ui/BdsRating.stories.tsx +19 -13
- package/src/web-components/ui/BdsSectionHeader.mdx +31 -28
- package/src/web-components/ui/BdsSectionHeader.stories.tsx +21 -23
- package/src/web-components/ui/BdsSeparator.mdx +22 -8
- package/src/web-components/ui/BdsSeparator.stories.tsx +27 -19
- package/src/web-components/ui/BdsSkeleton.mdx +20 -26
- package/src/web-components/ui/BdsSkeleton.stories.tsx +23 -23
- package/src/web-components/ui/BdsTable.mdx +81 -0
- package/src/web-components/ui/BdsTable.stories.tsx +83 -0
- package/src/web-components/ui/BdsTypography.mdx +20 -29
- package/src/web-components/ui/BdsTypography.stories.tsx +19 -18
- package/src/web-components/ui/bds-button-group.spec.ts +40 -0
- package/src/web-components/ui/bds-button-group.ts +78 -0
- package/src/web-components/ui/bds-calendar.spec.ts +91 -0
- package/src/web-components/ui/bds-calendar.ts +427 -0
- package/src/web-components/ui/bds-carousel.spec.ts +64 -0
- package/src/web-components/ui/bds-carousel.ts +296 -0
- package/src/web-components/ui/bds-pagination.spec.ts +67 -0
- package/src/web-components/ui/bds-pagination.ts +197 -0
- package/src/web-components/ui/bds-table.spec.ts +45 -0
- package/src/web-components/ui/bds-table.ts +96 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-form-input>` — form-associated text input with label, hint, and error support.
|
|
5
|
+
*
|
|
6
|
+
* Participates in HTML forms via the Form-Associated Custom Elements API.
|
|
7
|
+
*
|
|
8
|
+
* Attributes:
|
|
9
|
+
* name — form field name
|
|
10
|
+
* type — input type: text | email | password | search | url | tel (default: "text")
|
|
11
|
+
* value — current value
|
|
12
|
+
* placeholder — placeholder text
|
|
13
|
+
* label — visible label text
|
|
14
|
+
* hint — helper text shown below the input
|
|
15
|
+
* error — error message; when set the input enters error state
|
|
16
|
+
* disabled — disables the input
|
|
17
|
+
* required — marks the field as required
|
|
18
|
+
*
|
|
19
|
+
* Slots:
|
|
20
|
+
* prefix — icon or text rendered before the input
|
|
21
|
+
* suffix — icon or text rendered after the input
|
|
22
|
+
*
|
|
23
|
+
* Events:
|
|
24
|
+
* bds-change — fired on value change; `detail: { value: string }`
|
|
25
|
+
* bds-input — fired on every keystroke; `detail: { value: string }`
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <bds-form-input name="email" type="email" label="Email address" required></bds-form-input>
|
|
29
|
+
*/
|
|
30
|
+
export class BdsFormInput extends LitElement {
|
|
31
|
+
static formAssociated = true;
|
|
32
|
+
|
|
33
|
+
static styles = css`
|
|
34
|
+
:host {
|
|
35
|
+
display: block;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host([disabled]) {
|
|
39
|
+
opacity: 0.5;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.label {
|
|
44
|
+
display: block;
|
|
45
|
+
font-size: var(--bds-font_size--body--s);
|
|
46
|
+
font-family: var(--bds-font_family--body);
|
|
47
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
48
|
+
color: var(--bds-color_on-bg);
|
|
49
|
+
margin-block-end: var(--bds-space_xxs);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.required {
|
|
53
|
+
color: var(--bds-color_error);
|
|
54
|
+
margin-inline-start: var(--bds-space_xxxs);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.wrapper {
|
|
58
|
+
position: relative;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
--bdc_color: currentcolor;
|
|
62
|
+
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
63
|
+
|
|
64
|
+
border-radius: var(--formInput_radius, var(--bdc-outline_radius));
|
|
65
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--formInput_shadow, var(--bdc-outline_shadow));
|
|
66
|
+
background-color: var(--bds-color_bg);
|
|
67
|
+
transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([error]) .wrapper {
|
|
71
|
+
--bdc_color: var(--bds-color_error);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.wrapper:focus-within {
|
|
75
|
+
--bdc_color: var(--bdc_color--focus);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:host([error]) .wrapper:focus-within {
|
|
79
|
+
--bdc_color: var(--bds-color_error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.input {
|
|
83
|
+
flex: 1;
|
|
84
|
+
min-inline-size: 0;
|
|
85
|
+
border: none;
|
|
86
|
+
background: transparent;
|
|
87
|
+
padding: var(--bds-space_s);
|
|
88
|
+
font-size: var(--bds-font_size--body);
|
|
89
|
+
font-family: var(--bds-font_family--body);
|
|
90
|
+
color: var(--bds-color_on-bg);
|
|
91
|
+
outline: none;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.input::placeholder {
|
|
95
|
+
color: var(--bds-color_on-bg--subtle);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.input:focus-visible {
|
|
99
|
+
outline: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.wrapper:focus-within {
|
|
103
|
+
outline: var(--bds-outline_default);
|
|
104
|
+
outline-offset: var(--bds-outline_offset);
|
|
105
|
+
border-radius: var(--formInput_radius, var(--bdc-outline_radius));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.prefix,
|
|
109
|
+
.suffix {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
color: var(--bds-color_on-bg--subtle);
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.prefix {
|
|
117
|
+
padding-inline-start: var(--bds-space_s);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.suffix {
|
|
121
|
+
padding-inline-end: var(--bds-space_s);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.prefix:empty,
|
|
125
|
+
.suffix:empty {
|
|
126
|
+
display: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.message {
|
|
130
|
+
margin-block-start: var(--bds-space_xxs);
|
|
131
|
+
font-size: var(--bds-font_size--body--s);
|
|
132
|
+
font-family: var(--bds-font_family--body);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.message--error {
|
|
136
|
+
color: var(--bds-color_error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.message--hint {
|
|
140
|
+
color: var(--bds-color_on-bg--subtle);
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
static properties = {
|
|
145
|
+
name: { type: String, reflect: true },
|
|
146
|
+
type: { type: String, reflect: true },
|
|
147
|
+
value: { type: String },
|
|
148
|
+
placeholder: { type: String },
|
|
149
|
+
label: { type: String },
|
|
150
|
+
hint: { type: String },
|
|
151
|
+
error: { type: String, reflect: true },
|
|
152
|
+
disabled: { type: Boolean, reflect: true },
|
|
153
|
+
required: { type: Boolean, reflect: true },
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
declare name: string;
|
|
157
|
+
declare type: string;
|
|
158
|
+
declare value: string;
|
|
159
|
+
declare placeholder: string;
|
|
160
|
+
declare label: string;
|
|
161
|
+
declare hint: string | undefined;
|
|
162
|
+
declare error: string | undefined;
|
|
163
|
+
declare disabled: boolean;
|
|
164
|
+
declare required: boolean;
|
|
165
|
+
|
|
166
|
+
private _internals!: ElementInternals;
|
|
167
|
+
private readonly _uid = Math.random().toString(36).slice(2, 8);
|
|
168
|
+
|
|
169
|
+
constructor() {
|
|
170
|
+
super();
|
|
171
|
+
this._internals = this.attachInternals();
|
|
172
|
+
this.name = '';
|
|
173
|
+
this.type = 'text';
|
|
174
|
+
this.value = '';
|
|
175
|
+
this.placeholder = '';
|
|
176
|
+
this.label = '';
|
|
177
|
+
this.disabled = false;
|
|
178
|
+
this.required = false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
updated(changed: Map<string, unknown>) {
|
|
182
|
+
super.updated(changed);
|
|
183
|
+
if (changed.has('value')) {
|
|
184
|
+
this._internals.setFormValue?.(this.value);
|
|
185
|
+
}
|
|
186
|
+
if (changed.has('required') || changed.has('value')) {
|
|
187
|
+
if (this.required && !this.value) {
|
|
188
|
+
this._internals.setValidity?.({ valueMissing: true }, 'Required');
|
|
189
|
+
} else {
|
|
190
|
+
this._internals.setValidity?.({});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
formResetCallback() {
|
|
196
|
+
this.value = '';
|
|
197
|
+
this._internals.setFormValue?.('');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private _handleInput(e: Event) {
|
|
201
|
+
const input = e.target as HTMLInputElement;
|
|
202
|
+
this.value = input.value;
|
|
203
|
+
this.dispatchEvent(new CustomEvent('bds-input', {
|
|
204
|
+
detail: { value: this.value },
|
|
205
|
+
bubbles: true,
|
|
206
|
+
composed: true,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private _handleChange(e: Event) {
|
|
211
|
+
const input = e.target as HTMLInputElement;
|
|
212
|
+
this.value = input.value;
|
|
213
|
+
this.dispatchEvent(new CustomEvent('bds-change', {
|
|
214
|
+
detail: { value: this.value },
|
|
215
|
+
bubbles: true,
|
|
216
|
+
composed: true,
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
render() {
|
|
221
|
+
const id = `bds-form-input-${this._uid}`;
|
|
222
|
+
const hintId = `${id}-hint`;
|
|
223
|
+
const errorId = `${id}-error`;
|
|
224
|
+
const describedBy = [this.error && errorId, this.hint && hintId].filter(Boolean).join(' ') || undefined;
|
|
225
|
+
|
|
226
|
+
return html`
|
|
227
|
+
${this.label
|
|
228
|
+
? html`
|
|
229
|
+
<label class="label" for=${id}>
|
|
230
|
+
${this.label}
|
|
231
|
+
${this.required ? html`<span class="required" aria-hidden="true">*</span>` : ''}
|
|
232
|
+
</label>
|
|
233
|
+
`
|
|
234
|
+
: ''}
|
|
235
|
+
<div class="wrapper">
|
|
236
|
+
<span class="prefix"><slot name="prefix"></slot></span>
|
|
237
|
+
<input
|
|
238
|
+
id=${id}
|
|
239
|
+
class="input"
|
|
240
|
+
type=${this.type}
|
|
241
|
+
.value=${this.value}
|
|
242
|
+
placeholder=${this.placeholder}
|
|
243
|
+
?disabled=${this.disabled}
|
|
244
|
+
?required=${this.required}
|
|
245
|
+
aria-invalid=${this.error ? 'true' : 'false'}
|
|
246
|
+
aria-describedby=${describedBy ?? ''}
|
|
247
|
+
aria-required=${this.required ? 'true' : 'false'}
|
|
248
|
+
@input=${this._handleInput}
|
|
249
|
+
@change=${this._handleChange}
|
|
250
|
+
/>
|
|
251
|
+
<span class="suffix"><slot name="suffix"></slot></span>
|
|
252
|
+
</div>
|
|
253
|
+
${this.error
|
|
254
|
+
? html`<p id=${errorId} class="message message--error" role="alert">${this.error}</p>`
|
|
255
|
+
: this.hint
|
|
256
|
+
? html`<p id=${hintId} class="message message--hint">${this.hint}</p>`
|
|
257
|
+
: ''}
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
customElements.define('bds-form-input', BdsFormInput);
|
|
263
|
+
|
|
264
|
+
declare global {
|
|
265
|
+
interface HTMLElementTagNameMap {
|
|
266
|
+
'bds-form-input': BdsFormInput;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../../test/helpers';
|
|
2
|
+
import { BdsRadioGroup } from './bds-radio-group';
|
|
3
|
+
|
|
4
|
+
describe('bds-radio-group', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-radio-group')).toBe(BdsRadioGroup);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a fieldset', async () => {
|
|
10
|
+
const el = await fixture('<bds-radio-group label="Options"></bds-radio-group>');
|
|
11
|
+
expect(el.shadowRoot!.querySelector('fieldset')).not.toBeNull();
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders the label as a legend', async () => {
|
|
16
|
+
const el = await fixture('<bds-radio-group label="Select one"></bds-radio-group>');
|
|
17
|
+
const legend = el.shadowRoot!.querySelector('legend');
|
|
18
|
+
expect(legend?.textContent?.trim()).toContain('Select one');
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('disables the fieldset when disabled', async () => {
|
|
23
|
+
const el = await fixture('<bds-radio-group label="Test" disabled></bds-radio-group>');
|
|
24
|
+
const fieldset = el.shadowRoot!.querySelector('fieldset')!;
|
|
25
|
+
expect(fieldset.disabled).toBe(true);
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders a slot for radio children', async () => {
|
|
30
|
+
const el = await fixture('<bds-radio-group label="Test"></bds-radio-group>');
|
|
31
|
+
expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
|
|
32
|
+
cleanup(el);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders error message when error attribute is set', async () => {
|
|
36
|
+
const el = await fixture('<bds-radio-group label="Test" error="Required field"></bds-radio-group>');
|
|
37
|
+
const message = el.shadowRoot!.querySelector('.message--error');
|
|
38
|
+
expect(message?.textContent?.trim()).toBe('Required field');
|
|
39
|
+
cleanup(el);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('reflects name attribute', async () => {
|
|
43
|
+
const el = await fixture('<bds-radio-group label="Test" name="contact"></bds-radio-group>') as BdsRadioGroup;
|
|
44
|
+
expect(el.name).toBe('contact');
|
|
45
|
+
cleanup(el);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('propagates name to slotted bds-radio elements', async () => {
|
|
49
|
+
// Define a minimal bds-radio stub so querySelectorAll finds it
|
|
50
|
+
if (!customElements.get('bds-radio-stub')) {
|
|
51
|
+
customElements.define('bds-radio-stub', class extends HTMLElement {});
|
|
52
|
+
}
|
|
53
|
+
const el = await fixture(
|
|
54
|
+
'<bds-radio-group label="Contact" name="contact"><bds-radio value="email">Email</bds-radio></bds-radio-group>',
|
|
55
|
+
) as BdsRadioGroup;
|
|
56
|
+
// Wait for propagation promise
|
|
57
|
+
await Promise.resolve();
|
|
58
|
+
const radio = el.querySelector('bds-radio');
|
|
59
|
+
expect(radio?.getAttribute('name')).toBe('contact');
|
|
60
|
+
cleanup(el);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -131,8 +131,13 @@ export class BdsSegmentedControl extends LitElement {
|
|
|
131
131
|
display: none;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
:host([variant='outline']) .item {
|
|
135
|
+
background-color: var(--segmented_item-bg, transparent);
|
|
136
|
+
}
|
|
137
|
+
|
|
134
138
|
:host([variant='outline']) .item.--active {
|
|
135
139
|
color: var(--segmented_color--active, var(--bds-color_interactive));
|
|
140
|
+
background-color: var(--segmented_item-bg--active, var(--bds-color_bg));
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
:host([variant='outline']) .indicator {
|
|
@@ -5,7 +5,7 @@ import * as Stories from './BdsAvatar.stories';
|
|
|
5
5
|
|
|
6
6
|
# <bds-avatar>
|
|
7
7
|
|
|
8
|
-
Framework-agnostic Avatar custom element. Displays a
|
|
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
9
|
|
|
10
10
|
> **Status: experimental** — API may change before stable release.
|
|
11
11
|
|
|
@@ -15,16 +15,27 @@ Framework-agnostic Avatar custom element. Displays a profile image when `src` is
|
|
|
15
15
|
import '@boostdev/components/web-components';
|
|
16
16
|
```
|
|
17
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
|
+
|
|
18
23
|
## Examples
|
|
19
24
|
|
|
20
25
|
### All sizes
|
|
21
26
|
<Canvas of={Stories.AllSizes} />
|
|
22
27
|
|
|
23
|
-
###
|
|
28
|
+
### Default (initials)
|
|
24
29
|
<Canvas of={Stories.Default} />
|
|
25
30
|
|
|
26
|
-
###
|
|
27
|
-
<Canvas of={Stories.
|
|
31
|
+
### With image
|
|
32
|
+
<Canvas of={Stories.WithImage} />
|
|
33
|
+
|
|
34
|
+
### Small
|
|
35
|
+
<Canvas of={Stories.Small} />
|
|
36
|
+
|
|
37
|
+
### Large
|
|
38
|
+
<Canvas of={Stories.Large} />
|
|
28
39
|
|
|
29
40
|
## Props
|
|
30
41
|
|
|
@@ -34,34 +45,32 @@ import '@boostdev/components/web-components';
|
|
|
34
45
|
|
|
35
46
|
| Attribute | Type | Default | Description |
|
|
36
47
|
|-----------|------|---------|-------------|
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `size` | `"small" \| "medium" \| "large"` | `"medium"` |
|
|
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 |
|
|
41
52
|
|
|
42
53
|
## CSS Custom Properties
|
|
43
54
|
|
|
44
55
|
| Property | Description |
|
|
45
56
|
|----------|-------------|
|
|
46
|
-
| `--
|
|
47
|
-
| `--
|
|
48
|
-
| `--avatar_text` | Initials text colour (defaults to `--bds-color_on-blue`) |
|
|
57
|
+
| `--avatar_bg` | Background colour of the fallback initials circle |
|
|
58
|
+
| `--avatar_text` | Text colour of the initials |
|
|
49
59
|
|
|
50
60
|
## Usage in plain HTML
|
|
51
61
|
|
|
52
62
|
```html
|
|
53
|
-
<!--
|
|
54
|
-
<bds-avatar name="Jane Doe"></bds-avatar>
|
|
63
|
+
<!-- With photo -->
|
|
64
|
+
<bds-avatar src="/photo.jpg" name="Jane Doe"></bds-avatar>
|
|
55
65
|
|
|
56
|
-
<!--
|
|
57
|
-
<bds-avatar
|
|
66
|
+
<!-- Initials only -->
|
|
67
|
+
<bds-avatar name="Jane Doe" size="large"></bds-avatar>
|
|
58
68
|
|
|
59
|
-
<!--
|
|
60
|
-
<bds-avatar name="
|
|
69
|
+
<!-- Small -->
|
|
70
|
+
<bds-avatar name="JD" size="small"></bds-avatar>
|
|
61
71
|
```
|
|
62
72
|
|
|
63
73
|
## Accessibility
|
|
64
74
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
- Initials are hidden from assistive technology (`aria-hidden="true"`) — the label is on the container
|
|
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"
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import '../index';
|
|
3
|
+
import '../index'; // auto-registers all custom elements
|
|
4
4
|
|
|
5
5
|
function BdsAvatar({
|
|
6
|
-
name,
|
|
7
6
|
src,
|
|
8
7
|
alt,
|
|
9
|
-
|
|
8
|
+
name,
|
|
9
|
+
size = 'medium',
|
|
10
10
|
}: {
|
|
11
|
-
name?: string;
|
|
12
11
|
src?: string;
|
|
13
12
|
alt?: string;
|
|
13
|
+
name?: string;
|
|
14
14
|
size?: 'small' | 'medium' | 'large';
|
|
15
15
|
}) {
|
|
16
|
-
return React.createElement('bds-avatar', {
|
|
16
|
+
return React.createElement('bds-avatar', { src, alt, name, size });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const meta = {
|
|
@@ -29,34 +29,20 @@ const meta = {
|
|
|
29
29
|
export default meta;
|
|
30
30
|
type Story = StoryObj<typeof meta>;
|
|
31
31
|
|
|
32
|
-
export const Default: Story = { args: { name: 'Jane Doe' } };
|
|
33
|
-
export const
|
|
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
|
+
};
|
|
34
36
|
export const Small: Story = { args: { name: 'Jane Doe', size: 'small' } };
|
|
35
|
-
export const Medium: Story = { args: { name: 'Jane Doe', size: 'medium' } };
|
|
36
37
|
export const Large: Story = { args: { name: 'Jane Doe', size: 'large' } };
|
|
38
|
+
export const InitialsOnly: Story = { args: { name: 'John Smith', size: 'medium' } };
|
|
37
39
|
|
|
38
40
|
export const AllSizes: Story = {
|
|
39
41
|
render: () => (
|
|
40
|
-
<div style={{ display: 'flex',
|
|
41
|
-
{(
|
|
42
|
-
|
|
43
|
-
|
|
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" />
|
|
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' })}
|
|
60
46
|
</div>
|
|
61
47
|
),
|
|
62
48
|
};
|