@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
@@ -3,39 +3,70 @@ import * as Stories from './BdsRadio.stories';
3
3
 
4
4
  <Meta of={Stories} />
5
5
 
6
- # Radio
6
+ # &lt;bds-radio&gt;
7
7
 
8
- `<bds-radio>` is a form-associated radio button custom element. Use multiple elements with the same `name` to create a radio group where only one option can be selected.
8
+ Form-associated radio button custom element. Use multiple `<bds-radio>` elements with the same `name` to form a mutually exclusive group. For a labelled group with error/hint support, use `<bds-radio-group>`.
9
9
 
10
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
13
- <bds-radio name="size" value="s">Small</bds-radio>
14
- <bds-radio name="size" value="m" checked>Medium</bds-radio>
15
- <bds-radio name="size" value="l">Large</bds-radio>
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
16
  ```
17
17
 
18
+ ## When to use
19
+
20
+ Use `<bds-radio>` for single-choice selection from a set of options. Always group radios that belong to the same choice under `<bds-radio-group>` for accessibility.
21
+
22
+ ## Examples
23
+
24
+ ### Default
25
+ <Canvas of={Stories.Default} />
26
+
27
+ ### Checked
28
+ <Canvas of={Stories.Checked} />
29
+
30
+ ### Disabled
31
+ <Canvas of={Stories.Disabled} />
32
+
33
+ ## Props
34
+
35
+ <ArgTypes of={Stories} />
36
+
18
37
  ## Attributes
19
38
 
20
- | Attribute | Type | Default | Description |
21
- |------------|-----------|---------|-------------------------------------|
22
- | `name` | `string` | `""` | Groups radio buttons together |
23
- | `value` | `string` | `""` | Submitted value when selected |
24
- | `checked` | `boolean` | `false` | Whether this option is selected |
25
- | `required` | `boolean` | `false` | Marks the field as required |
26
- | `disabled` | `boolean` | `false` | Disables this option |
39
+ | Attribute | Type | Default | Description |
40
+ |------------|---------|---------|--------------------------------------------------|
41
+ | `name` | string | `""` | Form field name (groups radio buttons) |
42
+ | `value` | string | `""` | Value submitted when this option is selected |
43
+ | `checked` | boolean | `false` | Whether this option is selected |
44
+ | `required` | boolean | `false` | Marks the field as required |
45
+ | `disabled` | boolean | `false` | Disables this radio option |
27
46
 
28
47
  ## Slots
29
48
 
30
- | Slot | Description |
31
- |-----------|---------------|
32
- | (default) | Label text |
49
+ | Slot | Description |
50
+ |-------------|--------------------|
51
+ | `(default)` | Label text content |
33
52
 
34
- ## Stories
53
+ ## Events
35
54
 
36
- <Canvas of={Stories.Group} />
37
- <Canvas of={Stories.AllVariants} />
55
+ | Event | Detail | Description |
56
+ |----------|--------|------------------------------------------------|
57
+ | `change` | — | Native change event forwarded from the inner `<input>` |
38
58
 
39
- ## Controls
59
+ ## Usage in plain HTML
40
60
 
41
- <ArgTypes of={Stories} />
61
+ ```html
62
+ <bds-radio name="size" value="s">Small</bds-radio>
63
+ <bds-radio name="size" value="m" checked>Medium</bds-radio>
64
+ <bds-radio name="size" value="l">Large</bds-radio>
65
+ ```
66
+
67
+ ## Accessibility
68
+
69
+ - The inner `<input type="radio">` is associated with a `<label>` via matching `id`/`for` attributes
70
+ - Radios with the same `name` form a mutually exclusive group for keyboard navigation
71
+ - Use `<bds-radio-group>` to wrap related radios in a `<fieldset>`/`<legend>` for proper screen reader announcements
72
+ - Participates in HTML forms via the Form-Associated Custom Elements API
@@ -1,27 +1,32 @@
1
1
  import React from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
- import '../../index';
3
+ import '../../index'; // auto-registers all custom elements
4
4
 
5
5
  function BdsRadio({
6
- label = 'Option',
7
- value = 'option',
6
+ name,
7
+ value,
8
8
  checked,
9
+ required,
9
10
  disabled,
11
+ children,
10
12
  }: {
11
- label?: string;
13
+ name?: string;
12
14
  value?: string;
13
15
  checked?: boolean;
16
+ required?: boolean;
14
17
  disabled?: boolean;
18
+ children?: React.ReactNode;
15
19
  }) {
16
20
  return React.createElement(
17
21
  'bds-radio',
18
22
  {
19
- name: 'demo',
23
+ name,
20
24
  value,
21
25
  checked: checked || undefined,
26
+ required: required || undefined,
22
27
  disabled: disabled || undefined,
23
28
  },
24
- label,
29
+ children,
25
30
  );
26
31
  }
27
32
 
@@ -29,9 +34,10 @@ const meta = {
29
34
  title: 'Web Components/Interaction/Form/Radio',
30
35
  component: BdsRadio,
31
36
  tags: ['!stable', 'experimental'],
32
- parameters: { layout: 'padded' },
37
+ parameters: { layout: 'centered' },
33
38
  argTypes: {
34
39
  checked: { control: 'boolean' },
40
+ required: { control: 'boolean' },
35
41
  disabled: { control: 'boolean' },
36
42
  },
37
43
  } satisfies Meta<typeof BdsRadio>;
@@ -39,52 +45,14 @@ const meta = {
39
45
  export default meta;
40
46
  type Story = StoryObj<typeof meta>;
41
47
 
42
- export const Default: Story = { args: { label: 'Option A', value: 'a' } };
43
- export const Checked: Story = { args: { label: 'Pre-selected', value: 'b', checked: true } };
44
- export const Disabled: Story = { args: { label: 'Disabled option', value: 'c', disabled: true } };
45
-
46
- export const Group: Story = {
47
- render: () => (
48
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
49
- {React.createElement('bds-radio', { name: 'size', value: 's' }, 'Small')}
50
- {React.createElement('bds-radio', { name: 'size', value: 'm', checked: true }, 'Medium')}
51
- {React.createElement('bds-radio', { name: 'size', value: 'l' }, 'Large')}
52
- {React.createElement('bds-radio', { name: 'size', value: 'xl', disabled: true }, 'Extra Large (unavailable)')}
53
- </div>
54
- ),
48
+ export const Default: Story = {
49
+ args: { name: 'size', value: 'm', children: 'Medium' },
55
50
  };
56
51
 
57
- export const AllVariants: Story = {
58
- render: () => (
59
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
60
- {React.createElement('bds-radio', { name: 'v', value: 'a' }, 'Unselected')}
61
- {React.createElement('bds-radio', { name: 'v', value: 'b', checked: true }, 'Selected')}
62
- {React.createElement('bds-radio', { name: 'v', value: 'c', disabled: true }, 'Disabled')}
63
- {React.createElement('bds-radio', { name: 'v', value: 'd', checked: true, disabled: true }, 'Disabled selected')}
64
- </div>
65
- ),
52
+ export const Checked: Story = {
53
+ args: { name: 'size', value: 'm', checked: true, children: 'Medium (checked)' },
66
54
  };
67
55
 
68
- export const Interactive: Story = {
69
- render: () => {
70
- const [value, setValue] = React.useState('m');
71
- return (
72
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
73
- <p style={{ margin: '0 0 8px', fontSize: '0.875rem' }}>Selected: {value}</p>
74
- {(['s', 'm', 'l'] as const).map(v =>
75
- React.createElement(
76
- 'bds-radio',
77
- {
78
- key: v,
79
- name: 'ctrl',
80
- value: v,
81
- checked: value === v || undefined,
82
- onChange: () => setValue(v),
83
- },
84
- v === 's' ? 'Small' : v === 'm' ? 'Medium' : 'Large',
85
- ),
86
- )}
87
- </div>
88
- );
89
- },
56
+ export const Disabled: Story = {
57
+ args: { name: 'size', value: 'm', disabled: true, children: 'Medium (disabled)' },
90
58
  };
@@ -0,0 +1,73 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsRadioGroup.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-radio-group&gt;
7
+
8
+ Groups related radio buttons with a shared label and name. Renders a `<fieldset>` with a `<legend>`, and propagates the `name` attribute to all slotted `<bds-radio>` children so they form a mutually exclusive set.
9
+
10
+ > **Status: experimental** — API may change before stable release.
11
+
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ Use `<bds-radio-group>` whenever you have a set of mutually exclusive options. The `<fieldset>`/`<legend>` structure ensures screen readers announce the group label alongside each radio option.
21
+
22
+ ## Examples
23
+
24
+ ### Default
25
+ <Canvas of={Stories.Default} />
26
+
27
+ ### With error
28
+ <Canvas of={Stories.WithError} />
29
+
30
+ ### With hint
31
+ <Canvas of={Stories.WithHint} />
32
+
33
+ ### Disabled
34
+ <Canvas of={Stories.Disabled} />
35
+
36
+ ## Props
37
+
38
+ <ArgTypes of={Stories} />
39
+
40
+ ## Attributes
41
+
42
+ | Attribute | Type | Default | Description |
43
+ |------------|---------|---------|-------------------------------------------------------------------|
44
+ | `label` | string | `""` | Group label rendered as `<legend>` |
45
+ | `name` | string | `""` | Shared name propagated to all slotted `<bds-radio>` elements |
46
+ | `error` | string | — | Error message shown below items; takes priority over `hint` |
47
+ | `hint` | string | — | Helper text shown below items |
48
+ | `required` | boolean | `false` | Shows a required indicator on the legend |
49
+ | `disabled` | boolean | `false` | Disables the entire fieldset |
50
+
51
+ ## Slots
52
+
53
+ | Slot | Description |
54
+ |-------------|---------------------------|
55
+ | `(default)` | `<bds-radio>` elements |
56
+
57
+ ## Usage in plain HTML
58
+
59
+ ```html
60
+ <bds-radio-group label="Preferred contact" name="contact" required>
61
+ <bds-radio value="email">Email</bds-radio>
62
+ <bds-radio value="phone">Phone</bds-radio>
63
+ <bds-radio value="post">Post</bds-radio>
64
+ </bds-radio-group>
65
+ ```
66
+
67
+ ## Accessibility
68
+
69
+ - Renders a `<fieldset>` with a `<legend>`, which is announced as a group by screen readers
70
+ - The `name` attribute is automatically propagated to all slotted `<bds-radio>` elements
71
+ - `required` adds a visual asterisk to the legend
72
+ - `disabled` on the group disables all contained radios via the native `<fieldset disabled>` behaviour
73
+ - Error messages use `role="alert"` so they are announced immediately
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import '../../index'; // auto-registers all custom elements
4
+
5
+ function BdsRadioGroup({
6
+ label,
7
+ name,
8
+ error,
9
+ hint,
10
+ required,
11
+ disabled,
12
+ children,
13
+ }: {
14
+ label?: string;
15
+ name?: string;
16
+ error?: string;
17
+ hint?: string;
18
+ required?: boolean;
19
+ disabled?: boolean;
20
+ children?: React.ReactNode;
21
+ }) {
22
+ return React.createElement(
23
+ 'bds-radio-group',
24
+ {
25
+ label,
26
+ name,
27
+ error,
28
+ hint,
29
+ required: required || undefined,
30
+ disabled: disabled || undefined,
31
+ },
32
+ children,
33
+ );
34
+ }
35
+
36
+ const meta = {
37
+ title: 'Web Components/Interaction/Form/RadioGroup',
38
+ component: BdsRadioGroup,
39
+ tags: ['!stable', 'experimental'],
40
+ parameters: { layout: 'centered' },
41
+ argTypes: {
42
+ required: { control: 'boolean' },
43
+ disabled: { control: 'boolean' },
44
+ },
45
+ } satisfies Meta<typeof BdsRadioGroup>;
46
+
47
+ export default meta;
48
+ type Story = StoryObj<typeof meta>;
49
+
50
+ export const Default: Story = {
51
+ render: () => (
52
+ <BdsRadioGroup label="Preferred contact" name="contact">
53
+ {React.createElement('bds-radio', { value: 'email' }, 'Email')}
54
+ {React.createElement('bds-radio', { value: 'phone' }, 'Phone')}
55
+ {React.createElement('bds-radio', { value: 'post' }, 'Post')}
56
+ </BdsRadioGroup>
57
+ ),
58
+ };
59
+
60
+ export const WithError: Story = {
61
+ render: () => (
62
+ <BdsRadioGroup label="Preferred contact" name="contact" error="Please select a contact method.">
63
+ {React.createElement('bds-radio', { value: 'email' }, 'Email')}
64
+ {React.createElement('bds-radio', { value: 'phone' }, 'Phone')}
65
+ {React.createElement('bds-radio', { value: 'post' }, 'Post')}
66
+ </BdsRadioGroup>
67
+ ),
68
+ };
69
+
70
+ export const WithHint: Story = {
71
+ render: () => (
72
+ <BdsRadioGroup label="Preferred contact" name="contact" hint="We will only contact you via the selected method.">
73
+ {React.createElement('bds-radio', { value: 'email' }, 'Email')}
74
+ {React.createElement('bds-radio', { value: 'phone' }, 'Phone')}
75
+ {React.createElement('bds-radio', { value: 'post' }, 'Post')}
76
+ </BdsRadioGroup>
77
+ ),
78
+ };
79
+
80
+ export const Disabled: Story = {
81
+ render: () => (
82
+ <BdsRadioGroup label="Preferred contact" name="contact" disabled>
83
+ {React.createElement('bds-radio', { value: 'email' }, 'Email')}
84
+ {React.createElement('bds-radio', { value: 'phone' }, 'Phone')}
85
+ {React.createElement('bds-radio', { value: 'post' }, 'Post')}
86
+ </BdsRadioGroup>
87
+ ),
88
+ };
@@ -3,40 +3,93 @@ import * as Stories from './BdsSegmentedControl.stories';
3
3
 
4
4
  <Meta of={Stories} />
5
5
 
6
- # Segmented Control
6
+ # &lt;bds-segmented-control&gt;
7
7
 
8
- `<bds-segmented-control>` renders a group of radio buttons styled as a unified segmented control with an animated sliding thumb. Uses a JSON `options` attribute.
8
+ Form-associated segmented control. Renders a group of radio buttons (or anchor links) styled as a unified segmented bar. Supports two visual variants: `outline` (sliding inset-border indicator) and `filled` (sliding filled thumb).
9
9
 
10
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
13
- <bds-segmented-control
14
- name="view"
15
- value="list"
16
- options='[{"value":"list","label":"List"},{"value":"grid","label":"Grid"}]'
17
- ></bds-segmented-control>
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
18
16
  ```
