@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.
Files changed (168) hide show
  1. package/AGENTS.md +46 -4
  2. package/README.md +15 -15
  3. package/dist/client.cjs +149 -124
  4. package/dist/client.css +503 -525
  5. package/dist/client.d.cts +71 -96
  6. package/dist/client.d.ts +71 -96
  7. package/dist/client.js +149 -124
  8. package/dist/index.cjs +149 -124
  9. package/dist/index.css +503 -525
  10. package/dist/index.d.cts +71 -96
  11. package/dist/index.d.ts +71 -96
  12. package/dist/index.js +149 -124
  13. package/dist/web-components/{chunk-6MH5UWUD.js → chunk-2FGATTGT.js} +5 -0
  14. package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
  15. package/dist/web-components/{chunk-DI46Q2EA.js → chunk-OCODKRVZ.js} +57 -82
  16. package/dist/web-components/index.d.ts +571 -1
  17. package/dist/web-components/index.js +1501 -4
  18. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  19. package/dist/web-components/interaction/bds-drawer.js +1 -1
  20. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  21. package/package.json +1 -1
  22. package/src/components/interaction/Command/Command.tsx +4 -3
  23. package/src/components/interaction/Dialog/Dialog.tsx +4 -5
  24. package/src/components/interaction/Drawer/Drawer.module.css +37 -62
  25. package/src/components/interaction/Drawer/Drawer.spec.tsx +3 -3
  26. package/src/components/interaction/Drawer/Drawer.tsx +25 -24
  27. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +4 -2
  28. package/src/components/interaction/Popover/Popover.tsx +4 -3
  29. package/src/components/interaction/Rating/Rating.tsx +4 -2
  30. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +4 -6
  31. package/src/components/interaction/form/Combobox/Combobox.tsx +4 -2
  32. package/src/components/interaction/form/FileInput/FileInput.tsx +4 -3
  33. package/src/components/interaction/form/NumberInput/NumberInput.tsx +4 -3
  34. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +4 -6
  35. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +8 -4
  36. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +13 -6
  37. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -6
  38. package/src/components/interaction/form/atoms/InputContainer.tsx +2 -2
  39. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +4 -6
  40. package/src/components/layout/Card/Card.tsx +4 -10
  41. package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -5
  42. package/src/components/layout/SectionHeader/SectionHeader.tsx +5 -4
  43. package/src/components/ui/Accordion/Accordion.tsx +4 -3
  44. package/src/components/ui/Alert/Alert.tsx +4 -3
  45. package/src/components/ui/Avatar/Avatar.tsx +5 -3
  46. package/src/components/ui/Badge/Badge.tsx +4 -5
  47. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +4 -3
  48. package/src/components/ui/Calendar/Calendar.tsx +4 -4
  49. package/src/components/ui/Carousel/Carousel.tsx +4 -4
  50. package/src/components/ui/DescriptionList/DescriptionList.tsx +4 -4
  51. package/src/components/ui/Loading/Loading.tsx +4 -3
  52. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +4 -2
  53. package/src/components/ui/Pagination/Pagination.tsx +4 -2
  54. package/src/components/ui/Progress/Progress.tsx +4 -2
  55. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +4 -1
  56. package/src/components/ui/Separator/Separator.tsx +5 -3
  57. package/src/components/ui/Skeleton/Skeleton.tsx +4 -3
  58. package/src/components/ui/SkipLink/SkipLink.tsx +4 -5
  59. package/src/components/ui/Tabs/Tabs.tsx +4 -4
  60. package/src/components/ui/Tooltip/Tooltip.tsx +4 -2
  61. package/src/components/ui/Typography/Typography.tsx +4 -5
  62. package/src/stories/DesignSystem/Grid.mdx +2 -0
  63. package/src/stories/Introduction.mdx +2 -1
  64. package/src/web-components/index.ts +12 -0
  65. package/src/web-components/interaction/BdsAccordion.mdx +80 -28
  66. package/src/web-components/interaction/BdsAccordion.stories.tsx +67 -58
  67. package/src/web-components/interaction/BdsCollapsible.mdx +93 -23
  68. package/src/web-components/interaction/BdsCollapsible.stories.tsx +29 -48
  69. package/src/web-components/interaction/BdsDialog.mdx +88 -27
  70. package/src/web-components/interaction/BdsDialog.stories.tsx +129 -47
  71. package/src/web-components/interaction/BdsDrawer.mdx +85 -27
  72. package/src/web-components/interaction/BdsDrawer.stories.tsx +161 -31
  73. package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
  74. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
  75. package/src/web-components/interaction/BdsSkipLink.mdx +72 -16
  76. package/src/web-components/interaction/BdsSkipLink.stories.tsx +47 -34
  77. package/src/web-components/interaction/BdsTabs.mdx +97 -27
  78. package/src/web-components/interaction/BdsTabs.stories.tsx +69 -55
  79. package/src/web-components/interaction/BdsTooltip.mdx +84 -18
  80. package/src/web-components/interaction/BdsTooltip.stories.tsx +49 -30
  81. package/src/web-components/interaction/bds-collapsible.ts +1 -0
  82. package/src/web-components/interaction/bds-drawer.ts +59 -82
  83. package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
  84. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
  85. package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
  86. package/src/web-components/interaction/form/BdsCheckbox.mdx +57 -20
  87. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +35 -36
  88. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
  89. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
  90. package/src/web-components/interaction/form/BdsCombobox.mdx +52 -33
  91. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +55 -53
  92. package/src/web-components/interaction/form/BdsFileInput.mdx +59 -24
  93. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +38 -53
  94. package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
  95. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
  96. package/src/web-components/interaction/form/BdsNumberInput.mdx +51 -22
  97. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +24 -46
  98. package/src/web-components/interaction/form/BdsRadio.mdx +53 -22
  99. package/src/web-components/interaction/form/BdsRadio.stories.tsx +19 -51
  100. package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
  101. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
  102. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +76 -23
  103. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +64 -54
  104. package/src/web-components/interaction/form/BdsSelect.mdx +59 -21
  105. package/src/web-components/interaction/form/BdsSelect.stories.tsx +47 -56
  106. package/src/web-components/interaction/form/BdsSlider.mdx +55 -27
  107. package/src/web-components/interaction/form/BdsSlider.stories.tsx +23 -44
  108. package/src/web-components/interaction/form/BdsSwitch.mdx +53 -20
  109. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +22 -37
  110. package/src/web-components/interaction/form/BdsTextarea.mdx +54 -22
  111. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +35 -44
  112. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
  113. package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
  114. package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
  115. package/src/web-components/interaction/form/bds-form-input.ts +268 -0
  116. package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
  117. package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
  118. package/src/web-components/interaction/form/bds-segmented-control.ts +5 -0
  119. package/src/web-components/ui/BdsAvatar.mdx +29 -20
  120. package/src/web-components/ui/BdsAvatar.stories.tsx +14 -28
  121. package/src/web-components/ui/BdsBreadcrumb.mdx +22 -19
  122. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +11 -21
  123. package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
  124. package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
  125. package/src/web-components/ui/BdsCalendar.mdx +93 -0
  126. package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
  127. package/src/web-components/ui/BdsCard.mdx +30 -25
  128. package/src/web-components/ui/BdsCard.stories.tsx +19 -42
  129. package/src/web-components/ui/BdsCarousel.mdx +83 -0
  130. package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
  131. package/src/web-components/ui/BdsDescriptionList.mdx +13 -16
  132. package/src/web-components/ui/BdsDescriptionList.stories.tsx +34 -49
  133. package/src/web-components/ui/BdsIconWrapper.mdx +20 -20
  134. package/src/web-components/ui/BdsIconWrapper.stories.tsx +46 -41
  135. package/src/web-components/ui/BdsLink.mdx +21 -17
  136. package/src/web-components/ui/BdsLink.stories.tsx +19 -20
  137. package/src/web-components/ui/BdsLoading.mdx +9 -13
  138. package/src/web-components/ui/BdsLoading.stories.tsx +8 -10
  139. package/src/web-components/ui/BdsNotificationBanner.mdx +32 -37
  140. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +38 -47
  141. package/src/web-components/ui/BdsPagination.mdx +74 -0
  142. package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
  143. package/src/web-components/ui/BdsProgress.mdx +24 -17
  144. package/src/web-components/ui/BdsProgress.stories.tsx +55 -29
  145. package/src/web-components/ui/BdsProgressCircle.mdx +24 -18
  146. package/src/web-components/ui/BdsProgressCircle.stories.tsx +17 -29
  147. package/src/web-components/ui/BdsRating.mdx +22 -22
  148. package/src/web-components/ui/BdsRating.stories.tsx +19 -13
  149. package/src/web-components/ui/BdsSectionHeader.mdx +31 -28
  150. package/src/web-components/ui/BdsSectionHeader.stories.tsx +21 -23
  151. package/src/web-components/ui/BdsSeparator.mdx +22 -8
  152. package/src/web-components/ui/BdsSeparator.stories.tsx +27 -19
  153. package/src/web-components/ui/BdsSkeleton.mdx +20 -26
  154. package/src/web-components/ui/BdsSkeleton.stories.tsx +23 -23
  155. package/src/web-components/ui/BdsTable.mdx +81 -0
  156. package/src/web-components/ui/BdsTable.stories.tsx +83 -0
  157. package/src/web-components/ui/BdsTypography.mdx +20 -29
  158. package/src/web-components/ui/BdsTypography.stories.tsx +19 -18
  159. package/src/web-components/ui/bds-button-group.spec.ts +40 -0
  160. package/src/web-components/ui/bds-button-group.ts +78 -0
  161. package/src/web-components/ui/bds-calendar.spec.ts +91 -0
  162. package/src/web-components/ui/bds-calendar.ts +427 -0
  163. package/src/web-components/ui/bds-carousel.spec.ts +64 -0
  164. package/src/web-components/ui/bds-carousel.ts +296 -0
  165. package/src/web-components/ui/bds-pagination.spec.ts +67 -0
  166. package/src/web-components/ui/bds-pagination.ts +197 -0
  167. package/src/web-components/ui/bds-table.spec.ts +45 -0
  168. package/src/web-components/ui/bds-table.ts +96 -0
