@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
|
@@ -3,39 +3,71 @@ import * as Stories from './BdsTextarea.stories';
|
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# <bds-textarea>
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Form-associated multiline text input. Wraps a native `<textarea>` with consistent styling while participating in HTML forms via the Form-Associated Custom Elements API.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> **Status: experimental** — API may change before stable release.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import '@boostdev/components/web-components';
|
|
14
16
|
```
|
|
15
17
|
|
|
18
|
+
## When to use
|
|
19
|
+
|
|
20
|
+
Use `<bds-textarea>` for multiline text entry — messages, comments, descriptions, or any input that may span more than one line.
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
### Default
|
|
25
|
+
<Canvas of={Stories.Default} />
|
|
26
|
+
|
|
27
|
+
### Pre-filled value
|
|
28
|
+
<Canvas of={Stories.WithValue} />
|
|
29
|
+
|
|
30
|
+
### Tall (8 rows)
|
|
31
|
+
<Canvas of={Stories.TallRows} />
|
|
32
|
+
|
|
33
|
+
### Required
|
|
34
|
+
<Canvas of={Stories.Required} />
|
|
35
|
+
|
|
36
|
+
### Disabled
|
|
37
|
+
<Canvas of={Stories.Disabled} />
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
<ArgTypes of={Stories} />
|
|
42
|
+
|
|
16
43
|
## Attributes
|
|
17
44
|
|
|
18
|
-
| Attribute | Type
|
|
19
|
-
|
|
20
|
-
| `name` |
|
|
21
|
-
| `value` |
|
|
22
|
-
| `placeholder` |
|
|
23
|
-
| `rows` |
|
|
24
|
-
| `required` |
|
|
25
|
-
| `disabled` |
|
|
45
|
+
| Attribute | Type | Default | Description |
|
|
46
|
+
|---------------|---------|---------|------------------------------------------|
|
|
47
|
+
| `name` | string | `""` | Form field name |
|
|
48
|
+
| `value` | string | `""` | Text value |
|
|
49
|
+
| `placeholder` | string | `""` | Placeholder text |
|
|
50
|
+
| `rows` | number | `4` | Number of visible rows |
|
|
51
|
+
| `required` | boolean | `false` | Marks the field as required |
|
|
52
|
+
| `disabled` | boolean | `false` | Disables the textarea |
|
|
26
53
|
|
|
27
54
|
## Events
|
|
28
55
|
|
|
29
|
-
| Event | Description
|
|
30
|
-
|
|
31
|
-
| `input` | Fired
|
|
32
|
-
| `change` | Fired when value is committed
|
|
56
|
+
| Event | Detail | Description |
|
|
57
|
+
|----------|--------|-----------------------------------------------------|
|
|
58
|
+
| `input` | — | Fired on every keystroke (native input event) |
|
|
59
|
+
| `change` | — | Fired when the value is committed (native change event) |
|
|
33
60
|
|
|
34
|
-
##
|
|
61
|
+
## Usage in plain HTML
|
|
35
62
|
|
|
36
|
-
|
|
37
|
-
<
|
|
63
|
+
```html
|
|
64
|
+
<bds-textarea name="message" placeholder="Enter message…" rows="6"></bds-textarea>
|
|
65
|
+
<bds-textarea name="bio" required rows="4"></bds-textarea>
|
|
66
|
+
```
|
|
38
67
|
|
|
39
|
-
##
|
|
68
|
+
## Accessibility
|
|
40
69
|
|
|
41
|
-
|
|
70
|
+
- Wraps a native `<textarea>` which has full browser and assistive technology support
|
|
71
|
+
- `required` uses the native `required` attribute for browser validation
|
|
72
|
+
- `disabled` sets `pointer-events: none`, reduces opacity, and removes the resize handle
|
|
73
|
+
- Participates in HTML forms via the Form-Associated Custom Elements API
|
|
@@ -1,24 +1,29 @@
|
|
|
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 BdsTextarea({
|
|
6
|
-
|
|
6
|
+
name,
|
|
7
|
+
value,
|
|
8
|
+
placeholder,
|
|
7
9
|
rows,
|
|
8
|
-
disabled,
|
|
9
10
|
required,
|
|
11
|
+
disabled,
|
|
10
12
|
}: {
|
|
13
|
+
name?: string;
|
|
14
|
+
value?: string;
|
|
11
15
|
placeholder?: string;
|
|
12
16
|
rows?: number;
|
|
13
|
-
disabled?: boolean;
|
|
14
17
|
required?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
15
19
|
}) {
|
|
16
20
|
return React.createElement('bds-textarea', {
|
|
21
|
+
name,
|
|
22
|
+
value,
|
|
17
23
|
placeholder,
|
|
18
24
|
rows,
|
|
19
|
-
disabled: disabled || undefined,
|
|
20
25
|
required: required || undefined,
|
|
21
|
-
|
|
26
|
+
disabled: disabled || undefined,
|
|
22
27
|
});
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -26,54 +31,40 @@ const meta = {
|
|
|
26
31
|
title: 'Web Components/Interaction/Form/Textarea',
|
|
27
32
|
component: BdsTextarea,
|
|
28
33
|
tags: ['!stable', 'experimental'],
|
|
29
|
-
parameters: { layout: '
|
|
34
|
+
parameters: { layout: 'centered' },
|
|
30
35
|
argTypes: {
|
|
31
36
|
rows: { control: 'number' },
|
|
32
|
-
disabled: { control: 'boolean' },
|
|
33
37
|
required: { control: 'boolean' },
|
|
38
|
+
disabled: { control: 'boolean' },
|
|
34
39
|
},
|
|
40
|
+
decorators: [
|
|
41
|
+
(Story: React.ComponentType) => (
|
|
42
|
+
<div style={{ width: '360px' }}>
|
|
43
|
+
<Story />
|
|
44
|
+
</div>
|
|
45
|
+
),
|
|
46
|
+
],
|
|
35
47
|
} satisfies Meta<typeof BdsTextarea>;
|
|
36
48
|
|
|
37
49
|
export default meta;
|
|
38
50
|
type Story = StoryObj<typeof meta>;
|
|
39
51
|
|
|
40
|
-
export const Default: Story = {
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
export const Default: Story = {
|
|
53
|
+
args: { name: 'message', placeholder: 'Enter your message…' },
|
|
54
|
+
};
|
|
43
55
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxInlineSize: '400px' }}>
|
|
47
|
-
<div>
|
|
48
|
-
<p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Default (4 rows)</p>
|
|
49
|
-
{React.createElement('bds-textarea', { placeholder: 'Write something…' })}
|
|
50
|
-
</div>
|
|
51
|
-
<div>
|
|
52
|
-
<p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Tall (8 rows)</p>
|
|
53
|
-
{React.createElement('bds-textarea', { placeholder: 'More space to write…', rows: 8 })}
|
|
54
|
-
</div>
|
|
55
|
-
<div>
|
|
56
|
-
<p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Disabled</p>
|
|
57
|
-
{React.createElement('bds-textarea', { placeholder: 'Read-only area', disabled: true })}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
),
|
|
56
|
+
export const WithValue: Story = {
|
|
57
|
+
args: { name: 'message', value: 'Hello, this is a pre-filled message.' },
|
|
61
58
|
};
|
|
62
59
|
|
|
63
|
-
export const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
value,
|
|
74
|
-
onInput: (e: Event) => setValue((e.target as HTMLTextAreaElement).value),
|
|
75
|
-
})}
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
},
|
|
60
|
+
export const TallRows: Story = {
|
|
61
|
+
args: { name: 'message', placeholder: 'Enter a longer text…', rows: 8 },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const Required: Story = {
|
|
65
|
+
args: { name: 'message', placeholder: 'Required field', required: true },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Disabled: Story = {
|
|
69
|
+
args: { name: 'message', value: 'Read-only content', disabled: true },
|
|
79
70
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../../test/helpers';
|
|
2
|
+
import { BdsCheckboxGroup } from './bds-checkbox-group';
|
|
3
|
+
|
|
4
|
+
describe('bds-checkbox-group', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-checkbox-group')).toBe(BdsCheckboxGroup);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a fieldset', async () => {
|
|
10
|
+
const el = await fixture('<bds-checkbox-group label="Interests"></bds-checkbox-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-checkbox-group label="My Group"></bds-checkbox-group>');
|
|
17
|
+
const legend = el.shadowRoot!.querySelector('legend');
|
|
18
|
+
expect(legend?.textContent?.trim()).toContain('My Group');
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders slotted children', async () => {
|
|
23
|
+
const el = await fixture('<bds-checkbox-group label="Test"><span id="child">item</span></bds-checkbox-group>');
|
|
24
|
+
expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
|
|
25
|
+
cleanup(el);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('disables the fieldset when disabled attribute is set', async () => {
|
|
29
|
+
const el = await fixture('<bds-checkbox-group label="Test" disabled></bds-checkbox-group>');
|
|
30
|
+
const fieldset = el.shadowRoot!.querySelector('fieldset')!;
|
|
31
|
+
expect(fieldset.disabled).toBe(true);
|
|
32
|
+
cleanup(el);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shows required asterisk when required', async () => {
|
|
36
|
+
const el = await fixture('<bds-checkbox-group label="Test" required></bds-checkbox-group>');
|
|
37
|
+
const required = el.shadowRoot!.querySelector('.required');
|
|
38
|
+
expect(required).not.toBeNull();
|
|
39
|
+
cleanup(el);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders error message when error attribute is set', async () => {
|
|
43
|
+
const el = await fixture('<bds-checkbox-group label="Test" error="Select at least one"></bds-checkbox-group>');
|
|
44
|
+
const message = el.shadowRoot!.querySelector('.message--error');
|
|
45
|
+
expect(message?.textContent?.trim()).toBe('Select at least one');
|
|
46
|
+
cleanup(el);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders hint message when hint attribute is set', async () => {
|
|
50
|
+
const el = await fixture('<bds-checkbox-group label="Test" hint="Select all that apply"></bds-checkbox-group>');
|
|
51
|
+
const message = el.shadowRoot!.querySelector('.message--hint');
|
|
52
|
+
expect(message?.textContent?.trim()).toBe('Select all that apply');
|
|
53
|
+
cleanup(el);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-checkbox-group>` — groups related checkboxes with a shared label.
|
|
5
|
+
*
|
|
6
|
+
* Renders a `<fieldset>` with a `<legend>` for accessible grouping.
|
|
7
|
+
*
|
|
8
|
+
* Attributes:
|
|
9
|
+
* label — visible group label (rendered as `<legend>`)
|
|
10
|
+
* error — error message shown below items
|
|
11
|
+
* hint — hint text shown below items
|
|
12
|
+
* disabled — disables the entire fieldset
|
|
13
|
+
* required — marks the group as required
|
|
14
|
+
*
|
|
15
|
+
* Slots:
|
|
16
|
+
* (default) — `<bds-checkbox>` elements
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <bds-checkbox-group label="Interests" required>
|
|
20
|
+
* <bds-checkbox name="sports" value="sports">Sports</bds-checkbox>
|
|
21
|
+
* <bds-checkbox name="music" value="music">Music</bds-checkbox>
|
|
22
|
+
* </bds-checkbox-group>
|
|
23
|
+
*/
|
|
24
|
+
export class BdsCheckboxGroup extends LitElement {
|
|
25
|
+
static styles = css`
|
|
26
|
+
:host {
|
|
27
|
+
display: block;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fieldset {
|
|
31
|
+
border: none;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.legend {
|
|
37
|
+
font-size: var(--bds-font_size--body--s);
|
|
38
|
+
font-family: var(--bds-font_family--body);
|
|
39
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
40
|
+
color: var(--bds-color_on-bg);
|
|
41
|
+
margin-block-end: var(--bds-space_xs);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.required {
|
|
45
|
+
color: var(--bds-color_error);
|
|
46
|
+
margin-inline-start: var(--bds-space_xxxs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.items {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: var(--bds-space_xs);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.message {
|
|
56
|
+
margin-block-start: var(--bds-space_xs);
|
|
57
|
+
font-size: var(--bds-font_size--body--s);
|
|
58
|
+
font-family: var(--bds-font_family--body);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.message--error {
|
|
62
|
+
color: var(--bds-color_error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.message--hint {
|
|
66
|
+
color: var(--bds-color_on-bg--subtle);
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
static properties = {
|
|
71
|
+
label: { type: String },
|
|
72
|
+
error: { type: String },
|
|
73
|
+
hint: { type: String },
|
|
74
|
+
disabled: { type: Boolean, reflect: true },
|
|
75
|
+
required: { type: Boolean, reflect: true },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
declare label: string;
|
|
79
|
+
declare error: string | undefined;
|
|
80
|
+
declare hint: string | undefined;
|
|
81
|
+
declare disabled: boolean;
|
|
82
|
+
declare required: boolean;
|
|
83
|
+
|
|
84
|
+
constructor() {
|
|
85
|
+
super();
|
|
86
|
+
this.label = '';
|
|
87
|
+
this.disabled = false;
|
|
88
|
+
this.required = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
render() {
|
|
92
|
+
return html`
|
|
93
|
+
<fieldset ?disabled=${this.disabled}>
|
|
94
|
+
<legend class="legend">
|
|
95
|
+
${this.label}
|
|
96
|
+
${this.required ? html`<span class="required" aria-hidden="true"> *</span>` : ''}
|
|
97
|
+
</legend>
|
|
98
|
+
<div class="items">
|
|
99
|
+
<slot></slot>
|
|
100
|
+
</div>
|
|
101
|
+
${this.error
|
|
102
|
+
? html`<p class="message message--error" role="alert">${this.error}</p>`
|
|
103
|
+
: this.hint
|
|
104
|
+
? html`<p class="message message--hint">${this.hint}</p>`
|
|
105
|
+
: ''}
|
|
106
|
+
</fieldset>
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
customElements.define('bds-checkbox-group', BdsCheckboxGroup);
|
|
112
|
+
|
|
113
|
+
declare global {
|
|
114
|
+
interface HTMLElementTagNameMap {
|
|
115
|
+
'bds-checkbox-group': BdsCheckboxGroup;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../../test/helpers';
|
|
2
|
+
import { BdsFormInput } from './bds-form-input';
|
|
3
|
+
|
|
4
|
+
describe('bds-form-input', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-form-input')).toBe(BdsFormInput);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('is form-associated', () => {
|
|
10
|
+
expect(BdsFormInput.formAssociated).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders an input element', async () => {
|
|
14
|
+
const el = await fixture('<bds-form-input name="email"></bds-form-input>');
|
|
15
|
+
expect(el.shadowRoot!.querySelector('input')).not.toBeNull();
|
|
16
|
+
cleanup(el);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders a label when label attribute is set', async () => {
|
|
20
|
+
const el = await fixture('<bds-form-input name="email" label="Email"></bds-form-input>');
|
|
21
|
+
expect(el.shadowRoot!.querySelector('label')?.textContent?.trim()).toContain('Email');
|
|
22
|
+
cleanup(el);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('input id matches label for attribute', async () => {
|
|
26
|
+
const el = await fixture('<bds-form-input name="email" label="Email"></bds-form-input>');
|
|
27
|
+
const input = el.shadowRoot!.querySelector('input')!;
|
|
28
|
+
const label = el.shadowRoot!.querySelector('label')!;
|
|
29
|
+
expect(input.id).toBeTruthy();
|
|
30
|
+
expect(label.getAttribute('for')).toBe(input.id);
|
|
31
|
+
cleanup(el);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('defaults type to text', async () => {
|
|
35
|
+
const el = await fixture('<bds-form-input name="search"></bds-form-input>') as BdsFormInput;
|
|
36
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
|
37
|
+
expect(input.type).toBe('text');
|
|
38
|
+
cleanup(el);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('applies the specified type', async () => {
|
|
42
|
+
const el = await fixture('<bds-form-input name="pass" type="password"></bds-form-input>');
|
|
43
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
|
44
|
+
expect(input.type).toBe('password');
|
|
45
|
+
cleanup(el);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('disables input when disabled attribute is set', async () => {
|
|
49
|
+
const el = await fixture('<bds-form-input name="email" disabled></bds-form-input>');
|
|
50
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
|
51
|
+
expect(input.disabled).toBe(true);
|
|
52
|
+
cleanup(el);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders error message and sets aria-invalid when error is set', async () => {
|
|
56
|
+
const el = await fixture('<bds-form-input name="email" error="Invalid email"></bds-form-input>');
|
|
57
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
|
58
|
+
const errorMsg = el.shadowRoot!.querySelector('.message--error');
|
|
59
|
+
expect(input.getAttribute('aria-invalid')).toBe('true');
|
|
60
|
+
expect(errorMsg?.textContent?.trim()).toBe('Invalid email');
|
|
61
|
+
cleanup(el);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('renders hint message when hint is set (no error)', async () => {
|
|
65
|
+
const el = await fixture('<bds-form-input name="email" hint="We will not share your email"></bds-form-input>');
|
|
66
|
+
const hint = el.shadowRoot!.querySelector('.message--hint');
|
|
67
|
+
expect(hint?.textContent?.trim()).toBe('We will not share your email');
|
|
68
|
+
cleanup(el);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('fires bds-change on change', async () => {
|
|
72
|
+
const el = await fixture('<bds-form-input name="email"></bds-form-input>');
|
|
73
|
+
const values: string[] = [];
|
|
74
|
+
el.addEventListener('bds-change', (e: Event) => {
|
|
75
|
+
values.push((e as CustomEvent).detail.value);
|
|
76
|
+
});
|
|
77
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
|
|
78
|
+
Object.defineProperty(input, 'value', { value: 'test@example.com', writable: true });
|
|
79
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
80
|
+
expect(values).toEqual(['test@example.com']);
|
|
81
|
+
cleanup(el);
|
|
82
|
+
});
|
|
83
|
+
});
|