@boostdev/design-system-components 0.1.18 → 1.0.0

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 (137) hide show
  1. package/AGENTS.md +65 -27
  2. package/README.md +64 -29
  3. package/dist/client.cjs +412 -241
  4. package/dist/client.css +1378 -1186
  5. package/dist/client.d.cts +43 -16
  6. package/dist/client.d.ts +43 -16
  7. package/dist/client.js +414 -245
  8. package/dist/index.cjs +412 -241
  9. package/dist/index.css +1378 -1186
  10. package/dist/index.d.cts +43 -16
  11. package/dist/index.d.ts +43 -16
  12. package/dist/index.js +414 -245
  13. package/package.json +9 -4
  14. package/src/components/interaction/Button/Button.mdx +2 -5
  15. package/src/components/interaction/Button/Button.module.css +74 -44
  16. package/src/components/interaction/Button/Button.stories.tsx +4 -4
  17. package/src/components/interaction/Button/Button.tsx +5 -5
  18. package/src/components/interaction/Button/index.ts +1 -0
  19. package/src/components/interaction/Command/Command.module.css +53 -38
  20. package/src/components/interaction/Command/Command.spec.tsx +24 -0
  21. package/src/components/interaction/Command/Command.tsx +5 -0
  22. package/src/components/interaction/Dialog/Dialog.mdx +2 -2
  23. package/src/components/interaction/Dialog/Dialog.module.css +20 -10
  24. package/src/components/interaction/Dialog/Dialog.spec.tsx +19 -0
  25. package/src/components/interaction/Dialog/Dialog.tsx +22 -0
  26. package/src/components/interaction/Drawer/Drawer.module.css +11 -11
  27. package/src/components/interaction/Drawer/Drawer.spec.tsx +30 -0
  28. package/src/components/interaction/Drawer/Drawer.tsx +7 -3
  29. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +24 -19
  30. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +4 -4
  31. package/src/components/interaction/Popover/Popover.module.css +17 -12
  32. package/src/components/interaction/Popover/Popover.spec.tsx +25 -4
  33. package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
  34. package/src/components/interaction/Popover/Popover.tsx +5 -2
  35. package/src/components/interaction/Rating/Rating.module.css +3 -3
  36. package/src/components/interaction/Toast/Toast.module.css +19 -19
  37. package/src/components/interaction/Toast/Toast.tsx +1 -1
  38. package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
  39. package/src/components/interaction/form/Checkbox/Checkbox.module.css +22 -18
  40. package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
  41. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +29 -0
  42. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +87 -0
  43. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +62 -0
  44. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +46 -0
  45. package/src/components/interaction/form/CheckboxGroup/index.ts +2 -0
  46. package/src/components/interaction/form/Combobox/Combobox.module.css +44 -34
  47. package/src/components/interaction/form/Combobox/Combobox.tsx +0 -1
  48. package/src/components/interaction/form/FileInput/FileInput.module.css +24 -16
  49. package/src/components/interaction/form/FileInput/FileInput.tsx +5 -2
  50. package/src/components/interaction/form/FormInput/FormInput.module.css +21 -16
  51. package/src/components/interaction/form/FormInput/FormInput.tsx +3 -0
  52. package/src/components/interaction/form/NumberInput/NumberInput.module.css +25 -19
  53. package/src/components/interaction/form/NumberInput/NumberInput.tsx +5 -2
  54. package/src/components/interaction/form/Radio/Radio.mdx +3 -3
  55. package/src/components/interaction/form/Radio/Radio.module.css +28 -24
  56. package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
  57. package/src/components/interaction/form/Radio/Radio.tsx +2 -2
  58. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +29 -0
  59. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +75 -0
  60. package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +64 -0
  61. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +46 -0
  62. package/src/components/interaction/form/RadioGroup/index.ts +2 -0
  63. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +5 -5
  64. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +26 -26
  65. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -0
  66. package/src/components/interaction/form/Select/Select.module.css +24 -19
  67. package/src/components/interaction/form/Select/Select.tsx +3 -0
  68. package/src/components/interaction/form/Slider/Slider.mdx +1 -1
  69. package/src/components/interaction/form/Slider/Slider.module.css +24 -24
  70. package/src/components/interaction/form/Slider/Slider.tsx +1 -0
  71. package/src/components/interaction/form/Switch/Switch.mdx +4 -4
  72. package/src/components/interaction/form/Switch/Switch.module.css +22 -16
  73. package/src/components/interaction/form/Switch/Switch.tsx +1 -1
  74. package/src/components/interaction/form/Textarea/Textarea.module.css +23 -18
  75. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -0
  76. package/src/components/interaction/form/atoms/InputContainer.module.css +2 -2
  77. package/src/components/interaction/form/atoms/Label.module.css +2 -2
  78. package/src/components/interaction/form/atoms/Message.module.css +2 -2
  79. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +2 -2
  80. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +2 -2
  81. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +8 -3
  82. package/src/components/layout/ButtonGroup/index.ts +1 -0
  83. package/src/components/layout/Card/Card.module.css +30 -16
  84. package/src/components/layout/Card/Card.tsx +1 -1
  85. package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
  86. package/src/components/layout/IconWrapper/IconWrapper.module.css +5 -5
  87. package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -3
  88. package/src/components/layout/IconWrapper/index.ts +1 -0
  89. package/src/components/layout/SectionHeader/SectionHeader.module.css +23 -23
  90. package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +2 -2
  91. package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
  92. package/src/components/ui/Accordion/Accordion.module.css +23 -18
  93. package/src/components/ui/Alert/Alert.module.css +31 -27
  94. package/src/components/ui/Avatar/Avatar.module.css +6 -6
  95. package/src/components/ui/Avatar/Avatar.tsx +1 -1
  96. package/src/components/ui/Badge/Badge.mdx +2 -2
  97. package/src/components/ui/Badge/Badge.module.css +15 -15
  98. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +11 -11
  99. package/src/components/ui/Calendar/Calendar.module.css +39 -29
  100. package/src/components/ui/Calendar/Calendar.tsx +6 -2
  101. package/src/components/ui/Carousel/Carousel.module.css +15 -10
  102. package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
  103. package/src/components/ui/Collapsible/Collapsible.mdx +6 -6
  104. package/src/components/ui/Collapsible/Collapsible.module.css +18 -18
  105. package/src/components/ui/DescriptionList/DescriptionList.module.css +8 -8
  106. package/src/components/ui/Link/Link.module.css +14 -14
  107. package/src/components/ui/Loading/Loading.module.css +8 -2
  108. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +27 -23
  109. package/src/components/ui/Pagination/Pagination.module.css +22 -17
  110. package/src/components/ui/Progress/Progress.mdx +1 -1
  111. package/src/components/ui/Progress/Progress.module.css +10 -10
  112. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +8 -8
  113. package/src/components/ui/Separator/Separator.module.css +1 -1
  114. package/src/components/ui/Separator/Separator.tsx +1 -1
  115. package/src/components/ui/Skeleton/Skeleton.module.css +5 -5
  116. package/src/components/ui/SkipLink/SkipLink.module.css +17 -12
  117. package/src/components/ui/Table/Table.module.css +35 -30
  118. package/src/components/ui/Table/Table.tsx +2 -1
  119. package/src/components/ui/Tabs/Tabs.module.css +17 -17
  120. package/src/components/ui/Tabs/Tabs.tsx +0 -1
  121. package/src/components/ui/Tooltip/Tooltip.module.css +12 -12
  122. package/src/components/ui/Tooltip/Tooltip.spec.tsx +3 -3
  123. package/src/components/ui/Tooltip/Tooltip.tsx +10 -2
  124. package/src/components/ui/Typography/Typography.module.css +18 -18
  125. package/src/css/bdc.css +61 -0
  126. package/src/css/index.css +5 -0
  127. package/src/index.ts +7 -0
  128. package/src/stories/DesignSystem/Borders.mdx +158 -0
  129. package/src/stories/DesignSystem/Colors.mdx +196 -0
  130. package/src/stories/DesignSystem/Elevation.mdx +127 -0
  131. package/src/stories/DesignSystem/Grid.mdx +140 -0
  132. package/src/stories/DesignSystem/Motion.mdx +96 -0
  133. package/src/stories/DesignSystem/Overview.mdx +99 -0
  134. package/src/stories/DesignSystem/Spacing.mdx +74 -0
  135. package/src/stories/DesignSystem/Typography.mdx +110 -0
  136. package/src/stories/Introduction.css +2 -2
  137. package/src/stories/Introduction.mdx +7 -7
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "0.1.18",
3
+ "version": "1.0.0",
4
4
  "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
