@boostdev/design-system-components 0.1.13 → 0.1.14

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 (121) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/client.cjs +54 -50
  4. package/dist/client.css +495 -511
  5. package/dist/client.d.cts +5 -3
  6. package/dist/client.d.ts +5 -3
  7. package/dist/client.js +54 -50
  8. package/dist/index.cjs +54 -50
  9. package/dist/index.css +495 -511
  10. package/dist/index.d.cts +5 -3
  11. package/dist/index.d.ts +5 -3
  12. package/dist/index.js +54 -50
  13. package/package.json +4 -2
  14. package/src/components/interaction/Button/Button.mdx +73 -0
  15. package/src/components/interaction/Button/Button.module.css +12 -20
  16. package/src/components/interaction/Button/Button.stories.tsx +0 -1
  17. package/src/components/interaction/Command/Command.mdx +28 -0
  18. package/src/components/interaction/Command/Command.module.css +2 -1
  19. package/src/components/interaction/Command/Command.stories.tsx +3 -3
  20. package/src/components/interaction/Dialog/Dialog.mdx +57 -0
  21. package/src/components/interaction/Dialog/Dialog.module.css +1 -1
  22. package/src/components/interaction/Dialog/Dialog.stories.tsx +0 -1
  23. package/src/components/interaction/Drawer/Drawer.mdx +35 -0
  24. package/src/components/interaction/Drawer/Drawer.module.css +3 -9
  25. package/src/components/interaction/Drawer/Drawer.stories.tsx +3 -3
  26. package/src/components/interaction/Drawer/Drawer.tsx +5 -3
  27. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +40 -0
  28. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +5 -5
  29. package/src/components/interaction/Popover/Popover.mdx +34 -0
  30. package/src/components/interaction/Popover/Popover.stories.tsx +3 -3
  31. package/src/components/interaction/Rating/Rating.mdx +38 -0
  32. package/src/components/interaction/Rating/Rating.stories.tsx +0 -1
  33. package/src/components/interaction/Toast/Toast.mdx +78 -0
  34. package/src/components/interaction/Toast/Toast.module.css +21 -7
  35. package/src/components/interaction/Toast/Toast.stories.tsx +0 -1
  36. package/src/components/interaction/form/Checkbox/Checkbox.mdx +57 -0
  37. package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +0 -1
  38. package/src/components/interaction/form/Combobox/Combobox.mdx +37 -0
  39. package/src/components/interaction/form/Combobox/Combobox.stories.tsx +0 -1
  40. package/src/components/interaction/form/FileInput/FileInput.mdx +38 -0
  41. package/src/components/interaction/form/FileInput/FileInput.stories.tsx +0 -1
  42. package/src/components/interaction/form/FormInput/FormInput.mdx +54 -0
  43. package/src/components/interaction/form/FormInput/FormInput.stories.tsx +0 -1
  44. package/src/components/interaction/form/NumberInput/NumberInput.mdx +38 -0
  45. package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +0 -1
  46. package/src/components/interaction/form/Radio/Radio.mdx +60 -0
  47. package/src/components/interaction/form/Radio/Radio.module.css +24 -11
  48. package/src/components/interaction/form/Radio/Radio.stories.tsx +5 -5
  49. package/src/components/interaction/form/Radio/Radio.tsx +6 -2
  50. package/src/components/interaction/form/Select/Select.mdx +58 -0
  51. package/src/components/interaction/form/Select/Select.stories.tsx +0 -1
  52. package/src/components/interaction/form/Slider/Slider.mdx +58 -0
  53. package/src/components/interaction/form/Slider/Slider.stories.tsx +0 -1
  54. package/src/components/interaction/form/Switch/Switch.mdx +61 -0
  55. package/src/components/interaction/form/Switch/Switch.module.css +6 -10
  56. package/src/components/interaction/form/Switch/Switch.stories.tsx +0 -1
  57. package/src/components/interaction/form/Textarea/Textarea.mdx +41 -0
  58. package/src/components/interaction/form/Textarea/Textarea.stories.tsx +0 -1
  59. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +47 -0
  60. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +10 -10
  61. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -2
  62. package/src/components/layout/Card/Card.mdx +58 -0
  63. package/src/components/layout/Card/Card.stories.tsx +0 -1
  64. package/src/components/layout/IconWrapper/IconWrapper.mdx +39 -0
  65. package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +0 -1
  66. package/src/components/layout/SectionHeader/SectionHeader.mdx +38 -0
  67. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +0 -1
  68. package/src/components/ui/Accordion/Accordion.mdx +40 -0
  69. package/src/components/ui/Accordion/Accordion.stories.tsx +0 -1
  70. package/src/components/ui/Alert/Alert.mdx +63 -0
  71. package/src/components/ui/Alert/Alert.module.css +3 -7
  72. package/src/components/ui/Alert/Alert.stories.tsx +0 -1
  73. package/src/components/ui/Avatar/Avatar.mdx +53 -0
  74. package/src/components/ui/Avatar/Avatar.module.css +4 -8
  75. package/src/components/ui/Avatar/Avatar.stories.tsx +0 -1
  76. package/src/components/ui/Badge/Badge.mdx +62 -0
  77. package/src/components/ui/Badge/Badge.module.css +4 -10
  78. package/src/components/ui/Badge/Badge.stories.tsx +0 -1
  79. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +36 -0
  80. package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +0 -1
  81. package/src/components/ui/Calendar/Calendar.mdx +34 -0
  82. package/src/components/ui/Calendar/Calendar.stories.tsx +0 -1
  83. package/src/components/ui/Carousel/Carousel.mdx +31 -0
  84. package/src/components/ui/Carousel/Carousel.stories.tsx +0 -1
  85. package/src/components/ui/DescriptionList/DescriptionList.mdx +28 -0
  86. package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +0 -1
  87. package/src/components/ui/Link/Link.mdx +44 -0
  88. package/src/components/ui/Link/Link.module.css +2 -5
  89. package/src/components/ui/Link/Link.stories.tsx +0 -1
  90. package/src/components/ui/Loading/Loading.mdx +52 -0
  91. package/src/components/ui/Loading/Loading.stories.tsx +0 -1
  92. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +45 -0
  93. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -7
  94. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +2 -2
  95. package/src/components/ui/Pagination/Pagination.mdx +39 -0
  96. package/src/components/ui/Pagination/Pagination.module.css +3 -7
  97. package/src/components/ui/Pagination/Pagination.stories.tsx +0 -1
  98. package/src/components/ui/Progress/Progress.mdx +52 -0
  99. package/src/components/ui/Progress/Progress.module.css +1 -3
  100. package/src/components/ui/Progress/Progress.stories.tsx +0 -1
  101. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +46 -0
  102. package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +0 -1
  103. package/src/components/ui/Separator/Separator.mdx +29 -0
  104. package/src/components/ui/Separator/Separator.stories.tsx +0 -1
  105. package/src/components/ui/Skeleton/Skeleton.mdx +36 -0
  106. package/src/components/ui/Skeleton/Skeleton.stories.tsx +0 -1
  107. package/src/components/ui/SkipLink/SkipLink.mdx +34 -0
  108. package/src/components/ui/SkipLink/SkipLink.stories.tsx +0 -1
  109. package/src/components/ui/Table/Table.mdx +38 -0
  110. package/src/components/ui/Table/Table.stories.tsx +0 -1
  111. package/src/components/ui/Tabs/Tabs.mdx +40 -0
  112. package/src/components/ui/Tabs/Tabs.module.css +2 -5
  113. package/src/components/ui/Tabs/Tabs.stories.tsx +0 -1
  114. package/src/components/ui/Tooltip/Tooltip.mdx +43 -0
  115. package/src/components/ui/Tooltip/Tooltip.module.css +2 -5
  116. package/src/components/ui/Tooltip/Tooltip.stories.tsx +6 -6
  117. package/src/components/ui/Typography/Typography.mdx +41 -0
  118. package/src/components/ui/Typography/Typography.stories.tsx +0 -1
  119. package/src/static/logo.svg +8 -0
  120. package/src/stories/Introduction.css +17 -0
  121. package/src/stories/Introduction.mdx +129 -0
