@boostdev/design-system-components 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +25 -4
- package/dist/client.cjs +58 -54
- package/dist/client.css +503 -525
- package/dist/client.js +58 -54
- package/dist/index.cjs +58 -54
- package/dist/index.css +503 -525
- package/dist/index.js +58 -54
- 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/Drawer/Drawer.module.css +37 -62
- package/src/components/interaction/Drawer/Drawer.tsx +21 -17
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +8 -4
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +13 -6
- 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,44 +3,114 @@ import * as Stories from './BdsCollapsible.stories';
|
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# <bds-collapsible>
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Framework-agnostic Collapsible custom element. Wraps a native `<details>`/`<summary>` pair for built-in toggle behaviour with design-system styling and a `bds-toggle` event.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> **Status: experimental** — API may change before stable release.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import '@boostdev/components/web-components';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or in HTML without a bundler:
|
|
11
19
|
|
|
12
20
|
```html
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</bds-collapsible>
|
|
21
|
+
<script type="module">
|
|
22
|
+
import '@boostdev/components/web-components';
|
|
23
|
+
</script>
|
|
17
24
|
```
|
|
18
25
|
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
- Revealing secondary content on demand (e.g. "More details", "Advanced options")
|
|
29
|
+
- Standalone expandable sections that are independent of each other
|
|
30
|
+
- When accordion-style coordination is not required
|
|
31
|
+
|
|
32
|
+
## When not to use
|
|
33
|
+
|
|
34
|
+
- When multiple related sections need to be coordinated (open one, close others) — use `<bds-accordion>` instead
|
|
35
|
+
- For navigation menus — use a dedicated nav component
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
### Default
|
|
40
|
+
<Canvas of={Stories.Default} />
|
|
41
|
+
|
|
42
|
+
### Open by default
|
|
43
|
+
<Canvas of={Stories.Open} />
|
|
44
|
+
|
|
45
|
+
### Grouped (only one open at a time)
|
|
46
|
+
<Canvas of={Stories.Grouped} />
|
|
47
|
+
|
|
48
|
+
## Props
|
|
49
|
+
|
|
50
|
+
<ArgTypes of={Stories} />
|
|
51
|
+
|
|
19
52
|
## Attributes
|
|
20
53
|
|
|
21
|
-
| Attribute | Type
|
|
22
|
-
|
|
23
|
-
| `open`
|
|
24
|
-
| `name`
|
|
54
|
+
| Attribute | Type | Default | Description |
|
|
55
|
+
|-----------|------|---------|-------------|
|
|
56
|
+
| `open` | boolean | `false` | Whether the panel is currently expanded |
|
|
57
|
+
| `name` | string | `""` | Groups collapsibles so only one in a group is open at a time (native `<details name>` behaviour) |
|
|
25
58
|
|
|
26
59
|
## Slots
|
|
27
60
|
|
|
28
|
-
| Slot
|
|
29
|
-
|
|
30
|
-
| `summary` | The always-visible
|
|
31
|
-
| (default) | The
|
|
61
|
+
| Slot | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `summary` | The always-visible trigger text |
|
|
64
|
+
| `(default)` | The content revealed when expanded |
|
|
32
65
|
|
|
33
66
|
## Events
|
|
34
67
|
|
|
35
|
-
| Event
|
|
36
|
-
|
|
37
|
-
| `bds-toggle` | `{ open: boolean }`
|
|
68
|
+
| Event | Bubbles | Detail | Description |
|
|
69
|
+
|-------|---------|--------|-------------|
|
|
70
|
+
| `bds-toggle` | Yes | `{ open: boolean }` | Fired when the collapsible opens or closes; `detail.open` reflects the new state |
|
|
38
71
|
|
|
39
|
-
##
|
|
72
|
+
## Usage in plain HTML
|
|
40
73
|
|
|
41
|
-
|
|
42
|
-
<
|
|
74
|
+
```html
|
|
75
|
+
<bds-collapsible>
|
|
76
|
+
<span slot="summary">Show details</span>
|
|
77
|
+
<p>Hidden content revealed on toggle.</p>
|
|
78
|
+
</bds-collapsible>
|
|
79
|
+
|
|
80
|
+
<!-- Open by default -->
|
|
81
|
+
<bds-collapsible open>
|
|
82
|
+
<span slot="summary">Open section</span>
|
|
83
|
+
<p>This is visible immediately.</p>
|
|
84
|
+
</bds-collapsible>
|
|
85
|
+
|
|
86
|
+
<!-- Grouped: only one open at a time -->
|
|
87
|
+
<bds-collapsible name="faq">
|
|
88
|
+
<span slot="summary">Question 1</span>
|
|
89
|
+
<p>Answer 1</p>
|
|
90
|
+
</bds-collapsible>
|
|
91
|
+
<bds-collapsible name="faq">
|
|
92
|
+
<span slot="summary">Question 2</span>
|
|
93
|
+
<p>Answer 2</p>
|
|
94
|
+
</bds-collapsible>
|
|
95
|
+
```
|
|
43
96
|
|
|
44
|
-
##
|
|
97
|
+
## CSS Custom Properties
|
|
45
98
|
|
|
46
|
-
|
|
99
|
+
| Property | Default | Description |
|
|
100
|
+
|----------|---------|-------------|
|
|
101
|
+
| `--collapsible_bg` | `var(--bds-color_bg)` | Summary (header) background colour |
|
|
102
|
+
| `--collapsible_bg--hover` | `var(--bds-color_bg--subtle)` | Summary background on hover |
|
|
103
|
+
| `--collapsible_content-bg` | `var(--bds-color_bg)` | Content panel background colour |
|
|
104
|
+
| `--collapsible_color` | `var(--bds-color_on-bg)` | Summary text colour |
|
|
105
|
+
| `--collapsible_on-color` | `var(--bds-color_on-bg)` | Content text colour |
|
|
106
|
+
| `--collapsible_border-color` | `var(--bds-color_bg--subtle)` | Border colour |
|
|
107
|
+
| `--collapsible_border-width` | `1px` | Border width |
|
|
108
|
+
| `--collapsible_border-radius` | `var(--bds-border_radius--m)` | Border radius |
|
|
109
|
+
|
|
110
|
+
## Accessibility
|
|
111
|
+
|
|
112
|
+
- Built on native `<details>`/`<summary>`, which is keyboard accessible out of the box
|
|
113
|
+
- The `summary` slot element is focusable and responds to Enter/Space
|
|
114
|
+
- Focus outline follows design system tokens
|
|
115
|
+
- Chevron icon is `aria-hidden="true"`
|
|
116
|
+
- `prefers-reduced-motion` disables the chevron transition
|
|
@@ -1,25 +1,24 @@
|
|
|
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
|
+
// Thin React wrapper so Storybook controls work with the custom element's props
|
|
5
6
|
function BdsCollapsible({
|
|
6
|
-
open,
|
|
7
|
+
open = false,
|
|
7
8
|
name,
|
|
8
9
|
summary,
|
|
9
10
|
children,
|
|
10
|
-
onToggle,
|
|
11
11
|
}: {
|
|
12
12
|
open?: boolean;
|
|
13
13
|
name?: string;
|
|
14
14
|
summary?: string;
|
|
15
15
|
children?: React.ReactNode;
|
|
16
|
-
onToggle?: (e: Event) => void;
|
|
17
16
|
}) {
|
|
18
17
|
return React.createElement(
|
|
19
18
|
'bds-collapsible',
|
|
20
|
-
{ open: open || undefined, name
|
|
19
|
+
{ open: open || undefined, name: name || undefined },
|
|
21
20
|
React.createElement('span', { slot: 'summary' }, summary ?? 'Toggle section'),
|
|
22
|
-
children ?? React.createElement('p', { style: { margin: 0 } }, 'Collapsible content
|
|
21
|
+
children ?? React.createElement('p', { style: { margin: 0 } }, 'Collapsible panel content.'),
|
|
23
22
|
);
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -30,58 +29,40 @@ const meta = {
|
|
|
30
29
|
parameters: { layout: 'padded' },
|
|
31
30
|
argTypes: {
|
|
32
31
|
open: { control: 'boolean' },
|
|
32
|
+
name: { control: 'text' },
|
|
33
|
+
summary: { control: 'text' },
|
|
33
34
|
},
|
|
34
35
|
} satisfies Meta<typeof BdsCollapsible>;
|
|
35
36
|
|
|
36
37
|
export default meta;
|
|
37
38
|
type Story = StoryObj<typeof meta>;
|
|
38
39
|
|
|
39
|
-
export const Default: Story = {
|
|
40
|
-
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
summary: 'Show details',
|
|
43
|
+
open: false,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
41
46
|
|
|
42
|
-
export const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<BdsCollapsible name="faq" summary="How does grouping work?">
|
|
49
|
-
<p style={{ margin: 0 }}>Setting the same `name` on multiple collapsibles links them — opening one closes the others.</p>
|
|
50
|
-
</BdsCollapsible>
|
|
51
|
-
<BdsCollapsible name="faq" summary="Is it accessible?">
|
|
52
|
-
<p style={{ margin: 0 }}>Yes — built on native `<details>/<summary>` semantics.</p>
|
|
53
|
-
</BdsCollapsible>
|
|
54
|
-
</div>
|
|
55
|
-
),
|
|
47
|
+
export const Open: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
summary: 'Open by default',
|
|
50
|
+
open: true,
|
|
51
|
+
children: React.createElement('p', { style: { margin: 0 } }, 'This collapsible is open by default via the open attribute.'),
|
|
52
|
+
},
|
|
56
53
|
};
|
|
57
54
|
|
|
58
|
-
export const
|
|
55
|
+
export const Grouped: Story = {
|
|
59
56
|
render: () => (
|
|
60
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxWidth: '480px' }}>
|
|
58
|
+
{(['Section A', 'Section B', 'Section C'] as const).map((label, i) =>
|
|
59
|
+
React.createElement(
|
|
60
|
+
'bds-collapsible',
|
|
61
|
+
{ key: label, name: 'group', open: i === 0 ? '' : undefined },
|
|
62
|
+
React.createElement('span', { slot: 'summary' }, label),
|
|
63
|
+
React.createElement('p', { style: { margin: 0 } }, `Content for ${label}. Only one section in this group can be open at a time.`),
|
|
64
|
+
),
|
|
65
|
+
)}
|
|
67
66
|
</div>
|
|
68
67
|
),
|
|
69
68
|
};
|
|
70
|
-
|
|
71
|
-
export const Interactive: Story = {
|
|
72
|
-
render: () => {
|
|
73
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
74
|
-
return (
|
|
75
|
-
<div style={{ maxWidth: '480px' }}>
|
|
76
|
-
<p style={{ margin: '0 0 8px', fontSize: '0.875rem' }}>State: {isOpen ? 'open' : 'closed'}</p>
|
|
77
|
-
<BdsCollapsible
|
|
78
|
-
summary="Controlled collapsible"
|
|
79
|
-
open={isOpen}
|
|
80
|
-
onToggle={(e: Event) => setIsOpen((e as CustomEvent<{ open: boolean }>).detail.open)}
|
|
81
|
-
>
|
|
82
|
-
<p style={{ margin: 0 }}>Controlled via React state. The bds-toggle event updates the parent.</p>
|
|
83
|
-
</BdsCollapsible>
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
};
|
|
@@ -3,47 +3,108 @@ import * as Stories from './BdsDialog.stories';
|
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# <bds-dialog>
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Framework-agnostic Dialog custom element. Wraps a native `<dialog>` element with `showModal()` / `close()` lifecycle, backdrop-click to dismiss, Escape key handling, Tab focus trapping, and the Invoker Commands API.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> **Status: experimental** — API may change before stable release.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
<bds-dialog id="my-dialog">
|
|
14
|
-
<h2>Dialog title</h2>
|
|
15
|
-
<p>Dialog body content.</p>
|
|
16
|
-
<button type="button">Close</button>
|
|
17
|
-
</bds-dialog>
|
|
12
|
+
## Installation
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
```js
|
|
15
|
+
import '@boostdev/components/web-components';
|
|
21
16
|
```
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
Or in HTML without a bundler:
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
```html
|
|
21
|
+
<script type="module">
|
|
22
|
+
import '@boostdev/components/web-components';
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
28
25
|
|
|
29
|
-
##
|
|
26
|
+
## When to use
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
| `bds-close` | Fired when the dialog closes |
|
|
28
|
+
- Confirmations that require a decision before continuing
|
|
29
|
+
- Short forms (edit profile, create item) that interrupt the current flow
|
|
30
|
+
- Alerts or notices that must be acknowledged
|
|
35
31
|
|
|
36
|
-
##
|
|
32
|
+
## When not to use
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
- Complex multi-step flows — consider a dedicated page instead
|
|
35
|
+
- Non-blocking panels — use `<bds-drawer>` or `<bds-popover>` instead
|
|
36
|
+
- Displaying lots of scrollable content — prefer a full page or `<bds-drawer>`
|
|
41
37
|
|
|
42
|
-
##
|
|
38
|
+
## Examples
|
|
43
39
|
|
|
40
|
+
### Default
|
|
44
41
|
<Canvas of={Stories.Default} />
|
|
45
|
-
<Canvas of={Stories.InvokerAPI} />
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
### With form content
|
|
44
|
+
<Canvas of={Stories.WithForm} />
|
|
45
|
+
|
|
46
|
+
### Invoker Commands API
|
|
47
|
+
<Canvas of={Stories.InvokerCommands} />
|
|
48
|
+
|
|
49
|
+
## Props
|
|
48
50
|
|
|
49
51
|
<ArgTypes of={Stories} />
|
|
52
|
+
|
|
53
|
+
## Attributes
|
|
54
|
+
|
|
55
|
+
| Attribute | Type | Default | Description |
|
|
56
|
+
|-----------|------|---------|-------------|
|
|
57
|
+
| `open` | boolean | `false` | Controlled open state — syncs to native `showModal()` / `close()` |
|
|
58
|
+
|
|
59
|
+
## Slots
|
|
60
|
+
|
|
61
|
+
| Slot | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `(default)` | Dialog content |
|
|
64
|
+
|
|
65
|
+
## Events
|
|
66
|
+
|
|
67
|
+
| Event | Bubbles | Description |
|
|
68
|
+
|-------|---------|-------------|
|
|
69
|
+
| `bds-open` | Yes | Fired when the dialog opens |
|
|
70
|
+
| `bds-close` | Yes | Fired when the dialog closes (backdrop click, Escape, or close button) |
|
|
71
|
+
|
|
72
|
+
## Invoker Commands API
|
|
73
|
+
|
|
74
|
+
Open or close the dialog declaratively using the Invoker Commands API:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<button commandfor="my-dialog" command="show-modal">Open</button>
|
|
78
|
+
<button commandfor="my-dialog" command="close">Close</button>
|
|
79
|
+
|
|
80
|
+
<bds-dialog id="my-dialog">
|
|
81
|
+
<p>Dialog content</p>
|
|
82
|
+
</bds-dialog>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The Invoker Commands API is a native browser feature. For browsers that do not support it yet, use the `open` attribute or JavaScript.
|
|
86
|
+
|
|
87
|
+
## Usage in plain HTML
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<bds-dialog id="my-dialog">
|
|
91
|
+
<h2>Confirm deletion</h2>
|
|
92
|
+
<p>This action cannot be undone.</p>
|
|
93
|
+
<div>
|
|
94
|
+
<button onclick="document.getElementById('my-dialog').open = false">Cancel</button>
|
|
95
|
+
<button>Delete</button>
|
|
96
|
+
</div>
|
|
97
|
+
</bds-dialog>
|
|
98
|
+
|
|
99
|
+
<button onclick="document.getElementById('my-dialog').open = true">Delete item</button>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Accessibility
|
|
103
|
+
|
|
104
|
+
- Renders a native `<dialog>` element with `aria-modal="true"`
|
|
105
|
+
- Focus is trapped inside the dialog while open (Tab / Shift+Tab cycle)
|
|
106
|
+
- Escape key closes the dialog
|
|
107
|
+
- Clicking the backdrop closes the dialog
|
|
108
|
+
- Focus returns to the triggering element on close
|
|
109
|
+
- A visually positioned close button is provided with `aria-label="Close dialog"`
|
|
110
|
+
- `document.body.overflow` is set to `hidden` while the dialog is open to prevent background scroll
|
|
@@ -1,75 +1,157 @@
|
|
|
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
|
+
|
|
5
|
+
// Thin React wrapper for bds-dialog + its trigger button
|
|
6
|
+
function BdsDialog({
|
|
7
|
+
open,
|
|
8
|
+
triggerLabel = 'Open dialog',
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
open?: boolean;
|
|
12
|
+
triggerLabel?: string;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
}) {
|
|
15
|
+
const id = React.useId();
|
|
16
|
+
const [isOpen, setIsOpen] = React.useState(open ?? false);
|
|
17
|
+
const dialogRef = React.useRef<HTMLElement>(null);
|
|
18
|
+
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
setIsOpen(open ?? false);
|
|
21
|
+
}, [open]);
|
|
22
|
+
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
const el = dialogRef.current;
|
|
25
|
+
if (!el) return;
|
|
26
|
+
const handler = () => setIsOpen(false);
|
|
27
|
+
el.addEventListener('bds-close', handler);
|
|
28
|
+
return () => el.removeEventListener('bds-close', handler);
|
|
29
|
+
}, []);
|
|
4
30
|
|
|
5
|
-
function DialogDemo({ title = 'Dialog title', withForm = false }: { title?: string; withForm?: boolean }) {
|
|
6
|
-
const [open, setOpen] = React.useState(false);
|
|
7
31
|
return (
|
|
8
32
|
<div>
|
|
9
|
-
<button
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={() => setIsOpen(true)}
|
|
36
|
+
style={{ padding: '0.5rem 1rem', cursor: 'pointer' }}
|
|
37
|
+
>
|
|
38
|
+
{triggerLabel}
|
|
39
|
+
</button>
|
|
10
40
|
{React.createElement(
|
|
11
41
|
'bds-dialog',
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
React.createElement('label', null,
|
|
19
|
-
'Name',
|
|
20
|
-
React.createElement('input', { type: 'text', placeholder: 'Your name', style: { display: 'block', width: '100%', marginTop: '4px' } }),
|
|
21
|
-
),
|
|
22
|
-
React.createElement('label', null,
|
|
23
|
-
'Email',
|
|
24
|
-
React.createElement('input', { type: 'email', placeholder: 'you@example.com', style: { display: 'block', width: '100%', marginTop: '4px' } }),
|
|
25
|
-
),
|
|
26
|
-
)
|
|
27
|
-
: React.createElement('p', { style: { margin: '0 0 12px' } }, 'This is the dialog body. Press Escape or click the × button to close.'),
|
|
28
|
-
React.createElement(
|
|
29
|
-
'div',
|
|
30
|
-
{ style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '12px' } },
|
|
31
|
-
React.createElement('button', { type: 'button', onClick: () => setOpen(false) }, 'Cancel'),
|
|
32
|
-
React.createElement('button', { type: 'button', onClick: () => setOpen(false) }, 'Confirm'),
|
|
33
|
-
),
|
|
42
|
+
{
|
|
43
|
+
id,
|
|
44
|
+
ref: dialogRef,
|
|
45
|
+
open: isOpen || undefined,
|
|
46
|
+
},
|
|
47
|
+
children ?? React.createElement('p', { style: { margin: 0 } }, 'Dialog content goes here.'),
|
|
34
48
|
)}
|
|
35
49
|
</div>
|
|
36
50
|
);
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
function BdsDialogStory({ withForm }: { withForm?: boolean }) {
|
|
40
|
-
return React.createElement(DialogDemo, { withForm });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
53
|
const meta = {
|
|
44
54
|
title: 'Web Components/Interaction/Dialog',
|
|
45
|
-
component:
|
|
55
|
+
component: BdsDialog,
|
|
46
56
|
tags: ['!stable', 'experimental'],
|
|
47
57
|
parameters: { layout: 'centered' },
|
|
48
|
-
|
|
58
|
+
argTypes: {
|
|
59
|
+
triggerLabel: { control: 'text' },
|
|
60
|
+
},
|
|
61
|
+
} satisfies Meta<typeof BdsDialog>;
|
|
49
62
|
|
|
50
63
|
export default meta;
|
|
51
64
|
type Story = StoryObj<typeof meta>;
|
|
52
65
|
|
|
53
|
-
export const Default: Story = {
|
|
54
|
-
|
|
66
|
+
export const Default: Story = {
|
|
67
|
+
args: { triggerLabel: 'Open dialog' },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function WithFormDialog() {
|
|
71
|
+
const id = React.useId();
|
|
72
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
73
|
+
const dialogRef = React.useRef<HTMLElement>(null);
|
|
55
74
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
const el = dialogRef.current;
|
|
77
|
+
if (!el) return;
|
|
78
|
+
const handler = () => setIsOpen(false);
|
|
79
|
+
el.addEventListener('bds-close', handler);
|
|
80
|
+
return () => el.removeEventListener('bds-close', handler);
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={() => setIsOpen(true)}
|
|
88
|
+
style={{ padding: '0.5rem 1rem', cursor: 'pointer' }}
|
|
89
|
+
>
|
|
90
|
+
Open form dialog
|
|
91
|
+
</button>
|
|
63
92
|
{React.createElement(
|
|
64
93
|
'bds-dialog',
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
{
|
|
95
|
+
id,
|
|
96
|
+
ref: dialogRef,
|
|
97
|
+
open: isOpen || undefined,
|
|
98
|
+
},
|
|
99
|
+
React.createElement(
|
|
100
|
+
'form',
|
|
101
|
+
{
|
|
102
|
+
style: { display: 'flex', flexDirection: 'column', gap: '1rem', minWidth: '280px' },
|
|
103
|
+
onSubmit: (e: React.FormEvent) => { e.preventDefault(); setIsOpen(false); },
|
|
104
|
+
},
|
|
105
|
+
React.createElement('h2', { style: { margin: 0 } }, 'Edit profile'),
|
|
106
|
+
React.createElement(
|
|
107
|
+
'label',
|
|
108
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '0.25rem' } },
|
|
109
|
+
'Name',
|
|
110
|
+
React.createElement('input', { type: 'text', defaultValue: 'Jane Doe', style: { padding: '0.5rem' } }),
|
|
111
|
+
),
|
|
112
|
+
React.createElement(
|
|
113
|
+
'label',
|
|
114
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '0.25rem' } },
|
|
115
|
+
'Email',
|
|
116
|
+
React.createElement('input', { type: 'email', defaultValue: 'jane@example.com', style: { padding: '0.5rem' } }),
|
|
117
|
+
),
|
|
118
|
+
React.createElement(
|
|
119
|
+
'div',
|
|
120
|
+
{ style: { display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' } },
|
|
121
|
+
React.createElement('button', { type: 'button', onClick: () => setIsOpen(false) }, 'Cancel'),
|
|
122
|
+
React.createElement('button', { type: 'submit' }, 'Save'),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
68
125
|
)}
|
|
69
126
|
</div>
|
|
70
|
-
)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const WithForm: Story = {
|
|
131
|
+
render: () => React.createElement(WithFormDialog),
|
|
71
132
|
};
|
|
72
133
|
|
|
73
|
-
export const
|
|
74
|
-
render: () =>
|
|
134
|
+
export const InvokerCommands: Story = {
|
|
135
|
+
render: () => {
|
|
136
|
+
const dialogId = 'story-dialog-invoker';
|
|
137
|
+
return (
|
|
138
|
+
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
|
139
|
+
{React.createElement(
|
|
140
|
+
'button',
|
|
141
|
+
{ commandFor: dialogId, command: 'show-modal', style: { padding: '0.5rem 1rem', cursor: 'pointer' } },
|
|
142
|
+
'Open with Invoker Command',
|
|
143
|
+
)}
|
|
144
|
+
{React.createElement(
|
|
145
|
+
'bds-dialog',
|
|
146
|
+
{ id: dialogId },
|
|
147
|
+
React.createElement(
|
|
148
|
+
'div',
|
|
149
|
+
{ style: { minWidth: '260px' } },
|
|
150
|
+
React.createElement('h2', { style: { margin: '0 0 0.5rem' } }, 'Invoker Commands'),
|
|
151
|
+
React.createElement('p', { style: { margin: 0 } }, 'This dialog was opened using the Invoker Commands API (commandfor + command="show-modal").'),
|
|
152
|
+
),
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
},
|
|
75
157
|
};
|