5
  "keywords": [
6
6
  "React",
7
7
  "component",
8
- "library:",
8
+ "library",
9
9
  "accessible",
10
10
  "token-driven",
11
11
  "components",
@@ -36,6 +36,7 @@
36
36
  "require": "./dist/client.cjs"
37
37
  },
38
38
  "./css": "./dist/index.css",
39
+ "./css/bdc": "./src/css/bdc.css",
39
40
  "./css/accordion": "./src/components/ui/Accordion/Accordion.module.css",
40
41
  "./css/alert": "./src/components/ui/Alert/Alert.module.css",
41
42
  "./css/avatar": "./src/components/ui/Avatar/Avatar.module.css",
@@ -43,6 +44,7 @@
43
44
  "./css/breadcrumb": "./src/components/ui/Breadcrumb/Breadcrumb.module.css",
44
45
  "./css/calendar": "./src/components/ui/Calendar/Calendar.module.css",
45
46
  "./css/carousel": "./src/components/ui/Carousel/Carousel.module.css",
47
+ "./css/collapsible": "./src/components/ui/Collapsible/Collapsible.module.css",
46
48
  "./css/description-list": "./src/components/ui/DescriptionList/DescriptionList.module.css",
47
49
  "./css/link": "./src/components/ui/Link/Link.module.css",