19
17
 
18
+ ## When to use
19
+
20
+ Use `<bds-segmented-control>` for mutually exclusive view or mode toggles where all options are visible simultaneously — for example, switching between List, Grid, and Map views. When used with `href` options it renders `<a>` links for navigation.
21
+
22
+ ## Examples
23
+
24
+ ### Default (outline variant)
25
+ <Canvas of={Stories.Default} />
26
+
27
+ ### Filled variant
28
+ <Canvas of={Stories.Filled} />
29
+
30
+ ### With a disabled option
31
+ <Canvas of={Stories.WithDisabledOption} />
32
+
33
+ ### As navigation links
34
+ <Canvas of={Stories.AsNavLinks} />
35
+
36
+ ### Disabled
37
+ <Canvas of={Stories.Disabled} />
38
+
39
+ ## Props
40
+
41
+ <ArgTypes of={Stories} />
42
+
20
43
  ## Attributes
21
44
 
22
- | Attribute | Type | Default | Description |
23
- |------------|-----------|---------|-----------------------------------------------------|
24
- | `name` | `string` | `""` | Form field name (also used for radio group) |
25
- | `value` | `string` | `""` | Currently selected value |
26
- | `options` | `string` | `"[]"` | JSON array of `{ value, label, disabled? }` objects |
27
- | `disabled` | `boolean` | `false` | Disables all options |
45
+ | Attribute | Type | Default | Description |
46
+ |------------|---------------------------|-------------|----------------------------------------------------------------|
47
+ | `name` | string | `""` | Form field name; also used as the internal radio group name |
48
+ | `value` | string | `""` | Currently selected value |
49
+ | `options` | string | `"[]"` | JSON array of `{ value, label, disabled?, href? }` objects |
50
+ | `disabled` | boolean | `false` | Disables all options |
51
+ | `variant` | `"outline" \| "filled"` | `"outline"` | Visual style — sliding inset-border or sliding filled thumb |
28
52
 