@@ -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, onbdstoggle: onToggle },
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 goes here.'),
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 = { args: { summary: 'Show more details' } };
40
- export const OpenByDefault: Story = { args: { summary: 'Open by default', open: true } };
40
+ export const Default: Story = {
41
+ args: {
42
+ summary: 'Show details',
43
+ open: false,
44
+ },
45
+ };
41
46
 
42
- export const Grouped: Story = {
43
- render: () => (
44
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', maxWidth: '480px' }}>
45
- <BdsCollapsible name="faq" summary="What is this component?">
46
- <p style={{ margin: 0 }}>A Collapsible reveals or hides content on demand.</p>
47
- </BdsCollapsible>
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 `&lt;details&gt;/&lt;summary&gt;` 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 AllVariants: Story = {
55
+ export const Grouped: Story = {
59
56
  render: () => (
60
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: '480px' }}>
61
- <BdsCollapsible summary="Closed by default">
62
- <p style={{ margin: 0 }}>This starts closed.</p>
63
- </BdsCollapsible>
64
- <BdsCollapsible summary="Open by default" open>
65
- <p style={{ margin: 0 }}>This starts open.</p>
66
- </BdsCollapsible>
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
- # Dialog
6
+ # &lt;bds-dialog&gt;
7
7
 
8
- `<bds-dialog>` is a modal dialog built on the native `<dialog>` element. It traps focus while open, dismisses on Escape and backdrop click, and restores focus to the trigger element on close. Supports the native Invoker Commands API.
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
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
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
- <!-- Declarative open via Invoker API -->
20
- <button type="button" commandfor="my-dialog" command="show-modal">Open</button>
14
+ ```js
15
+ import '@boostdev/components/web-components';
21
16
  ```
22
17
 
23
- ## Attributes
18
+ Or in HTML without a bundler:
24
19
 
25
- | Attribute | Type | Default | Description |
26
- |-----------|-----------|---------|-----------------------------|
27
- | `open` | `boolean` | `false` | Controls the open state |
20
+ ```html
21
+ <script type="module">
22
+ import '@boostdev/components/web-components';
23
+ </script>
24
+ ```
28
25
 
29
- ## Events
26
+ ## When to use
30
27
 
31
- | Event | Description |
32
- |-------------|---------------------------------------|
33
- | `bds-open` | Fired when the dialog opens |
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
- ## Slots
32
+ ## When not to use
37
33
 
38
- | Slot | Description |
39
- |-----------|-------------------------------------|
40
- | (default) | Dialog content (title, body, actions) |
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
- ## Stories
38
+ ## Examples
43
39
 
40
+ ### Default
44
41
  <Canvas of={Stories.Default} />
45
- <Canvas of={Stories.InvokerAPI} />
46
42
 
47
- ## Controls
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 type="button" onClick={() => setOpen(true)}>Open dialog</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
- { open: open || undefined, onbdsclose: () => setOpen(false) },
13
- React.createElement('h2', { style: { margin: '0 0 12px' } }, title),
14
- withForm
15
- ? React.createElement(
16
- 'form',
17
- { style: { display: 'flex', flexDirection: 'column', gap: '12px' } },
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: BdsDialogStory,
55
+ component: BdsDialog,
46
56
  tags: ['!stable', 'experimental'],
47
57
  parameters: { layout: 'centered' },
48
- } satisfies Meta<typeof BdsDialogStory>;
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 = { args: { withForm: false } };
54
- export const WithForm: Story = { args: { withForm: true } };
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
- export const InvokerAPI: Story = {
57
- render: () => (
58
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '16px' }}>
59
- <p style={{ margin: 0, fontSize: '0.875rem', color: '#666' }}>
60
- Uses native Invoker Commands API — no JavaScript needed.
61
- </p>
62
- {React.createElement('button', { type: 'button', commandfor: 'invoker-dialog', command: 'show-modal' }, 'Open via commandfor')}
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
- { id: 'invoker-dialog' },
66
- React.createElement('h2', { style: { margin: '0 0 12px' } }, 'Opened via Invoker API'),
67
- React.createElement('p', { style: { margin: 0 } }, 'This dialog was opened declaratively using commandfor and command attributes.'),
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 AllVariants: Story = {
74
- render: () => React.createElement(DialogDemo, { title: 'Confirmation' }),
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
  };
@@ -3,48 +3,106 @@ import * as Stories from './BdsDrawer.stories';
3
3
 
4
4
  <Meta of={Stories} />
5
5
 
6
- # Drawer
6
+ # &lt;bds-drawer&gt;
7
7
 
8
- `<bds-drawer>` is a slide-in side panel built on the native `<dialog>` element. It uses the same modal infrastructure as `<bds-dialog>` but positions itself at the edge of the viewport. Supports the native Invoker Commands API.
8
+ Framework-agnostic Drawer (slide-in panel) custom element. Same modal infrastructure as `<bds-dialog>` but positioned as a side panel that slides in from either side of the viewport.
9
9
 
10
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
13
- <bds-drawer id="settings-drawer" side="right">
14
- <span slot="title">Settings</span>
15
- <p>Drawer body content.</p>
16
- </bds-drawer>
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ Or in HTML without a bundler:
17
19
 
18
- <!-- Declarative open via Invoker API -->
19
- <button type="button" commandfor="settings-drawer" command="show-modal">Open settings</button>
20
+ ```html
21
+ <script type="module">
22
+ import '@boostdev/components/web-components';
23
+ </script>
20
24
  ```
21
25
 
26
+ ## When to use
27
+
28
+ - Navigation panels (mobile nav, side menus)
29
+ - Settings or configuration panels that don't interrupt the primary flow
30
+ - Detail views that overlay the current content
31
+
32
+ ## When not to use
33
+
34
+ - Short confirmations — use `<bds-dialog>` instead
35
+ - Persistent side content that is always visible — use a regular sidebar layout
36
+
37
+ ## Examples
38
+
39
+ ### Default (right side)
40
+ <Canvas of={Stories.Default} />
41
+
42
+ ### Left side
43
+ <Canvas of={Stories.LeftSide} />
44
+
45
+ ### With rich content
46
+ <Canvas of={Stories.WithRichContent} />
47
+
48
+ ### All placements
49
+ <Canvas of={Stories.AllPlacements} />
50
+
51
+ ## Props
52
+
53
+ <ArgTypes of={Stories} />
54
+
22
55
  ## Attributes
23
56
 
24
- | Attribute | Type | Default | Description |
25
- |-----------|-------------------------|-----------|-------------------------------|
26
- | `open` | `boolean` | `false` | Controls the open state |
27
- | `side` | `"left" \| "right"` | `"right"` | Which side the drawer slides in from |
57
+ | Attribute | Type | Default | Description |
58
+ |-----------|------|---------|-------------|
59
+ | `open` | boolean | `false` | Controlled open state |
60
+ | `side` | `"left" \| "right"` | `"right"` | Which side the panel slides in from |
28
61
 
29
62
  ## Slots
30
63
 
31
- | Slot | Description |
32
- |-----------|----------------------------------|
33
- | `title` | Drawer title text in the header |
34
- | (default) | Drawer body content |
64
+ | Slot | Description |
65
+ |------|-------------|
66
+ | `title` | Drawer header title text |
67
+ | `(default)` | Drawer body content |
35
68
 
36
69
  ## Events
37
70
 
38
- | Event | Description |
39
- |-------------|---------------------------------|
40
- | `bds-open` | Fired when the drawer opens |
41
- | `bds-close` | Fired when the drawer closes |
71
+ | Event | Bubbles | Description |
72
+ |-------|---------|-------------|
73
+ | `bds-open` | Yes | Fired when the drawer opens |
74
+ | `bds-close` | Yes | Fired when the drawer closes (backdrop click, Escape, or close button) |
42
75
 
43
- ## Stories
76
+ ## Invoker Commands API
44
77
 
45
- <Canvas of={Stories.Right} />
46
- <Canvas of={Stories.InvokerAPI} />
78
+ Open or close the drawer declaratively using the Invoker Commands API:
47
79
 
48
- ## Controls
80
+ ```html
81
+ <button commandfor="my-drawer" command="show-modal">Open</button>
82
+ <bds-drawer id="my-drawer" side="right">
83
+ <span slot="title">Settings</span>
84
+ <p>Drawer content</p>
85
+ </bds-drawer>
86
+ ```
49
87
 
50
- <ArgTypes of={Stories} />
88
+ ## Usage in plain HTML
89
+
90
+ ```html
91
+ <bds-drawer id="my-drawer" side="right">
92
+ <span slot="title">Settings</span>
93
+ <p>Adjust your preferences here.</p>
94
+ </bds-drawer>
95
+
96
+ <button onclick="document.getElementById('my-drawer').open = true">Open settings</button>
97
+ ```
98
+
99
+ ## Accessibility
100
+
101
+ - Renders a native `<dialog>` element with `aria-modal="true"`
102
+ - Focus is trapped inside the drawer while open (Tab / Shift+Tab cycle)
103
+ - Escape key closes the drawer
104
+ - Clicking the backdrop closes the drawer
105
+ - Focus returns to the triggering element on close
106
+ - A close button is provided with `aria-label="Close drawer"`
107
+ - `document.body.overflow` is set to `hidden` while the drawer is open
108
+ - Slide-in animation respects `prefers-reduced-motion`