@boostdev/design-system-components 2.6.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +102 -2
- package/dist/client.cjs +257 -219
- package/dist/client.css +587 -539
- package/dist/client.d.cts +27 -1
- package/dist/client.d.ts +27 -1
- package/dist/client.js +220 -183
- package/dist/index.cjs +257 -219
- package/dist/index.css +587 -539
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +220 -183
- package/dist/web-components/{chunk-T6AETZRP.js → chunk-3GFWLSLS.js} +1 -1
- package/dist/web-components/{chunk-N6IMUOY4.js → chunk-65JXQOBA.js} +3 -3
- package/dist/web-components/{chunk-ZTC6GRP7.js → chunk-BKIHQYJR.js} +1 -0
- package/dist/web-components/{chunk-X3FKVHLK.js → chunk-CM3KYZ6Z.js} +1 -1
- package/dist/web-components/{chunk-QLZ2362S.js → chunk-CZBIVR4V.js} +1 -1
- package/dist/web-components/{chunk-GER5UJOF.js → chunk-F5E3IT7G.js} +1 -3
- package/dist/web-components/{chunk-UFFCKVZE.js → chunk-FXURM6D5.js} +1 -1
- package/dist/web-components/{chunk-HHHRF2PS.js → chunk-IYGF6CD5.js} +1 -1
- package/dist/web-components/{chunk-OCODKRVZ.js → chunk-KUI55GFB.js} +2 -2
- package/dist/web-components/{chunk-ZNKEZIYE.js → chunk-NCPQ7AF3.js} +3 -2
- package/dist/web-components/{chunk-RTOS7LLG.js → chunk-PC3IOQAH.js} +3 -8
- package/dist/web-components/{chunk-JUKB3BUP.js → chunk-PXZWYO6Z.js} +1 -1
- package/dist/web-components/{chunk-EBJM3VD4.js → chunk-QBB6ZX4R.js} +1 -1
- package/dist/web-components/{chunk-ECW3HHWA.js → chunk-TQ7IQ4GZ.js} +7 -2
- package/dist/web-components/{chunk-AJSXNDAP.js → chunk-X46KL5BK.js} +19 -18
- package/dist/web-components/{chunk-XNA6WTXG.js → chunk-Y3POAKGS.js} +1 -1
- package/dist/web-components/{chunk-HGBNDR22.js → chunk-Y7V7LM6R.js} +1 -1
- package/dist/web-components/index.d.ts +56 -1
- package/dist/web-components/index.js +140 -19
- package/dist/web-components/interaction/bds-accordion-item.js +1 -1
- package/dist/web-components/interaction/bds-accordion.js +1 -1
- package/dist/web-components/interaction/bds-button.js +1 -1
- package/dist/web-components/interaction/bds-dialog.js +1 -1
- package/dist/web-components/interaction/bds-drawer.js +1 -1
- package/dist/web-components/interaction/bds-popover.js +1 -1
- package/dist/web-components/interaction/bds-tooltip.js +1 -1
- package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
- package/dist/web-components/interaction/form/bds-combobox.js +1 -1
- package/dist/web-components/interaction/form/bds-number-input.js +1 -1
- package/dist/web-components/interaction/form/bds-radio.js +1 -1
- package/dist/web-components/interaction/form/bds-select.js +1 -1
- package/dist/web-components/interaction/form/bds-switch.js +1 -1
- package/dist/web-components/interaction/form/bds-textarea.js +1 -1
- package/dist/web-components/ui/bds-alert.js +1 -1
- package/dist/web-components/ui/bds-card.d.ts +3 -9
- package/dist/web-components/ui/bds-card.js +1 -1
- package/dist/web-components/ui/bds-notification-banner.js +1 -1
- package/package.json +4 -2
- package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +113 -0
- package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +96 -0
- package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +196 -0
- package/src/components/interaction/form/FieldGroup/FieldGroup.stories.tsx +99 -0
- package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +64 -0
- package/src/components/interaction/form/FieldGroup/index.ts +2 -0
- package/src/components/interaction/form/atoms/Message.module.css +4 -0
- package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
- package/src/index.ts +2 -0
- package/src/test/token-contract.spec.ts +86 -0
- package/src/web-components/index.ts +2 -0
- package/src/web-components/interaction/bds-accordion-item.ts +1 -1
- package/src/web-components/interaction/bds-accordion.ts +1 -0
- package/src/web-components/interaction/bds-button.ts +1 -3
- package/src/web-components/interaction/bds-dialog.ts +3 -3
- package/src/web-components/interaction/bds-drawer.ts +2 -2
- package/src/web-components/interaction/bds-popover.ts +7 -2
- package/src/web-components/interaction/bds-tooltip.ts +1 -1
- package/src/web-components/interaction/form/BdsFieldGroup.mdx +67 -0
- package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +110 -0
- package/src/web-components/interaction/form/bds-checkbox-group.ts +1 -0
- package/src/web-components/interaction/form/bds-checkbox.ts +1 -1
- package/src/web-components/interaction/form/bds-combobox.ts +1 -1
- package/src/web-components/interaction/form/bds-field-group.spec.ts +64 -0
- package/src/web-components/interaction/form/bds-field-group.ts +157 -0
- package/src/web-components/interaction/form/bds-form-input.ts +2 -1
- package/src/web-components/interaction/form/bds-number-input.ts +1 -1
- package/src/web-components/interaction/form/bds-radio-group.ts +1 -0
- package/src/web-components/interaction/form/bds-radio.ts +1 -1
- package/src/web-components/interaction/form/bds-select.ts +1 -1
- package/src/web-components/interaction/form/bds-switch.ts +1 -1
- package/src/web-components/interaction/form/bds-textarea.ts +1 -1
- package/src/web-components/ui/BdsCard.mdx +4 -3
- package/src/web-components/ui/BdsCard.stories.tsx +7 -11
- package/src/web-components/ui/BdsSkeleton.stories.tsx +4 -4
- package/src/web-components/ui/bds-alert.ts +3 -2
- package/src/web-components/ui/bds-card.spec.ts +3 -5
- package/src/web-components/ui/bds-card.ts +5 -12
- package/src/web-components/ui/bds-notification-banner.ts +19 -18
- package/src/web-components/ui/bds-pagination.ts +1 -1
- package/src/web-components/ui/bds-table.ts +4 -0
|
@@ -50,10 +50,15 @@ export class BdsPopover extends LitElement {
|
|
|
50
50
|
overflow-y: auto;
|
|
51
51
|
z-index: var(--popover_z-index, 100);
|
|
52
52
|
min-width: 12rem;
|
|
53
|
+
max-width: var(--popover_max-width, 20rem);
|
|
54
|
+
max-height: var(--popover_max-height, 80dvh);
|
|
53
55
|
padding: var(--bds-space_m);
|
|
54
|
-
border
|
|
56
|
+
border: var(--popover_border, none);
|
|
57
|
+
--bdc_color: currentcolor;
|
|
58
|
+
--bdc-outline_radius: var(--bds-border_radius--s);
|
|
59
|
+
border-radius: var(--popover_radius, var(--bdc-outline_radius));
|
|
55
60
|
background-color: var(--popover_color, var(--bds-color_bg));
|
|
56
|
-
box-shadow: var(--popover_shadow, var(--bds-shadow_m));
|
|
61
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--popover_shadow, var(--bds-shadow_m));
|
|
57
62
|
color: var(--popover_on-color, var(--bds-color_on-bg));
|
|
58
63
|
font-size: var(--bds-font_size--body);
|
|
59
64
|
}
|
|
@@ -37,7 +37,7 @@ export class BdsTooltip extends LitElement {
|
|
|
37
37
|
position: absolute;
|
|
38
38
|
z-index: var(--bds-z-index_popover, 200);
|
|
39
39
|
padding: var(--bds-space_xxs) var(--bds-space_xs);
|
|
40
|
-
border-radius: var(--bds-border_radius--
|
|
40
|
+
border-radius: var(--tooltip_radius, var(--bds-border_radius--full));
|
|
41
41
|
background-color: var(--tooltip_bg, var(--bds-color_on-bg));
|
|
42
42
|
color: var(--tooltip_text, var(--bds-color_bg));
|
|
43
43
|
font-size: var(--bds-font_size--body--s);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Meta, Canvas } from '@storybook/blocks';
|
|
2
|
+
import * as Stories from './BdsFieldGroup.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={Stories} />
|
|
5
|
+
|
|
6
|
+
# `<bds-field-group>`
|
|
7
|
+
|
|
8
|
+
Framework-agnostic FieldGroup custom element. Wraps slotted fields in a `<fieldset>` with a styled `<legend>` and lays them out horizontally (with auto-collapse to vertical via CSS container query) or vertically.
|
|
9
|
+
|
|
10
|
+
Mirrors the React `FieldGroup` component — same token surface, same behavior, same auto-derived collapse threshold.
|
|
11
|
+
|
|
12
|
+
## Attributes
|
|
13
|
+
|
|
14
|
+
| Attribute | Values | Default | Description |
|
|
15
|
+
|-----------|--------|---------|-------------|
|
|
16
|
+
| `variant` | `horizontal` \| `vertical` | `horizontal` | Layout direction. |
|
|
17
|
+
| `legend` | string | — | Optional legend text. Falls back to `aria-label` semantics if you set that on the host directly. |
|
|
18
|
+
| `disabled` | boolean | `false` | Forwards to the inner `<fieldset>`; the UA cascades it to descendant form controls (light-DOM children projected through the slot). |
|
|
19
|
+
|
|
20
|
+
## Slots
|
|
21
|
+
|
|
22
|
+
- `(default)` — projected form fields (`<bds-form-input>`, `<bds-select>`, etc.). The component listens for `slotchange` and surfaces the slotted-element count via `--fieldGroup_field-count`, which the auto-derived collapse threshold reads.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Horizontal
|
|
27
|
+
<Canvas of={Stories.Horizontal} />
|
|
28
|
+
|
|
29
|
+
### Vertical
|
|
30
|
+
<Canvas of={Stories.Vertical} />
|
|
31
|
+
|
|
32
|
+
### Horizontal collapses in a narrow container
|
|
33
|
+
<Canvas of={Stories.HorizontalCollapsesInNarrowContainer} />
|
|
34
|
+
|
|
35
|
+
### Without a legend
|
|
36
|
+
<Canvas of={Stories.WithoutLegend} />
|
|
37
|
+
|
|
38
|
+
### Disabled
|
|
39
|
+
<Canvas of={Stories.Disabled} />
|
|
40
|
+
|
|
41
|
+
## CSS variables
|
|
42
|
+
|
|
43
|
+
Same surface as the React component. All public tokens are read with foundation-default fallbacks; consumers can set them on the host (or any ancestor) to theme one or many groups without touching internal classes.
|
|
44
|
+
|
|
45
|
+
### Layout
|
|
46
|
+
|
|
47
|
+
| Variable | Default |
|
|
48
|
+
|----------|---------|
|
|
49
|
+
| `--fieldGroup_gap` | `var(--bds-space_m)` |
|
|
50
|
+
| `--fieldGroup_min-field-width` | `12rem` |
|
|
51
|
+
| `--fieldGroup_collapse-threshold` | `calc(N × min-field-width + (N − 1) × gap)` (computed) |
|
|
52
|
+
|
|
53
|
+
### Legend
|
|
54
|
+
|
|
55
|
+
| Variable | Default |
|
|
56
|
+
|----------|---------|
|
|
57
|
+
| `--fieldGroup_legend-color` | `var(--bds-color_on-bg)` |
|
|
58
|
+
| `--fieldGroup_legend-font-size` | `var(--bds-font_size--heading-3)` |
|
|
59
|
+
| `--fieldGroup_legend-font-weight` | `var(--bds-font_weight--semibold)` |
|
|
60
|
+
| `--fieldGroup_legend-line-height` | `var(--bds-font_line-height--heading)` |
|
|
61
|
+
| `--fieldGroup_legend-padding-block` | `var(--bds-space_xs)` |
|
|
62
|
+
| `--fieldGroup_legend-gap` | `var(--bds-space_m)` |
|
|
63
|
+
| `--fieldGroup_legend-border-block-end` | `1px solid var(--bds-color_bg--subtle)` |
|
|
64
|
+
|
|
65
|
+
## Notes on shadow DOM
|
|
66
|
+
|
|
67
|
+
The component renders its own `<fieldset>` inside the shadow root and projects fields through a `<slot>`. Because slotted elements stay in the host's light DOM, the native `fieldset[disabled]` cascade onto descendants is browser-dependent — modern browsers do propagate disabled to slotted form controls; older ones may not. If you support an older runtime and need a hard guarantee, set `disabled` on each slotted field too.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import '../../index'; // auto-registers all custom elements
|
|
4
|
+
|
|
5
|
+
function BdsFieldGroup({
|
|
6
|
+
legend,
|
|
7
|
+
variant,
|
|
8
|
+
disabled,
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
legend?: string;
|
|
12
|
+
variant?: 'horizontal' | 'vertical';
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
return React.createElement(
|
|
17
|
+
'bds-field-group',
|
|
18
|
+
{
|
|
19
|
+
legend: legend || undefined,
|
|
20
|
+
variant: variant || undefined,
|
|
21
|
+
disabled: disabled || undefined,
|
|
22
|
+
},
|
|
23
|
+
children,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const meta = {
|
|
28
|
+
title: 'Web Components/Form/FieldGroup',
|
|
29
|
+
component: BdsFieldGroup,
|
|
30
|
+
tags: ['!stable', 'experimental'],
|
|
31
|
+
parameters: {
|
|
32
|
+
layout: 'padded',
|
|
33
|
+
docs: {
|
|
34
|
+
description: {
|
|
35
|
+
component:
|
|
36
|
+
'Framework-agnostic FieldGroup. Wraps slotted fields in a `<fieldset>` ' +
|
|
37
|
+
'and lays them out horizontally with a CSS container query that auto-collapses ' +
|
|
38
|
+
'to a single column when the host is too narrow to fit a complete row at minimum field width.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
argTypes: {
|
|
43
|
+
variant: { control: 'select', options: ['horizontal', 'vertical'] },
|
|
44
|
+
legend: { control: 'text' },
|
|
45
|
+
disabled: { control: 'boolean' },
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof BdsFieldGroup>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof meta>;
|
|
51
|
+
|
|
52
|
+
const field = (name: string, label: string, type = 'text') =>
|
|
53
|
+
React.createElement('bds-form-input', { name, label, type });
|
|
54
|
+
|
|
55
|
+
export const Horizontal: Story = {
|
|
56
|
+
args: { legend: 'Contact details', variant: 'horizontal' },
|
|
57
|
+
render: args => (
|
|
58
|
+
<BdsFieldGroup {...args}>
|
|
59
|
+
{field('first', 'First name')}
|
|
60
|
+
{field('last', 'Last name')}
|
|
61
|
+
{field('email', 'Email', 'email')}
|
|
62
|
+
</BdsFieldGroup>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const Vertical: Story = {
|
|
67
|
+
args: { legend: 'Contact details', variant: 'vertical' },
|
|
68
|
+
render: args => (
|
|
69
|
+
<BdsFieldGroup {...args}>
|
|
70
|
+
{field('first', 'First name')}
|
|
71
|
+
{field('last', 'Last name')}
|
|
72
|
+
{field('email', 'Email', 'email')}
|
|
73
|
+
</BdsFieldGroup>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const HorizontalCollapsesInNarrowContainer: Story = {
|
|
78
|
+
name: 'Horizontal — collapses in narrow container',
|
|
79
|
+
args: { legend: 'Same fields, narrower parent', variant: 'horizontal' },
|
|
80
|
+
render: args => (
|
|
81
|
+
<div style={{ inlineSize: '20rem', border: '1px dashed currentcolor', padding: '1rem' }}>
|
|
82
|
+
<BdsFieldGroup {...args}>
|
|
83
|
+
{field('first', 'First name')}
|
|
84
|
+
{field('last', 'Last name')}
|
|
85
|
+
{field('email', 'Email', 'email')}
|
|
86
|
+
</BdsFieldGroup>
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const WithoutLegend: Story = {
|
|
92
|
+
args: { variant: 'horizontal' },
|
|
93
|
+
render: args => (
|
|
94
|
+
<BdsFieldGroup {...args}>
|
|
95
|
+
{field('q', 'Query')}
|
|
96
|
+
{field('from', 'From', 'date')}
|
|
97
|
+
{field('to', 'To', 'date')}
|
|
98
|
+
</BdsFieldGroup>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const Disabled: Story = {
|
|
103
|
+
args: { legend: 'Disabled section', variant: 'horizontal', disabled: true },
|
|
104
|
+
render: args => (
|
|
105
|
+
<BdsFieldGroup {...args}>
|
|
106
|
+
{field('first', 'First name')}
|
|
107
|
+
{field('last', 'Last name')}
|
|
108
|
+
</BdsFieldGroup>
|
|
109
|
+
),
|
|
110
|
+
};
|
|
@@ -49,7 +49,7 @@ export class BdsCheckbox extends LitElement {
|
|
|
49
49
|
inline-size: var(--inputSize);
|
|
50
50
|
block-size: var(--inputSize);
|
|
51
51
|
flex-shrink: 0;
|
|
52
|
-
--bdc_color: currentcolor;
|
|
52
|
+
--bdc_color: var(--checkbox_border-color, currentcolor);
|
|
53
53
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
54
54
|
|
|
55
55
|
border-radius: var(--checkbox_radius, var(--bdc-outline_radius));
|
|
@@ -54,7 +54,7 @@ export class BdsCombobox extends LitElement {
|
|
|
54
54
|
padding: var(--bds-space_s);
|
|
55
55
|
padding-inline-end: var(--bds-space_xl);
|
|
56
56
|
border: none;
|
|
57
|
-
--bdc_color: currentcolor;
|
|
57
|
+
--bdc_color: var(--combobox_border-color, currentcolor);
|
|
58
58
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
59
59
|
|
|
60
60
|
border-radius: var(--combobox_radius, var(--bdc-outline_radius));
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../../test/helpers';
|
|
2
|
+
import { BdsFieldGroup } from './bds-field-group';
|
|
3
|
+
|
|
4
|
+
describe('bds-field-group', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-field-group')).toBe(BdsFieldGroup);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a fieldset in the shadow DOM', async () => {
|
|
10
|
+
const el = await fixture('<bds-field-group></bds-field-group>');
|
|
11
|
+
expect(el.shadowRoot!.querySelector('fieldset')).not.toBeNull();
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders the legend when the legend attribute is set', async () => {
|
|
16
|
+
const el = await fixture('<bds-field-group legend="Contact details"></bds-field-group>');
|
|
17
|
+
const legend = el.shadowRoot!.querySelector('legend');
|
|
18
|
+
expect(legend?.textContent?.trim()).toBe('Contact details');
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('omits the legend element when no legend attribute is set', async () => {
|
|
23
|
+
const el = await fixture('<bds-field-group></bds-field-group>');
|
|
24
|
+
expect(el.shadowRoot!.querySelector('legend')).toBeNull();
|
|
25
|
+
cleanup(el);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reflects the default horizontal variant onto the host', async () => {
|
|
29
|
+
const el = await fixture('<bds-field-group></bds-field-group>') as BdsFieldGroup;
|
|
30
|
+
expect(el.getAttribute('variant')).toBe('horizontal');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('honors variant="vertical" on the host', async () => {
|
|
34
|
+
const el = await fixture('<bds-field-group variant="vertical"></bds-field-group>') as BdsFieldGroup;
|
|
35
|
+
expect(el.getAttribute('variant')).toBe('vertical');
|
|
36
|
+
cleanup(el);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('forwards the disabled attribute to the inner fieldset', async () => {
|
|
40
|
+
const el = await fixture('<bds-field-group disabled></bds-field-group>');
|
|
41
|
+
const fieldset = el.shadowRoot!.querySelector('fieldset') as HTMLFieldSetElement;
|
|
42
|
+
expect(fieldset.disabled).toBe(true);
|
|
43
|
+
cleanup(el);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders a slot for projected fields', async () => {
|
|
47
|
+
const el = await fixture(
|
|
48
|
+
'<bds-field-group><span id="a"></span><span id="b"></span></bds-field-group>',
|
|
49
|
+
);
|
|
50
|
+
expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
|
|
51
|
+
expect(el.querySelectorAll('span').length).toBe(2);
|
|
52
|
+
cleanup(el);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exposes the slotted-element count as --fieldGroup_field-count after slotchange', async () => {
|
|
56
|
+
const el = await fixture(
|
|
57
|
+
'<bds-field-group><span></span><span></span><span></span></bds-field-group>',
|
|
58
|
+
) as BdsFieldGroup;
|
|
59
|
+
// slotchange fires synchronously after the initial render; allow a microtask.
|
|
60
|
+
await new Promise(r => requestAnimationFrame(() => r(null)));
|
|
61
|
+
expect(el.style.getPropertyValue('--fieldGroup_field-count')).toBe('3');
|
|
62
|
+
cleanup(el);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
+
|
|
3
|
+
export type FieldGroupVariant = 'horizontal' | 'vertical';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `<bds-field-group>` — framework-agnostic FieldGroup custom element.
|
|
7
|
+
*
|
|
8
|
+
* Semantically groups related form fields under a single `<fieldset>` and
|
|
9
|
+
* lays them out either horizontally (with a CSS container query that
|
|
10
|
+
* collapses to a vertical stack when the FieldGroup itself is too narrow)
|
|
11
|
+
* or vertically. Mirrors the React `FieldGroup` component.
|
|
12
|
+
*
|
|
13
|
+
* Attributes:
|
|
14
|
+
* variant — "horizontal" (default) | "vertical"
|
|
15
|
+
* legend — optional legend text rendered above the fields
|
|
16
|
+
* disabled — boolean; forwarded to the inner <fieldset>
|
|
17
|
+
*
|
|
18
|
+
* Slots:
|
|
19
|
+
* (default) — form fields (any element). The slotted-element count is
|
|
20
|
+
* measured on each `slotchange` and surfaced via the
|
|
21
|
+
* `--fieldGroup_field-count` custom property, which the auto-derived
|
|
22
|
+
* collapse threshold reads.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <bds-field-group variant="horizontal" legend="Contact details">
|
|
26
|
+
* <bds-form-input label="First name" name="first"></bds-form-input>
|
|
27
|
+
* <bds-form-input label="Last name" name="last"></bds-form-input>
|
|
28
|
+
* <bds-form-input label="Email" name="email"></bds-form-input>
|
|
29
|
+
* </bds-field-group>
|
|
30
|
+
*/
|
|
31
|
+
export class BdsFieldGroup extends LitElement {
|
|
32
|
+
static styles = css`
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
|
|
36
|
+
/* Establish an inline-size containment context so the @container rule
|
|
37
|
+
queries the host's own width — independent of the viewport. */
|
|
38
|
+
container-type: inline-size;
|
|
39
|
+
container-name: field-group;
|
|
40
|
+
|
|
41
|
+
/* Auto-derived collapse threshold (mirror of the React component).
|
|
42
|
+
--fieldGroup_field-count is set inline by _onSlotChange; the other
|
|
43
|
+
tokens use foundation-default fallbacks so the calc rebuilds when
|
|
44
|
+
a consumer overrides --fieldGroup_min-field-width or _gap. */
|
|
45
|
+
--fieldGroup_collapse-threshold:
|
|
46
|
+
calc(
|
|
47
|
+
var(--fieldGroup_field-count, 1)
|
|
48
|
+
* var(--fieldGroup_min-field-width, 12rem)
|
|
49
|
+
+ (var(--fieldGroup_field-count, 1) - 1)
|
|
50
|
+
* var(--fieldGroup_gap, var(--bds-space_m))
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fieldset {
|
|
55
|
+
border: 0;
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 0;
|
|
58
|
+
min-inline-size: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.legend {
|
|
62
|
+
inline-size: 100%;
|
|
63
|
+
padding-inline: 0;
|
|
64
|
+
|
|
65
|
+
padding-block:
|
|
66
|
+
var(--fieldGroup_legend-padding-block, var(--bds-space_xs));
|
|
67
|
+
margin-block-end:
|
|
68
|
+
var(--fieldGroup_legend-gap, var(--bds-space_m));
|
|
69
|
+
border-block-end:
|
|
70
|
+
var(--fieldGroup_legend-border-block-end, 1px solid var(--bds-color_bg--subtle));
|
|
71
|
+
|
|
72
|
+
color:
|
|
73
|
+
var(--fieldGroup_legend-color, var(--bds-color_on-bg));
|
|
74
|
+
font-family: var(--bds-font_family--body);
|
|
75
|
+
font-size:
|
|
76
|
+
var(--fieldGroup_legend-font-size, var(--bds-font_size--heading-3));
|
|
77
|
+
font-weight:
|
|
78
|
+
var(--fieldGroup_legend-font-weight, var(--bds-font_weight--semibold));
|
|
79
|
+
line-height:
|
|
80
|
+
var(--fieldGroup_legend-line-height, var(--bds-font_line-height--heading));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.fields {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
gap: var(--fieldGroup_gap, var(--bds-space_m));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
:host([variant='horizontal']) .fields {
|
|
90
|
+
flex-flow: row wrap;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
:host([variant='horizontal']) .fields ::slotted(*) {
|
|
94
|
+
flex: 1 1 var(--fieldGroup_min-field-width, 12rem);
|
|
95
|
+
min-inline-size: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Auto-collapse: when the host's inline-size can't fit a complete
|
|
99
|
+
horizontal row, switch to a single-column stack. Threshold scales
|
|
100
|
+
with the actual slotted-element count (see :host calc above). */
|
|
101
|
+
@container field-group (max-inline-size: var(--fieldGroup_collapse-threshold)) {
|
|
102
|
+
:host([variant='horizontal']) .fields {
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:host([variant='horizontal']) .fields ::slotted(*) {
|
|
107
|
+
flex: 1 1 auto;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
static properties = {
|
|
113
|
+
variant: { type: String, reflect: true },
|
|
114
|
+
legend: { type: String },
|
|
115
|
+
disabled: { type: Boolean, reflect: true },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare variant: FieldGroupVariant;
|
|
119
|
+
declare legend: string;
|
|
120
|
+
declare disabled: boolean;
|
|
121
|
+
|
|
122
|
+
constructor() {
|
|
123
|
+
super();
|
|
124
|
+
this.variant = 'horizontal';
|
|
125
|
+
this.legend = '';
|
|
126
|
+
this.disabled = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Arrow-bound for stable identity (unused here but matches the
|
|
130
|
+
// bds-popover / bds-button conventions for handlers attached in render).
|
|
131
|
+
private _onSlotChange = (e: Event) => {
|
|
132
|
+
const slot = e.target as HTMLSlotElement;
|
|
133
|
+
const count = slot.assignedElements().length;
|
|
134
|
+
this.style.setProperty('--fieldGroup_field-count', String(count));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
render() {
|
|
138
|
+
return html`
|
|
139
|
+
<fieldset ?disabled=${this.disabled}>
|
|
140
|
+
${this.legend
|
|
141
|
+
? html`<legend class="legend">${this.legend}</legend>`
|
|
142
|
+
: nothing}
|
|
143
|
+
<div class="fields">
|
|
144
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
145
|
+
</div>
|
|
146
|
+
</fieldset>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
customElements.define('bds-field-group', BdsFieldGroup);
|
|
152
|
+
|
|
153
|
+
declare global {
|
|
154
|
+
interface HTMLElementTagNameMap {
|
|
155
|
+
'bds-field-group': BdsFieldGroup;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -58,7 +58,7 @@ export class BdsFormInput extends LitElement {
|
|
|
58
58
|
position: relative;
|
|
59
59
|
display: flex;
|
|
60
60
|
align-items: center;
|
|
61
|
-
--bdc_color: currentcolor;
|
|
61
|
+
--bdc_color: var(--formInput_border-color, currentcolor);
|
|
62
62
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
63
63
|
|
|
64
64
|
border-radius: var(--formInput_radius, var(--bdc-outline_radius));
|
|
@@ -138,6 +138,7 @@ export class BdsFormInput extends LitElement {
|
|
|
138
138
|
|
|
139
139
|
.message--hint {
|
|
140
140
|
color: var(--bds-color_on-bg--subtle);
|
|
141
|
+
font-style: italic;
|
|
141
142
|
}
|
|
142
143
|
`;
|
|
143
144
|
|
|
@@ -42,7 +42,7 @@ export class BdsNumberInput extends LitElement {
|
|
|
42
42
|
font-size: var(--bds-font_size--body);
|
|
43
43
|
padding: var(--bds-space_s);
|
|
44
44
|
border: none;
|
|
45
|
-
--bdc_color: currentcolor;
|
|
45
|
+
--bdc_color: var(--number-input_border-color, currentcolor);
|
|
46
46
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
47
47
|
|
|
48
48
|
border-radius: var(--number-input_radius, var(--bdc-outline_radius));
|
|
@@ -47,7 +47,7 @@ export class BdsRadio extends LitElement {
|
|
|
47
47
|
inline-size: var(--inputSize);
|
|
48
48
|
block-size: var(--inputSize);
|
|
49
49
|
flex-shrink: 0;
|
|
50
|
-
--bdc_color: currentcolor;
|
|
50
|
+
--bdc_color: var(--radio_border-color, currentcolor);
|
|
51
51
|
--bdc-outline_radius: 50%;
|
|
52
52
|
|
|
53
53
|
border-radius: 50%;
|
|
@@ -55,7 +55,7 @@ export class BdsSelect extends LitElement {
|
|
|
55
55
|
padding: var(--bds-space_s);
|
|
56
56
|
padding-inline-end: var(--bds-space_xl);
|
|
57
57
|
border: none;
|
|
58
|
-
--bdc_color: currentcolor;
|
|
58
|
+
--bdc_color: var(--select_border-color, currentcolor);
|
|
59
59
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
60
60
|
|
|
61
61
|
border-radius: var(--select_radius, var(--bdc-outline_radius));
|
|
@@ -46,7 +46,7 @@ export class BdsSwitch extends LitElement {
|
|
|
46
46
|
block-size: 1.5rem;
|
|
47
47
|
border-radius: 999px;
|
|
48
48
|
background-color: var(--switch_color_bg, var(--bds-color_bg--subtle));
|
|
49
|
-
--bdc_color: currentcolor;
|
|
49
|
+
--bdc_color: var(--switch_border-color, currentcolor);
|
|
50
50
|
|
|
51
51
|
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
|
|
52
52
|
transition: background-color 0.2s ease;
|
|
@@ -41,7 +41,7 @@ export class BdsTextarea extends LitElement {
|
|
|
41
41
|
line-height: var(--bds-font_line-height--body);
|
|
42
42
|
padding: var(--bds-space_s);
|
|
43
43
|
border: none;
|
|
44
|
-
--bdc_color: currentcolor;
|
|
44
|
+
--bdc_color: var(--textarea_border-color, currentcolor);
|
|
45
45
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
46
46
|
|
|
47
47
|
border-radius: var(--textarea_radius, var(--bdc-outline_radius));
|
|
@@ -5,7 +5,7 @@ import * as Stories from './BdsCard.stories';
|
|
|
5
5
|
|
|
6
6
|
# <bds-card>
|
|
7
7
|
|
|
8
|
-
Framework-agnostic Card layout custom element. A versatile container with three visual variants,
|
|
8
|
+
Framework-agnostic Card layout custom element. A versatile container with three visual variants, text alignment, and an optional clickable mode. Padding is controlled via the `--card_padding` CSS custom property.
|
|
9
9
|
|
|
10
10
|
> **Status: experimental** — API may change before stable release.
|
|
11
11
|
|
|
@@ -46,7 +46,6 @@ import '@boostdev/design-system-components/web-components';
|
|
|
46
46
|
| Attribute | Type | Default | Description |
|
|
47
47
|
|-----------|------|---------|-------------|
|
|
48
48
|
| `variant` | `"default" \| "elevated" \| "outlined"` | `"default"` | Shadow and border style |
|
|
49
|
-
| `padding` | `"none" \| "small" \| "medium" \| "large"` | `"medium"` | Inner padding |
|
|
50
49
|
| `text-align` | `"start" \| "center" \| "end"` | `"start"` | Text alignment |
|
|
51
50
|
| `clickable` | boolean | `false` | Renders the card as a `<button>` with hover/active effects |
|
|
52
51
|
| `aria-label` | string | — | Accessible label (recommended when `clickable` is set) |
|
|
@@ -67,13 +66,15 @@ import '@boostdev/design-system-components/web-components';
|
|
|
67
66
|
|
|
68
67
|
| Property | Description |
|
|
69
68
|
|----------|-------------|
|
|
69
|
+
| `--card_padding` | Inner padding (default: `var(--bds-space_m)`) |
|
|
70
70
|
| `--card_color` | Card background colour |
|
|
71
71
|
| `--card_on-color` | Card text/foreground colour |
|
|
72
|
+
| `--card_border` | Border on outlined variant (default: `none`) |
|
|
72
73
|
|
|
73
74
|
## Usage in plain HTML
|
|
74
75
|
|
|
75
76
|
```html
|
|
76
|
-
<bds-card variant="elevated"
|
|
77
|
+
<bds-card variant="elevated" style="--card_padding: var(--bds-space_l)">
|
|
77
78
|
<p>Card content</p>
|
|
78
79
|
</bds-card>
|
|
79
80
|
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import type { CardVariant,
|
|
3
|
+
import type { CardVariant, CardTextAlign } from './bds-card';
|
|
4
4
|
import '../index'; // auto-registers all custom elements
|
|
5
5
|
|
|
6
6
|
function BdsCard({
|
|
7
7
|
variant = 'default',
|
|
8
|
-
padding = 'medium',
|
|
9
8
|
textAlign = 'start',
|
|
10
9
|
clickable,
|
|
11
10
|
ariaLabel,
|
|
12
11
|
children,
|
|
13
12
|
}: {
|
|
14
13
|
variant?: CardVariant;
|
|
15
|
-
padding?: CardPadding;
|
|
16
14
|
textAlign?: CardTextAlign;
|
|
17
15
|
clickable?: boolean;
|
|
18
16
|
ariaLabel?: string;
|
|
@@ -22,7 +20,6 @@ function BdsCard({
|
|
|
22
20
|
'bds-card',
|
|
23
21
|
{
|
|
24
22
|
variant,
|
|
25
|
-
padding,
|
|
26
23
|
'text-align': textAlign,
|
|
27
24
|
clickable: clickable || undefined,
|
|
28
25
|
'aria-label': ariaLabel,
|
|
@@ -38,7 +35,6 @@ const meta = {
|
|
|
38
35
|
parameters: { layout: 'centered' },
|
|
39
36
|
argTypes: {
|
|
40
37
|
variant: { control: 'select', options: ['default', 'elevated', 'outlined'] },
|
|
41
|
-
padding: { control: 'select', options: ['none', 'small', 'medium', 'large'] },
|
|
42
38
|
textAlign: { control: 'select', options: ['start', 'center', 'end'] },
|
|
43
39
|
clickable: { control: 'boolean' },
|
|
44
40
|
},
|
|
@@ -47,19 +43,19 @@ const meta = {
|
|
|
47
43
|
export default meta;
|
|
48
44
|
type Story = StoryObj<typeof meta>;
|
|
49
45
|
|
|
50
|
-
export const Default: Story = { args: { variant: 'default',
|
|
51
|
-
export const Elevated: Story = { args: { variant: 'elevated',
|
|
52
|
-
export const Outlined: Story = { args: { variant: 'outlined',
|
|
46
|
+
export const Default: Story = { args: { variant: 'default', children: 'Card content goes here.' } };
|
|
47
|
+
export const Elevated: Story = { args: { variant: 'elevated', children: 'Elevated card.' } };
|
|
48
|
+
export const Outlined: Story = { args: { variant: 'outlined', children: 'Outlined card.' } };
|
|
53
49
|
export const Clickable: Story = {
|
|
54
|
-
args: { variant: 'default',
|
|
50
|
+
args: { variant: 'default', clickable: true, ariaLabel: 'Open details', children: 'Clickable card — hover me.' },
|
|
55
51
|
};
|
|
56
52
|
|
|
57
53
|
export const AllVariants: Story = {
|
|
58
54
|
render: () => (
|
|
59
55
|
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', maxWidth: 600 }}>
|
|
60
56
|
{(['default', 'elevated', 'outlined'] as const).map(v =>
|
|
61
|
-
React.createElement('bds-card', { key: v, variant: v,
|
|
62
|
-
React.createElement('p', { style:
|
|
57
|
+
React.createElement('bds-card', { key: v, variant: v, style: { width: 160 } },
|
|
58
|
+
React.createElement('p', { style: { margin: 0 } }, v),
|
|
63
59
|
),
|
|
64
60
|
)}
|
|
65
61
|
</div>
|
|
@@ -41,10 +41,10 @@ export const Avatar: Story = {
|
|
|
41
41
|
export const ContentBlock: Story = {
|
|
42
42
|
render: () => (
|
|
43
43
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: 300 }}>
|
|
44
|
-
{React.createElement('bds-skeleton', { style:
|
|
45
|
-
{React.createElement('bds-skeleton', { style:
|
|
46
|
-
{React.createElement('bds-skeleton', { style:
|
|
47
|
-
{React.createElement('bds-skeleton', { style:
|
|
44
|
+
{React.createElement('bds-skeleton', { style: { width: '100%', height: '1.5rem' } })}
|
|
45
|
+
{React.createElement('bds-skeleton', { style: { width: '80%', height: '1rem' } })}
|
|
46
|
+
{React.createElement('bds-skeleton', { style: { width: '90%', height: '1rem' } })}
|
|
47
|
+
{React.createElement('bds-skeleton', { style: { width: '60%', height: '1rem' } })}
|
|
48
48
|
</div>
|
|
49
49
|
),
|
|
50
50
|
};
|
|
@@ -112,7 +112,8 @@ export class BdsAlert extends LitElement {
|
|
|
112
112
|
cursor: pointer;
|
|
113
113
|
color: currentcolor;
|
|
114
114
|
transition: var(--bds-animation_transition);
|
|
115
|
-
border
|
|
115
|
+
border: var(--alert_dismiss-border, none);
|
|
116
|
+
border-radius: var(--alert_dismiss-radius, var(--bds-border_radius--circle));
|
|
116
117
|
--bdc_color: currentcolor;
|
|
117
118
|
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
|
|
118
119
|
}
|
|
@@ -129,7 +130,7 @@ export class BdsAlert extends LitElement {
|
|
|
129
130
|
|
|
130
131
|
@media (hover: hover) and (pointer: fine) {
|
|
131
132
|
.dismiss:hover {
|
|
132
|
-
opacity: 0.7;
|
|
133
|
+
opacity: var(--alert_dismiss-opacity--hover, 0.7);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
`;
|