48
50
  "./css/loading": "./src/components/ui/Loading/Loading.module.css",
@@ -66,11 +68,14 @@
66
68
  "./css/rating": "./src/components/interaction/Rating/Rating.module.css",
67
69
  "./css/toast": "./src/components/interaction/Toast/Toast.module.css",
68
70
  "./css/form/checkbox": "./src/components/interaction/form/Checkbox/Checkbox.module.css",
71
+ "./css/form/checkbox-group": "./src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css",
69
72
  "./css/form/combobox": "./src/components/interaction/form/Combobox/Combobox.module.css",
70
73
  "./css/form/file-input": "./src/components/interaction/form/FileInput/FileInput.module.css",
71
74
  "./css/form/form-input": "./src/components/interaction/form/FormInput/FormInput.module.css",
72
75
  "./css/form/number-input": "./src/components/interaction/form/NumberInput/NumberInput.module.css",
73
76
  "./css/form/radio": "./src/components/interaction/form/Radio/Radio.module.css",
77
+ "./css/form/radio-group": "./src/components/interaction/form/RadioGroup/RadioGroup.module.css",
78
+ "./css/form/segmented-control": "./src/components/interaction/form/SegmentedControl/SegmentedControl.module.css",
74
79
  "./css/form/select": "./src/components/interaction/form/Select/Select.module.css",
75
80
  "./css/form/slider": "./src/components/interaction/form/Slider/Slider.module.css",
76
81
  "./css/form/switch": "./src/components/interaction/form/Switch/Switch.module.css",
@@ -105,7 +110,7 @@
105
110
  "changelog:unreleased": "pnpm exec git-cliff --unreleased"
106
111
  },
107
112
  "devDependencies": {
108
- "@boostdev/design-system-foundation": "^0.1.13",
113
+ "@boostdev/design-system-foundation": "^1.0.0",
109
114
  "@modelcontextprotocol/sdk": "^1.27.0",
110
115
  "@storybook/addon-docs": "8.6.14",
111
116
  "@storybook/addon-essentials": "^8.6.14",
@@ -135,7 +140,7 @@
135
140
  "vitest": "^3"
136
141
  },
137
142
  "peerDependencies": {
138
- "@boostdev/design-system-foundation": ">=0.1.0",
143
+ "@boostdev/design-system-foundation": ">=1.0.0",
139
144
  "react": ">=18",
140
145
  "react-dom": ">=18"
141
146
  },