29
53
  ## Events
30
54
 
31
- | Event | Description |
32
- |----------|--------------------------------------|
33
- | `change` | Fired when a new segment is selected |
55
+ | Event | Detail | Description |
56
+ |----------|--------|----------------------------------------------------------|
57
+ | `change` | — | Fired when a new segment is selected (radio mode only) |
34
58
 
35
- ## Stories
59
+ ## Usage in plain HTML
36
60
 
37
- <Canvas of={Stories.Default} />
38
- <Canvas of={Stories.AllVariants} />
61
+ ```html
62
+ <!-- Radio mode -->
63
+ <bds-segmented-control
64
+ name="view"
65
+ value="list"
66
+ options='[{"value":"list","label":"List"},{"value":"grid","label":"Grid"}]'
67
+ ></bds-segmented-control>
68
+
69
+ <!-- Link mode -->
70
+ <bds-segmented-control
71
+ variant="outline"
72
+ value="home"
73
+ options='[{"value":"home","label":"Home","href":"/"},{"value":"about","label":"About","href":"/about"}]'
74
+ ></bds-segmented-control>
75
+ ```
39
76
 
40
- ## Controls
77
+ ## CSS Custom Properties
41
78
 
42
- <ArgTypes of={Stories} />
79
+ | Property | Default | Description |
80
+ |----------|---------|-------------|
81
+ | `--segmented_color_bg` | `var(--bds-color_bg--subtle)` | Track background colour (both variants) |
82
+ | `--segmented_item-bg` | `transparent` | Inactive item background (outline variant) |
83
+ | `--segmented_item-bg--active` | `var(--bds-color_bg)` | Active item background (outline variant) |
84
+ | `--segmented_thumb-color` | `var(--bds-color_interactive)` | Filled thumb colour (filled variant) |
85
+ | `--segmented_color` | `var(--bds-color_on-bg)` | Item text colour |
86
+ | `--segmented_color--active` | `var(--bds-color_on-interactive)` (filled) / `var(--bds-color_interactive)` (outline) | Active item text colour |
87
+
88
+ ## Accessibility
89
+
90
+ - In radio mode, each option renders as a visually hidden `<input type="radio">` inside a `<label>`, so keyboard users can navigate with arrow keys
91
+ - In link mode, options render as `<a>` elements with `aria-current="page"` on the active item
92
+ - Disabled options have `aria-disabled="true"` and remove the `href` from link mode items
93
+ - `disabled` on the host element disables all options
94
+ - The sliding thumb and indicator use `aria-hidden="true"` so they are not announced by screen readers
95
+ - Participates in HTML forms via the Form-Associated Custom Elements API (radio mode only)
@@ -1,33 +1,44 @@
1
1
  import React from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