@@ -0,0 +1,78 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Toast.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Toast
7
+
8
+ Transient feedback notification that auto-dismisses after 5 seconds. Provided via a context provider and `useToast` hook.
9
+
10
+ ## When to use
11
+ - Confirmation after a user action (save, delete, send)
12
+ - Non-blocking error or warning messages that do not require immediate action
13
+
14
+ ## When not to use
15
+ - Critical errors that require user acknowledgement — use `Alert` or `Dialog`
16
+ - Status that needs to persist — use `Alert` or `NotificationBanner`
17
+
18
+ ## Setup
19
+
20
+ Wrap your app (or layout root) once with `ToastProvider`:
21
+
22
+ ```tsx
23
+ import { ToastProvider } from '@boostdev/components';
24
+
25
+ <ToastProvider>
26
+ <App />
27
+ </ToastProvider>
28
+ ```
29
+
30
+ Then call `showToast` anywhere inside:
31
+
32
+ ```tsx
33
+ import { useToast } from '@boostdev/components';
34
+
35
+ const { showToast } = useToast();
36
+
37
+ showToast('Changes saved', 'success');
38
+ showToast('Something went wrong', 'error');
39
+ showToast('New message received', 'info');
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Success
45
+ <Canvas of={Stories.Success} />
46
+
47
+ ### Error
48
+ <Canvas of={Stories.Error} />
49
+
50
+ ### Info
51
+ <Canvas of={Stories.Info} />
52
+
53
+ ### All variants
54
+ <Canvas of={Stories.AllVariants} />
55
+
56
+ ## Props
57
+
58
+ <ArgTypes of={Stories} />
59
+
60
+ ## CSS variables
61
+
62
+
63
+ <table>
64
+ <thead>
65
+ <tr><th>Variable</th><th>Description</th></tr>
66
+ </thead>
67
+ <tbody>
68
+ <tr><td>`--toast_color`</td><td>Toast background</td></tr>
69
+ <tr><td>`--toast_on-color`</td><td>Toast text colour</td></tr>
70
+ </tbody>
71
+ </table>
72
+
73
+
74
+ ## Accessibility
75
+
76
+ - Toast container uses `role="status"` and `aria-live="polite"` — announcements are non-intrusive
77
+ - Auto-dismisses after 5 seconds; users can also manually dismiss
78
+ - Animation respects `prefers-reduced-motion`
@@ -13,7 +13,6 @@
13
13
  padding: var(--space_m) var(--space_l);
14
14
  background-color: var(--toast_color, var(--color_bg));
15
15
  color: var(--toast_on-color, var(--color_on-bg));
16
- border: 1px solid currentcolor;
17
16
  border-radius: var(--border_radius--s);
18
17
  box-shadow: var(--shadow_s);
19
18
  display: flex;
@@ -23,6 +22,26 @@
23
22
  animation: slideIn var(--animation_duration--fast) var(--animation_easing);
24
23
  }
25
24
 
25
+ .toast.--variant_success {
26
+ --toast_color: var(--color_success);
27
+ --toast_on-color: var(--color_on-success);
28
+ }
29
+
30
+ .toast.--variant_warning {
31
+ --toast_color: var(--color_warning);
32
+ --toast_on-color: var(--color_on-warning);
33
+ }
34
+
35
+ .toast.--variant_info {
36
+ --toast_color: var(--color_bg--subtle);
37
+ --toast_on-color: var(--color_on-bg--subtle);
38
+ }
39
+
40
+ .toast.--variant_error {
41
+ --toast_color: var(--color_error);
42
+ --toast_on-color: var(--color_on-error);
43
+ }
44
+
26
45
  .message {
27
46
  flex: 1;
28
47
  }
@@ -38,12 +57,7 @@
38
57
  background: none;
39
58
  border: none;
40
59
  cursor: pointer;
41
- color: inherit;
42
- opacity: 0.6;
43
- }
44
-
45
- .closeButton:hover {
46
- opacity: 1;
60
+ color: var(--toast_on-color);
47
61
  }
48
62
 
49
63
  .closeButton svg {
@@ -5,7 +5,6 @@ import { Button } from '../Button/Button';
5
5
  const meta = {
6
6
  title: 'Interaction/Toast',
7
7
  component: ToastProvider,
8
- tags: ['autodocs'],
9
8
  } satisfies Meta<typeof ToastProvider>;
10
9
 
11
10
  export default meta;
@@ -0,0 +1,57 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Checkbox.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Checkbox
7
+
8
+ Binary toggle for opt-in/opt-out choices. Renders a styled native `<input type="checkbox">` with label, hint, and error support.
9
+
10
+ ## When to use
11
+ - Independent on/off choices (newsletter opt-in, terms acceptance)
12
+ - Multi-select within a group of options
13
+
14
+ ## When not to use
15
+ - Mutually exclusive choices — use `Radio`
16
+ - A single toggle that takes immediate effect — use `Switch`
17
+
18
+ ## Examples
19
+
20
+ ### Default (unchecked)
21
+ <Canvas of={Stories.Default} />
22
+
23
+ ### Checked
24
+ <Canvas of={Stories.Checked} />
25
+
26
+ ### With hint
27
+ <Canvas of={Stories.WithHint} />
28
+
29
+ ### With error
30
+ <Canvas of={Stories.WithError} />
31
+
32
+ ### Disabled
33
+ <Canvas of={Stories.Disabled} />
34
+
35
+ ## Props
36
+
37
+ <ArgTypes of={Stories} />
38
+
39
+ ## CSS variables
40
+
41
+
42
+ <table>
43
+ <thead>
44
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
45
+ </thead>
46
+ <tbody>
47
+ <tr><td>`--checkbox_color-active`</td><td>`var(--color_active)`</td><td>Checked background</td></tr>
48
+ <tr><td>`--checkbox_color-on-active`</td><td>`var(--color_on-active)`</td><td>Checkmark colour</td></tr>
49
+ </tbody>
50
+ </table>
51
+
52
+
53
+ ## Accessibility
54
+
55
+ - Native `<input type="checkbox">` — correct semantics without ARIA
56
+ - Label is always associated via `htmlFor`/`id`
57
+ - Error message is linked via `aria-describedby`
@@ -4,7 +4,6 @@ import { Checkbox } from './Checkbox';
4
4
  const meta = {
5
5
  title: 'Form/Checkbox',
6
6
  component: Checkbox,
7
- tags: ['autodocs'],
8
7
  } satisfies Meta<typeof Checkbox>;
9
8
 
10
9
  export default meta;
@@ -0,0 +1,37 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Combobox.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Combobox
7
+
8
+ Searchable select with type-ahead filtering. Combines a text input with a dropdown option list.
9
+
10
+ ## When to use
11
+ - Large option lists (10+) where filtering helps the user find their choice
12
+ - Data that may be loaded asynchronously
13
+
14
+ ## When not to use
15
+ - Small, static option sets (< 10 items) — use `Select` for simplicity
16
+
17
+ ## Examples
18
+
19
+ ### Default
20
+ <Canvas of={Stories.Default} />
21
+
22
+ ### With hint
23
+ <Canvas of={Stories.WithHint} />
24
+
25
+ ### With error
26
+ <Canvas of={Stories.WithError} />
27
+
28
+ ## Props
29
+
30
+ <ArgTypes of={Stories} />
31
+
32
+ ## Accessibility
33
+
34
+ - Input uses `role="combobox"` with `aria-autocomplete="list"` and `aria-controls`
35
+ - Listbox uses `role="listbox"` with `role="option"` items
36
+ - `aria-activedescendant` tracks keyboard-highlighted option
37
+ - `Escape` closes the dropdown; `Enter` selects the active option
@@ -14,7 +14,6 @@ const options = [
14
14
  const meta = {
15
15
  title: 'Form/Combobox',
16
16
  component: Combobox,
17
- tags: ['autodocs'],
18
17
  } satisfies Meta<typeof Combobox>;
19
18
 
20
19
  export default meta;
@@ -0,0 +1,38 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './FileInput.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # FileInput
7
+
8
+ File upload input with styled trigger button, hint, and error support.
9
+
10
+ ## When to use
11
+ - Any form that requires file attachments
12
+
13
+ ## Examples
14
+
15
+ ### Default
16
+ <Canvas of={Stories.Default} />
17
+
18
+ ### With accept restriction
19
+ <Canvas of={Stories.WithAccept} />
20
+
21
+ ### Multiple files
22
+ <Canvas of={Stories.Multiple} />
23
+
24
+ ### With error
25
+ <Canvas of={Stories.WithError} />
26
+
27
+ ### Disabled
28
+ <Canvas of={Stories.Disabled} />
29
+
30
+ ## Props
31
+
32
+ <ArgTypes of={Stories} />
33
+
34
+ ## Accessibility
35
+
36
+ - Native `<input type="file">` — opens the OS file picker
37
+ - Label linked via `id`/`htmlFor`
38
+ - `accept` restricts the file picker but does not validate — validate on the server too
@@ -4,7 +4,6 @@ import { FileInput } from './FileInput';
4
4
  const meta = {
5
5
  title: 'Form/FileInput',
6
6
  component: FileInput,
7
- tags: ['autodocs'],
8
7
  } satisfies Meta<typeof FileInput>;
9
8
 
10
9
  export default meta;
@@ -0,0 +1,54 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './FormInput.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # FormInput
7
+
8
+ Text input field with label, optional hint, and error state. The label and input are connected via `htmlFor`/`id` automatically.
9
+
10
+ ## When to use
11
+ - Any single-line text, email, password, or number input in a form
12
+
13
+ ## Examples
14
+
15
+ ### Default
16
+ <Canvas of={Stories.Default} />
17
+
18
+ ### With hint
19
+ <Canvas of={Stories.WithHint} />
20
+
21
+ ### With error
22
+ <Canvas of={Stories.WithError} />
23
+
24
+ ### Required
25
+ <Canvas of={Stories.Required} />
26
+
27
+ ### Disabled
28
+ <Canvas of={Stories.Disabled} />
29
+
30
+ ## Props
31
+
32
+ <ArgTypes of={Stories} />
33
+
34
+ ## CSS variables
35
+
36
+
37
+ <table>
38
+ <thead>
39
+ <tr><th>Variable</th><th>Description</th></tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr><td>`--input_border-color`</td><td>Border colour (set by state — focus, error)</td></tr>
43
+ <tr><td>`--input_color`</td><td>Background colour</td></tr>
44
+ <tr><td>`--input_on-color`</td><td>Text colour</td></tr>
45
+ </tbody>
46
+ </table>
47
+
48
+
49
+ ## Accessibility
50
+
51
+ - `label` is always rendered and linked to the input via `id`/`htmlFor`
52
+ - Error message uses `aria-describedby` to associate with the input
53
+ - `required` sets both the HTML attribute and a visual indicator
54
+ - `disabled` removes the field from the tab order
@@ -4,7 +4,6 @@ import { FormInput } from './FormInput';
4
4
  const meta = {
5
5
  title: 'Form/FormInput',
6
6
  component: FormInput,
7
- tags: ['autodocs'],
8
7
  } satisfies Meta<typeof FormInput>;
9
8
 
10
9
  export default meta;
@@ -0,0 +1,38 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './NumberInput.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # NumberInput
7
+
8
+ Numeric text input with increment/decrement stepper buttons. Enforces `min`, `max`, and `step` constraints.
9
+
10
+ ## When to use
11
+ - Quantity selectors, precise numeric values where a slider would be too imprecise
12
+
13
+ ## Examples
14
+
15
+ ### Default
16
+ <Canvas of={Stories.Default} />
17
+
18
+ ### With range
19
+ <Canvas of={Stories.WithRange} />
20
+
21
+ ### With step
22
+ <Canvas of={Stories.WithStep} />
23
+
24
+ ### With error
25
+ <Canvas of={Stories.WithError} />
26
+
27
+ ### Disabled
28
+ <Canvas of={Stories.Disabled} />
29
+
30
+ ## Props
31
+
32
+ <ArgTypes of={Stories} />
33
+
34
+ ## Accessibility
35
+
36
+ - Renders `<input type="number">` — browser-native spinner support
37
+ - Stepper buttons have `aria-label="Increment"` / `"Decrement"`
38
+ - Label linked via `id`/`htmlFor`
@@ -4,7 +4,6 @@ import { NumberInput } from './NumberInput';
4
4
  const meta = {
5
5
  title: 'Form/NumberInput',
6
6
  component: NumberInput,
7
- tags: ['autodocs'],
8
7
  } satisfies Meta<typeof NumberInput>;
9
8
 
10
9
  export default meta;
@@ -0,0 +1,60 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Radio.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Radio
7
+
8
+ Single-select option within a group. Renders a styled native `<input type="radio">`. Group radios by a shared `name` attribute.
9
+
10
+ ## When to use
11
+ - Mutually exclusive choices where all options are visible (2–6 options)
12
+
13
+ ## When not to use
14
+ - More than 6 options — use `Select` or `Combobox`
15
+ - Independent on/off choices — use `Checkbox`
16
+
17
+ ## Examples
18
+
19
+ ### Default
20
+ <Canvas of={Stories.Default} />
21
+
22
+ ### Selected
23
+ <Canvas of={Stories.Selected} />
24
+
25
+ ### With description
26
+ <Canvas of={Stories.WithDescription} />
27
+
28
+ ### Group
29
+ <Canvas of={Stories.Group} />
30
+
31
+ ### With hint
32
+ <Canvas of={Stories.WithHint} />
33
+
34
+ ### With error
35
+ <Canvas of={Stories.WithError} />
36
+
37
+ ## Props
38
+
39
+ <ArgTypes of={Stories} />
40
+
41
+ ## CSS variables
42
+
43
+
44
+ <table>
45
+ <thead>
46
+ <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
47
+ </thead>
48
+ <tbody>
49
+ <tr><td>`--radio_color-active`</td><td>`var(--color_active)`</td><td>Selected fill and border colour</td></tr>
50
+ <tr><td>`--radio_color-on-active`</td><td>`var(--color_on-active)`</td><td>Inner dot colour</td></tr>
51
+ <tr><td>`--radio_description-color`</td><td>`var(--color_on-bg--subtle)`</td><td>Description text colour</td></tr>
52
+ </tbody>
53
+ </table>
54
+
55
+
56
+ ## Accessibility
57
+
58
+ - Native `<input type="radio">` — group semantics via shared `name`
59
+ - Wrap groups in `<fieldset>` with a `<legend>` for a group label
60
+ - Arrow keys move between radios in the same group
@@ -9,23 +9,36 @@
9
9
 
10
10
  .inputWrapper {
11
11
  display: flex;
12
- align-items: stretch;
12
+ align-items: flex-start;
13
13
  gap: var(--space_xs);
14
14
  }
15
15
 
16
+ .textWrapper {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--space_xxxs);
20
+ padding-block-start: var(--space_xxxs);
21
+ }
22
+
23
+ .description {
24
+ font-size: var(--font_size--body--s);
25
+ color: var(--radio_description-color, var(--color_on-bg--subtle));
26
+ line-height: var(--font_line-height--body);
27
+ }
28
+
16
29
  .radio {
17
30
  flex-shrink: 0;
18
- margin-block-start: 0.25em;
31
+ margin-block-start: 0.2em;
19
32
  width: var(--inputSize);
20
33
  height: var(--inputSize);
21
34
  color: var(--radio_color, var(--color_on-bg));
22
- border: 1px solid currentcolor;
35
+ border: 1.5px solid currentcolor;
23
36
  border-radius: 50%;
24
37
  appearance: none;
25
38
  background-color: var(--radio_color_bg, var(--color_bg));
26
39
  cursor: pointer;
27
40
  position: relative;
28
- transition: background-color 0.3s ease, border-color 0.3s ease;
41
+ transition: background-color 0.2s ease, border-color 0.2s ease;
29
42
  }
30
43
 
31
44
  .radio:checked {
@@ -36,13 +49,13 @@
36
49
  .radio:checked::after {
37
50
  content: '';
38
51
  position: absolute;
39
- left: 0.6em;
40
- top: 0.25em;
41
- width: 0.5em;
42
- height: 1em;
43
- border: solid var(--radio_color-on-active, var(--color_on-active));
44
- border-width: 0 2px 2px 0;
45
- transform: rotate(45deg);
52
+ left: 50%;
53
+ top: 50%;
54
+ transform: translate(-50%, -50%);
55
+ width: 0.4em;
56
+ height: 0.4em;
57
+ border-radius: 50%;
58
+ background-color: var(--radio_color-on-active, var(--color_on-active));
46
59
  }
47
60
 
48
61
  .radio:focus {
@@ -4,7 +4,6 @@ import { Radio } from './Radio';
4
4
  const meta = {
5
5
  title: 'Form/Radio',
6
6
  component: Radio,
7
- tags: ['autodocs'],
8
7
  } satisfies Meta<typeof Radio>;
9
8
 
10
9
  export default meta;
@@ -12,14 +11,15 @@ type Story = StoryObj<typeof meta>;
12
11
 
13
12
  export const Default: Story = { args: { label: 'Option A', name: 'choice', value: 'a' } };
14
13
  export const Selected: Story = { args: { label: 'Selected', name: 'choice2', value: 'b', defaultChecked: true } };
14
+ export const WithDescription: Story = { args: { label: 'Option A', name: 'choice5', value: 'a', description: 'Additional context about this option.' } };
15
15
  export const WithError: Story = { args: { label: 'Required', name: 'choice3', value: 'c', error: 'Please select an option' } };
16
16
  export const WithHint: Story = { args: { label: 'Option', name: 'choice4', value: 'd', hint: 'Choose carefully' } };
17
17
  export const Group: Story = {
18
18
  render: () => (
19
- <fieldset>
20
- <legend>Pick one</legend>
21
- <Radio label="Option A" name="group" value="a" />
22
- <Radio label="Option B" name="group" value="b" />
19
+ <fieldset style={{ border: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
20
+ <legend style={{ fontWeight: 'var(--font_weight--semibold)', marginBottom: '0.5rem' }}>Pick one</legend>
21
+ <Radio label="Option A" name="group" value="a" description="Short description for option A." />
22
+ <Radio label="Option B" name="group" value="b" description="Short description for option B." />
23
23
  <Radio label="Option C" name="group" value="c" />
24
24
  </fieldset>
25
25
  ),
@@ -8,12 +8,13 @@ import { cn } from '@boostdev/design-system-foundation';
8
8
  interface RadioProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
9
  label: string;
10
10
  name: string;
11
+ description?: string;
11
12
  error?: string;
12
13
  hint?: string;
13
14
  className?: string;
14
15
  }
15
16
 
16
- export function Radio({ label, name, error, hint, className, ...props }: RadioProps) {
17
+ export function Radio({ label, name, description, error, hint, className, ...props }: RadioProps) {
17
18
  const id = name + useId();
18
19
  const hintId = id + 'hint';
19
20
  const errorId = id + 'error';
@@ -31,7 +32,10 @@ export function Radio({ label, name, error, hint, className, ...props }: RadioPr
31
32
  className={cn(css.radio, error && css.radioError)}
32
33
  {...props}
33
34
  />
34
- <Label id={id} label={label} />
35
+ <div className={css.textWrapper}>
36
+ <Label id={id} label={label} />
37
+ {description && <span className={css.description}>{description}</span>}
38
+ </div>
35
39
  </div>
36
40
  <Message inputId={id} type="error" message={error} />
37
41
  <Message inputId={id} type="hint" message={hint} />
@@ -0,0 +1,58 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Select.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Select
7
+
8
+ Native dropdown for choosing one option from a list. Renders a styled `<select>` element.
9
+
10
+ ## When to use
11
+ - 5+ mutually exclusive options
12
+ - When space is constrained and a radio group would be too tall
13
+
14
+ ## When not to use
15
+ - Fewer than 5 options — prefer `Radio` buttons for better scannability
16
+ - Searchable or async-loaded options — use `Combobox`
17
+
18
+ ## Examples
19
+
20
+ ### Default
21
+ <Canvas of={Stories.Default} />
22
+
23
+ ### With placeholder
24
+ <Canvas of={Stories.WithPlaceholder} />
25
+
26
+ ### With hint
27
+ <Canvas of={Stories.WithHint} />
28
+
29
+ ### With error
30
+ <Canvas of={Stories.WithError} />
31
+
32
+ ### Disabled
33
+ <Canvas of={Stories.Disabled} />
34
+
35
+ ## Props
36
+
37
+ <ArgTypes of={Stories} />
38
+
39
+ ## CSS variables
40
+
41
+
42
+ <table>
43
+ <thead>
44
+ <tr><th>Variable</th><th>Description</th></tr>
45
+ </thead>
46
+ <tbody>
47
+ <tr><td>`--select_border-color`</td><td>Border colour</td></tr>
48
+ <tr><td>`--select_color`</td><td>Background colour</td></tr>
49
+ <tr><td>`--select_on-color`</td><td>Text colour</td></tr>
50
+ </tbody>
51
+ </table>
52
+
53
+
54
+ ## Accessibility
55
+
56
+ - Native `<select>` — keyboard, screen reader, and mobile support built-in
57
+ - Label always associated via `id`/`htmlFor`
58
+ - Error message linked via `aria-describedby`
@@ -11,7 +11,6 @@ const options = [
11
11
  const meta = {
12
12
  title: 'Form/Select',
13
13
  component: Select,
14
- tags: ['autodocs'],
15
14
  } satisfies Meta<typeof Select>;
16
15
 
17
16
  export default meta;