@@ -56,11 +56,8 @@ Primary interactive element for triggering actions. Renders as `<button>` or `<a
56
56
  <tr><th>Variable</th><th>Description</th></tr>
57
57
  </thead>
58
58
  <tbody>
59
- <tr><td>`--button_bg`</td><td>Background colour</td></tr>
60
- <tr><td>`--button_color`</td><td>Text colour</td></tr>
61
- <tr><td>`--button_border-color`</td><td>Border colour</td></tr>
62
- <tr><td>`--button_bg--hover`</td><td>Background on hover</td></tr>
63
- <tr><td>`--button_color--hover`</td><td>Text colour on hover</td></tr>
59
+ <tr><td>`--button_color`</td><td>Identity colour — fills default, outlines ghost. Set this to re-theme.</td></tr>
60
+ <tr><td>`--button_on-color`</td><td>Text/icon colour on the button. Pair with `--button_color`.</td></tr>
64
61
  </tbody>
65
62
  </table>
66
63
 
@@ -1,17 +1,29 @@
1
+ @keyframes fadeZoom {
2
+ 0%, 100% {
3
+ opacity: 1;
4
+ transform: scale(1);
5
+ }
6
+
7
+ 50% {
8
+ opacity: 0.75;
9
+ transform: scale(1.15);
10
+ }
11
+ }
12
+
1
13
  @keyframes pulse {
2
14
  0% {
3
15
  transform: scale(1);
4
- box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color, var(--color_cta)) r g b / 50%);
16
+ box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color, var(--button_color, var(--bds-cta))) r g b / 50%);
5
17
  }
6
18
 
7
19
  70% {
8
20
  transform: scale(1.01);
9
- box-shadow: 0 0 0 var(--space_m) rgb(from var(--button_pulse-color, var(--color_cta)) r g b / 0%);
21
+ box-shadow: 0 0 0 var(--bds-space_m) rgb(from var(--button_pulse-color, var(--button_color, var(--bds-cta))) r g b / 0%);
10
22
  }
11
23
 
12
24
  100% {
13
25
  transform: scale(1);
14
- box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color, var(--color_cta)) r g b / 0%);
26
+ box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color, var(--button_color, var(--bds-cta))) r g b / 0%);
15
27
  }
16
28
  }
17
29
 
@@ -21,54 +33,62 @@
21
33
  all: unset;
22
34
  position: relative;
23
35
  font: inherit;
24
- padding-inline: var(--space_l);
36
+ padding-inline: var(--bds-space_l);
25
37
  display: inline-flex;
26
- gap: var(--space_xs);
27
- border-radius: var(--button_border_radius--s, var(--border_radius--s));
38
+ gap: var(--bds-space_xs);
39
+ border-radius: var(--button_border_radius--s, var(--bds-border_radius--s));
28
40
  align-items: center;
29
41
  cursor: pointer;
30
- scroll-padding-block-end: var(--space_l);
42
+ scroll-padding-block-end: var(--bds-space_l);
31
43
  justify-content: center;
32
44
  height: var(--button_height, 3em);
33
45
  font-size: var(--button_font-size, inherit);
34
46
  white-space: nowrap;
35
47
  line-height: 1;
36
- background-color: var(--button_bg, var(--color_cta));
37
- color: var(--button_text, var(--color_on-cta));
38
- border: 2px solid var(--button_border-color, var(--color_cta));
39
- transition: var(--animation_transition);
40
- }
41
48
 
42
- .button.--primary {
43
- --button_border-color: transparent;
49
+ /* Colour model: override --button_color + --button_on-color to re-theme */
50
+ --button_color: var(--bds-cta);
51
+ --button_on-color: var(--bds-on-cta);
52
+
53
+ background-color: var(--button_bg, var(--button_color));
54
+ color: var(--button_text, var(--button_on-color));
55
+ border: var(--button_border, none);
56
+ --bdc_color: transparent;
57
+
58
+ outline: var(--bdc_width) solid var(--bdc_color);
59
+ outline-offset: var(--bdc_offset);
60
+ transition: var(--bds-animation_transition), --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing);
44
61
  }
45
62
 
46
- .button.--secondary {
47
- --button_bg: var(--color_bg);
48
- --button_text: var(--color_on-bg);
49
- --button_border-color: var(--button_text);
63
+ /* Default: filled with --button_color, no visible border */
64
+ .button.--default { /* inherits base — no overrides needed */ }
65
+
66
+ /* Ghost: transparent background, border and text use --button_color */
67
+ .button.--ghost {
68
+ --button_bg: transparent;
69
+ --button_text: var(--button_color);
70
+ --bdc_color: var(--button_color);
50
71
  }
51
72
 
52
73
  .button.--size_small {
53
74
  --button_height: 2.25em;
54
- --button_font-size: var(--font_size--body--s);
55
- --button_border_radius--s: var(--border_radius--xs);
75
+ --button_font-size: var(--bds-font_size--body--s);
76
+ --button_border_radius--s: var(--bds-border_radius--xs);
56
77
 
57
- padding-inline: var(--space_m);
78
+ padding-inline: var(--bds-space_m);
58
79
  }
59
80
 
60
81
  .button.--size_medium {
61
82
  --button_height: 3em;
62
83
 
63
- padding-inline: var(--space_l);
84
+ padding-inline: var(--bds-space_l);
64
85
  }
65
86
 
66
87
  .button.--size_large {
67
- --button_font-size: var(--font_size--heading-3);
68
-
69
- padding-inline: var(--space_xl);
70
- --button_border_radius--s: var(--border_radius--m);
88
+ --button_font-size: var(--bds-font_size--heading-3);
89
+ --button_border_radius--s: var(--bds-border_radius--m);
71
90
 
91
+ padding-inline: var(--bds-space_xl);
72
92
  }
73
93
 
74
94
  .button.--hasPulse {
@@ -87,42 +107,52 @@
87
107
  flex-shrink: 0;
88
108
  }
89
109
 
90
- .prefix {
110
+ .iconStart {
91
111
  display: flex;
92
112
  align-items: center;
93
- margin-inline-start: calc(var(--space_s) * -1);
94
- transition: var(--animation_transition);
113
+ margin-inline-start: calc(var(--bds-space_s) * -1);
114
+ transition: var(--bds-animation_transition);
95
115
  }
96
116
 
97
- .suffix {
117
+ .iconEnd {
98
118
  display: flex;
99
119
  align-items: center;
100
- margin-inline-end: calc(var(--space_s) * -1);
101
- transition: var(--animation_transition);
120
+ margin-inline-end: calc(var(--bds-space_s) * -1);
121
+ transition: var(--bds-animation_transition);
102
122
  }
103
123
 
104
124
  @media (hover: hover) and (pointer: fine) {
105
- .button:hover .prefix svg {
106
- animation: 2s infinite 'fadeZoom' ease-out;
125
+ .button:hover .iconStart svg {
126
+ animation: fadeZoom 2s ease-out infinite;
107
127
  }
108
128
 
109
- .button:hover .suffix svg {
110
- animation: 2s infinite 'fadeZoom' ease-out;
129
+ .button:hover .iconEnd svg {
130
+ animation: fadeZoom 2s ease-out infinite;
111
131
  }
112
132
  }
113
133
 
114
134
  .button:focus-visible {
115
- outline-offset: var(--outline_offset);
116
- outline: var(--outline_default);
117
- border-radius: var(--border_radius--s);
135
+ outline: var(--bds-outline_default);
136
+ outline-offset: var(--bds-outline_offset);
137
+ border-radius: var(--bds-border_radius--s);
138
+ }
139
+
140
+ /* Default hover: outline appears, bg becomes page surface */
141
+ @media (hover: hover) and (pointer: fine) {
142
+ .button.--default:hover {
143
+ --button_bg: var(--bds-bg);
144
+ --button_text: var(--bds-interactive);
145
+ --button_pulse-color: var(--bds-interactive);
146
+ --bdc_color: currentcolor;
147
+ }
118
148
  }
119
149
 
150
+ /* Ghost hover: fills with --button_color */
120
151
  @media (hover: hover) and (pointer: fine) {
121
- .button:hover {
122
- --button_bg: var(--color_bg);
123
- --button_text: var(--color_interactive);
124
- --button_pulse-color: var(--color_interactive);
125
- --button_border-color: currentcolor;
152
+ .button.--ghost:hover {
153
+ --button_bg: var(--button_color);
154
+ --button_text: var(--button_on-color);
155
+ --bdc_color: transparent;
126
156
  }
127
157
  }
128
158
  }
@@ -5,7 +5,7 @@ const meta = {
5
5
  title: 'Interaction/Button',
6
6
  component: Button,
7
7
  argTypes: {
8
- variant: { control: 'select', options: ['primary', 'secondary'] },
8
+ variant: { control: 'select', options: ['default', 'ghost'] },
9
9
  size: { control: 'select', options: ['small', 'medium', 'large'] },
10
10
  onClick: { action: 'clicked' },
11
11
  },
@@ -14,8 +14,8 @@ const meta = {
14
14
  export default meta;
15
15
  type Story = StoryObj<typeof meta>;
16
16
 
17
- export const Primary: Story = { args: { children: 'Primary', variant: 'primary' } };
18
- export const Secondary: Story = { args: { children: 'Secondary', variant: 'secondary' } };
17
+ export const Default: Story = { args: { children: 'Default', variant: 'default' } };
18
+ export const Ghost: Story = { args: { children: 'Ghost', variant: 'ghost' } };
19
19
  export const Small: Story = { args: { children: 'Small', size: 'small' } };
20
20
  export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
21
21
  export const Large: Story = { args: { children: 'Large', size: 'large' } };
@@ -32,7 +32,7 @@ export const WithIcons: Story = {
32
32
  export const AllVariants: Story = {
33
33
  render: () => (
34
34
  <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'center' }}>
35
- {(['primary', 'secondary'] as const).map(v => (
35
+ {(['default', 'ghost'] as const).map(v => (
36
36
  ['small', 'medium', 'large'].map(s => (
37
37
  <Button key={`${v}-${s}`} variant={v} size={s as 'small' | 'medium' | 'large'}>{v} {s}</Button>
38
38
  ))
@@ -2,9 +2,9 @@ import {ButtonHTMLAttributes, MouseEventHandler, ReactNode} from 'react';
2
2
  import css from './Button.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
 
5
- interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
5
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
6
6
  href?: string;
7
- variant?: 'primary' | 'secondary';
7
+ variant?: 'default' | 'ghost';
8
8
  type?: 'button' | 'submit' | 'reset';
9
9
  size?: 'small' | 'medium' | 'large';
10
10
  iconStart?: ReactNode;
@@ -23,7 +23,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement | HTMLAncho
23
23
  export function Button({
24
24
  children,
25
25
  className,
26
- variant = 'primary',
26
+ variant = 'default',
27
27
  type = 'button',
28
28
  iconStart,
29
29
  iconEnd,
@@ -58,9 +58,9 @@ export function Button({
58
58
 
59
59
  const content = (
60
60
  <>
61
- {Boolean(iconStart) && <span className={css.prefix}>{iconStart}</span>}
61
+ {Boolean(iconStart) && <span className={css.iconStart}>{iconStart}</span>}
62
62
  {children}
63
- {Boolean(iconEnd) && <span className={css.suffix}>{iconEnd}</span>}
63
+ {Boolean(iconEnd) && <span className={css.iconEnd}>{iconEnd}</span>}
64
64
  </>
65
65
  );
66
66
 
@@ -1 +1,2 @@
1
1
  export { Button } from './Button';
2
+ export type { ButtonProps } from './Button';
@@ -2,7 +2,7 @@
2
2
  .dialog {
3
3
  padding: 0;
4
4
  border: none;
5
- border-radius: var(--border_radius--m);
5
+ border-radius: var(--bds-border_radius--m);
6
6
  background: transparent;
7
7
  width: min(40rem, 90vw);
8
8
  max-height: 70vh;
@@ -11,27 +11,32 @@
11
11
  }
12
12
 
13
13
  .dialog::backdrop {
14
- background-color: var(--command_backdrop-color, rgb(0 0 0 / 50%));
14
+ background-color: var(--color_backdrop, rgb(0 0 0 / 50%));
15
15
  backdrop-filter: blur(3px);
16
16
  }
17
17
 
18
18
  .palette {
19
19
  display: flex;
20
20
  flex-direction: column;
21
- background-color: var(--command_color, var(--color_bg));
22
- color: var(--command_on-color, var(--color_on-bg));
23
- border-radius: var(--border_radius--m);
24
- box-shadow: var(--shadow_xl);
21
+ background-color: var(--command_color, var(--bds-bg));
22
+ color: var(--command_on-color, var(--bds-on-bg));
23
+ border-radius: var(--bds-border_radius--m);
24
+ box-shadow: var(--command_shadow, var(--bds-shadow_xl));
25
25
  overflow: hidden;
26
26
  max-height: 70vh;
27
- border: 1px solid currentcolor;
27
+ border: var(--command_border, none);
28
+ --bdc_color: currentcolor;
29
+ --bdc_radius: var(--bds-border_radius--m);
30
+
31
+ outline: var(--bdc_width) solid var(--bdc_color);
32
+ outline-offset: var(--bdc_offset);
28
33
  }
29
34
 
30
35
  .searchRow {
31
36
  display: flex;
32
37
  align-items: center;
33
- gap: var(--space_xs);
34
- padding: var(--space_s) var(--space_m);
38
+ gap: var(--bds-space_xs);
39
+ padding: var(--bds-space_s) var(--bds-space_m);
35
40
  border-block-end: 1px solid currentcolor;
36
41
  flex-shrink: 0;
37
42
  }
@@ -39,7 +44,7 @@
39
44
  .searchIcon {
40
45
  width: 1.25rem;
41
46
  height: 1.25rem;
42
- color: var(--color_on-bg--muted);
47
+ color: var(--bds-on-bg);
43
48
  flex-shrink: 0;
44
49
  }
45
50
 
@@ -47,31 +52,36 @@
47
52
  flex: 1;
48
53
  border: none;
49
54
  outline: none;
50
- font-family: var(--font_family--body);
51
- font-size: var(--font_size--body);
52
- color: var(--color_on-bg);
55
+ font-family: var(--bds-font_family--body);
56
+ font-size: var(--bds-font_size--body);
57
+ color: var(--bds-on-bg);
53
58
  background: transparent;
54
59
  min-width: 0;
55
60
  }
56
61
 
57
62
  .search::placeholder {
58
- color: var(--color_on-bg--muted);
63
+ color: var(--bds-on-bg);
59
64
  }
60
65
 
61
66
  .escHint {
62
- font-size: var(--font_size--body--s);
63
- color: var(--color_on-bg--muted);
64
- border: 1px solid currentcolor;
65
- border-radius: var(--border_radius--xs);
67
+ font-size: var(--bds-font_size--body--s);
68
+ color: var(--bds-on-bg);
69
+ border-radius: var(--bds-border_radius--xs);
66
70
  padding: 0.1em 0.4em;
67
71
  flex-shrink: 0;
72
+
73
+ --bdc_color: currentcolor;
74
+
75
+ border: none;
76
+ outline: var(--bdc_width) solid var(--bdc_color);
77
+ outline-offset: var(--bdc_offset);
68
78
  }
69
79
 
70
80
  .list {
71
81
  overflow-y: auto;
72
82
  list-style: none;
73
83
  margin: 0;
74
- padding: var(--space_xs) 0;
84
+ padding: var(--bds-space_xs) 0;
75
85
  }
76
86
 
77
87
  .groupList {
@@ -81,10 +91,10 @@
81
91
  }
82
92
 
83
93
  .group {
84
- padding: var(--space_xs) var(--space_m);
85
- font-size: var(--font_size--body--s);
86
- font-weight: var(--font_weight--semibold);
87
- color: var(--color_on-bg--muted);
94
+ padding: var(--bds-space_xs) var(--bds-space_m);
95
+ font-size: var(--bds-font_size--body--s);
96
+ font-weight: var(--bds-font_weight--semibold);
97
+ color: var(--bds-on-bg);
88
98
  text-transform: uppercase;
89
99
  letter-spacing: 0.05em;
90
100
  }
@@ -92,39 +102,44 @@
92
102
  .item {
93
103
  display: flex;
94
104
  align-items: center;
95
- gap: var(--space_s);
96
- padding: var(--space_s) var(--space_m);
105
+ gap: var(--bds-space_s);
106
+ padding: var(--bds-space_s) var(--bds-space_m);
97
107
  cursor: pointer;
98
- transition: background-color var(--animation_transition-duration) var(--animation_easing);
108
+ transition: background-color var(--bds-animation_transition-duration) var(--bds-animation_easing);
99
109
  }
100
110
 
101
111
  .itemActive {
102
- background-color: var(--color_bg--subtle);
112
+ background-color: var(--bds-bg--subtle);
103
113
  }
104
114
 
105
115
  .itemLabel {
106
116
  flex: 1;
107
- font-size: var(--font_size--body);
108
- color: var(--color_on-bg);
117
+ font-size: var(--bds-font_size--body);
118
+ color: var(--bds-on-bg);
109
119
  }
110
120
 
111
121
  .itemDesc {
112
- font-size: var(--font_size--body--s);
113
- color: var(--color_on-bg--muted);
122
+ font-size: var(--bds-font_size--body--s);
123
+ color: var(--bds-on-bg);
114
124
  }
115
125
 
116
126
  .shortcut {
117
- font-size: var(--font_size--body--s);
118
- color: var(--color_on-bg--muted);
119
- border: 1px solid currentcolor;
120
- border-radius: var(--border_radius--xs);
127
+ font-size: var(--bds-font_size--body--s);
128
+ color: var(--bds-on-bg);
129
+ border-radius: var(--bds-border_radius--xs);
121
130
  padding: 0.1em 0.4em;
131
+
132
+ --bdc_color: currentcolor;
133
+
134
+ border: none;
135
+ outline: var(--bdc_width) solid var(--bdc_color);
136
+ outline-offset: var(--bdc_offset);
122
137
  }
123
138
 
124
139
  .empty {
125
- padding: var(--space_l) var(--space_m);
140
+ padding: var(--bds-space_l) var(--bds-space_m);
126
141
  text-align: center;
127
- color: var(--color_on-bg--muted);
128
- font-size: var(--font_size--body);
142
+ color: var(--bds-on-bg);
143
+ font-size: var(--bds-font_size--body);
129
144
  }
130
145
  }
@@ -75,4 +75,28 @@ describe('Command', () => {
75
75
  unmount();
76
76
  expect(document.body.style.overflow).toBe('');
77
77
  });
78
+
79
+ it('has aria-modal="true" when open', () => {
80
+ render(<Command isOpen items={items} onClose={() => {}} />);
81
+ const dialog = document.querySelector('dialog');
82
+ expect(dialog).toHaveAttribute('aria-modal', 'true');
83
+ });
84
+
85
+ it('has aria-label on the dialog', () => {
86
+ render(<Command isOpen items={items} onClose={() => {}} />);
87
+ const dialog = document.querySelector('dialog');
88
+ expect(dialog).toHaveAttribute('aria-label', 'Command palette');
89
+ });
90
+
91
+ it('restores focus to trigger element on close', () => {
92
+ const trigger = document.createElement('button');
93
+ document.body.appendChild(trigger);
94
+ trigger.focus();
95
+
96
+ const { rerender } = render(<Command isOpen items={items} onClose={() => {}} />);
97
+ rerender(<Command isOpen={false} items={items} onClose={() => {}} />);
98
+ expect(document.activeElement).toBe(trigger);
99
+
100
+ document.body.removeChild(trigger);
101
+ });
78
102
  });
@@ -51,10 +51,13 @@ export function Command({
51
51
  return map;
52
52
  }, [filtered]);
53
53
 
54
+ const triggerRef = useRef<Element | null>(null);
55
+
54
56
  useEffect(() => {
55
57
  const dialog = dialogRef.current;
56
58
  if (!dialog) return;
57
59
  if (isOpen) {
60
+ triggerRef.current = document.activeElement;
58
61
  dialog.showModal();
59
62
  document.body.style.overflow = 'hidden';
60
63
  setQuery('');
@@ -63,6 +66,7 @@ export function Command({
63
66
  } else if (dialog.open) {
64
67
  dialog.close();
65
68
  document.body.style.overflow = '';
69
+ (triggerRef.current as HTMLElement | null)?.focus();
66
70
  }
67
71
  return () => {
68
72
  document.body.style.overflow = '';
@@ -104,6 +108,7 @@ export function Command({
104
108
  ref={dialogRef}
105
109
  className={cn(css.dialog, className)}
106
110
  aria-label="Command palette"
111
+ aria-modal="true"
107
112
  onCancel={handleCancel}
108
113
  >
109
114
  <div className={css.palette} onKeyDown={handleKeyDown}>
@@ -35,8 +35,8 @@ Modal overlay using the native `<dialog>` element. Traps focus inside the dialog
35
35
  <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
36
36
  </thead>
37
37
  <tbody>
38
- <tr><td>`--dialog_color`</td><td>`var(--color_grey--subtle)`</td><td>Dialog background</td></tr>
39
- <tr><td>`--dialog_on-color`</td><td>`var(--color_on-grey--subtle)`</td><td>Dialog text colour</td></tr>
38
+ <tr><td>`--dialog_color`</td><td>`var(--bds-grey--subtle)`</td><td>Dialog background</td></tr>
39
+ <tr><td>`--dialog_on-color`</td><td>`var(--bds-on-grey--subtle)`</td><td>Dialog text colour</td></tr>
40
40
  <tr><td>`--dialog_backdrop-color`</td><td>`rgb(0 0 0 / 50%)`</td><td>Backdrop overlay colour</td></tr>
41
41
  </tbody>
42
42
  </table>
@@ -1,11 +1,17 @@
1
1
  @layer component {
2
2
  .dialog {
3
- font-size: var(--font_size--body--s);
4
- border: 2px solid currentcolor;
5
- background-color: var(--dialog_color ,var(--color_grey--subtle));
6
- color: var(--dialog_on-color, var(--color_on-grey--subtle));
7
- border-radius: var(--border_radius--m);
8
- padding: var(--space_l);
3
+ font-size: var(--bds-font_size--body--s);
4
+ border: var(--dialog_border, none);
5
+ --bdc_color: currentcolor;
6
+ --bdc_radius: var(--bds-border_radius--m);
7
+
8
+ outline: var(--bdc_width) solid var(--bdc_color);
9
+ outline-offset: var(--bdc_offset);
10
+ border-radius: var(--dialog_radius, var(--bdc_radius));
11
+ box-shadow: var(--dialog_shadow, var(--bdc_shadow));
12
+ background-color: var(--dialog_color ,var(--bds-grey--subtle));
13
+ color: var(--dialog_on-color, var(--bds-on-grey--subtle));
14
+ padding: var(--bds-space_l);
9
15
  max-width: 90svw;
10
16
  max-height: 90svh;
11
17
  overflow: visible;
@@ -13,7 +19,7 @@
13
19
 
14
20
  .dialogContent {
15
21
  display: grid;
16
- gap: var(--grid_gap);
22
+ gap: var(--bds-grid_gap);
17
23
  overflow-y: auto;
18
24
  min-height: 0;
19
25
  flex: 1;
@@ -33,9 +39,13 @@
33
39
  align-items: center;
34
40
  justify-content: center;
35
41
  border-radius: 50%;
36
- background-color: var(--color_bg);
42
+ background-color: var(--bds-bg);
37
43
  color: currentcolor;
38
- border: 2px solid currentcolor;
44
+ border: var(--dialog_close-border, none);
45
+ --bdc_color: currentcolor;
46
+
47
+ outline: var(--bdc_width) solid var(--bdc_color);
48
+ outline-offset: var(--bdc_offset);
39
49
  cursor: pointer;
40
50
  }
41
51
 
@@ -45,7 +55,7 @@
45
55
  }
46
56
 
47
57
  .dialog::backdrop {
48
- background-color: var(--dialog_backdrop-color, rgb(0 0 0 / 50%));
58
+ background-color: var(--color_backdrop, rgb(0 0 0 / 50%));
49
59
  backdrop-filter: blur(3px);
50
60
  }
51
61
  }
@@ -58,4 +58,23 @@ describe('Dialog', () => {
58
58
  unmount();
59
59
  expect(document.body.style.overflow).toBe('');
60
60
  });
61
+
62
+ it('has aria-modal="true" when open', () => {
63
+ render(<Dialog isOpen><span>Content</span></Dialog>);
64
+ const dialog = document.querySelector('dialog');
65
+ expect(dialog).toHaveAttribute('aria-modal', 'true');
66
+ });
67
+
68
+ it('restores focus to trigger element on close', () => {
69
+ const trigger = document.createElement('button');
70
+ document.body.appendChild(trigger);
71
+ trigger.focus();
72
+
73
+ const onClose = vi.fn();
74
+ const { rerender } = render(<Dialog isOpen onClose={onClose}><span>Content</span></Dialog>);
75
+ rerender(<Dialog isOpen={false} onClose={onClose}><span>Content</span></Dialog>);
76
+ expect(document.activeElement).toBe(trigger);
77
+
78
+ document.body.removeChild(trigger);
79
+ });
61
80
  });