- import type { SegmentedOption } from './bds-segmented-control';
4
- import '../../index';
3
+ import '../../index'; // auto-registers all custom elements
5
4
 
6
- const VIEW_OPTIONS: SegmentedOption[] = [
5
+ const VIEW_OPTIONS = JSON.stringify([
7
6
  { value: 'list', label: 'List' },
8
7
  { value: 'grid', label: 'Grid' },
9
- { value: 'table', label: 'Table' },
10
- ];
8
+ { value: 'map', label: 'Map' },
9
+ ]);
11
10
 
12
- const PERIOD_OPTIONS: SegmentedOption[] = [
13
- { value: 'day', label: 'Day' },
14
- { value: 'week', label: 'Week' },
15
- { value: 'month', label: 'Month' },
16
- { value: 'year', label: 'Year', disabled: true },
17
- ];
11
+ const OPTIONS_WITH_DISABLED = JSON.stringify([
12
+ { value: 'list', label: 'List' },
13
+ { value: 'grid', label: 'Grid', disabled: true },
14
+ { value: 'map', label: 'Map' },
15
+ ]);
16
+
17
+ const NAV_OPTIONS = JSON.stringify([
18
+ { value: 'home', label: 'Home', href: '/' },
19
+ { value: 'about', label: 'About', href: '/about' },
20
+ { value: 'contact', label: 'Contact', href: '/contact' },
21
+ ]);
18
22
 
19
23
  function BdsSegmentedControl({
20
- value = 'list',
24
+ name,
25
+ value,
26
+ options,
21
27
  disabled,
28
+ variant,
22
29
  }: {
30
+ name?: string;
23
31
  value?: string;
32
+ options?: string;
24
33
  disabled?: boolean;
34
+ variant?: 'outline' | 'filled';
25
35
  }) {
26
36
  return React.createElement('bds-segmented-control', {
27
- name: 'view',
37
+ name,
28
38
  value,
29
- options: JSON.stringify(VIEW_OPTIONS),
39
+ options,
30
40
  disabled: disabled || undefined,
41
+ variant,
31
42
  });
32
43
  }
33
44
 
@@ -35,8 +46,9 @@ const meta = {
35
46
  title: 'Web Components/Interaction/Form/SegmentedControl',
36
47
  component: BdsSegmentedControl,
37
48
  tags: ['!stable', 'experimental'],
38
- parameters: { layout: 'padded' },
49
+ parameters: { layout: 'centered' },
39
50
  argTypes: {
51
+ variant: { control: 'select', options: ['outline', 'filled'] },
40
52
  disabled: { control: 'boolean' },
41
53
  },
42
54
  } satisfies Meta<typeof BdsSegmentedControl>;
@@ -44,49 +56,47 @@ const meta = {
44
56
  export default meta;
45
57
  type Story = StoryObj<typeof meta>;
46
58
 
47
- export const Default: Story = { args: { value: 'list' } };
48
- export const Disabled: Story = { args: { value: 'grid', disabled: true } };
59
+ export const Default: Story = {
60
+ args: {
61
+ name: 'view',
62
+ value: 'list',
63
+ options: VIEW_OPTIONS,
64
+ variant: 'outline',
65
+ },
66
+ };
67
+
68
+ export const Filled: Story = {
69
+ args: {
70
+ name: 'view',
71
+ value: 'list',
72
+ options: VIEW_OPTIONS,
73
+ variant: 'filled',
74
+ },
75
+ };
76
+
49
77
  export const WithDisabledOption: Story = {
50
- render: () =>
51
- React.createElement('bds-segmented-control', {
52
- name: 'period',
53
- value: 'week',
54
- options: JSON.stringify(PERIOD_OPTIONS),
55
- }),
78
+ args: {
79
+ name: 'view',
80
+ value: 'list',
81
+ options: OPTIONS_WITH_DISABLED,
82
+ variant: 'outline',
83
+ },
56
84
  };
57
85
 
58
- export const AllVariants: Story = {
59
- render: () => (
60
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
61
- <div>
62
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>View mode</p>
63
- {React.createElement('bds-segmented-control', { name: 'view1', value: 'grid', options: JSON.stringify(VIEW_OPTIONS) })}
64
- </div>
65
- <div>
66
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Time period (year disabled)</p>
67
- {React.createElement('bds-segmented-control', { name: 'period1', value: 'week', options: JSON.stringify(PERIOD_OPTIONS) })}
68
- </div>
69
- <div>
70
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Disabled</p>
71
- {React.createElement('bds-segmented-control', { name: 'view2', value: 'list', options: JSON.stringify(VIEW_OPTIONS), disabled: true })}
72
- </div>
73
- </div>
74
- ),
86
+ export const AsNavLinks: Story = {
87
+ args: {
88
+ value: 'home',
89
+ options: NAV_OPTIONS,
90
+ variant: 'outline',
91
+ },
75
92
  };
76
93
 
77
- export const Interactive: Story = {
78
- render: () => {
79
- const [view, setView] = React.useState('list');
80
- return (
81
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
82
- <p style={{ margin: 0, fontSize: '0.875rem' }}>Active view: {view}</p>
83
- {React.createElement('bds-segmented-control', {
84
- name: 'view-ctrl',
85
- value: view,
86
- options: JSON.stringify(VIEW_OPTIONS),
87
- onChange: (e: Event) => setView((e.target as HTMLInputElement).value),
88
- })}
89
- </div>
90
- );
94
+ export const Disabled: Story = {
95
+ args: {
96
+ name: 'view',
97
+ value: 'list',
98
+ options: VIEW_OPTIONS,
99
+ disabled: true,
100
+ variant: 'outline',
91
101
  },
92
102
  };