@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.
- package/AGENTS.md +65 -27
- package/README.md +64 -29
- package/dist/client.cjs +412 -241
- package/dist/client.css +1378 -1186
- package/dist/client.d.cts +43 -16
- package/dist/client.d.ts +43 -16
- package/dist/client.js +414 -245
- package/dist/index.cjs +412 -241
- package/dist/index.css +1378 -1186
- package/dist/index.d.cts +43 -16
- package/dist/index.d.ts +43 -16
- package/dist/index.js +414 -245
- package/package.json +9 -4
- package/src/components/interaction/Button/Button.mdx +2 -5
- package/src/components/interaction/Button/Button.module.css +74 -44
- package/src/components/interaction/Button/Button.stories.tsx +4 -4
- package/src/components/interaction/Button/Button.tsx +5 -5
- package/src/components/interaction/Button/index.ts +1 -0
- package/src/components/interaction/Command/Command.module.css +53 -38
- package/src/components/interaction/Command/Command.spec.tsx +24 -0
- package/src/components/interaction/Command/Command.tsx +5 -0
- package/src/components/interaction/Dialog/Dialog.mdx +2 -2
- package/src/components/interaction/Dialog/Dialog.module.css +20 -10
- package/src/components/interaction/Dialog/Dialog.spec.tsx +19 -0
- package/src/components/interaction/Dialog/Dialog.tsx +22 -0
- package/src/components/interaction/Drawer/Drawer.module.css +11 -11
- package/src/components/interaction/Drawer/Drawer.spec.tsx +30 -0
- package/src/components/interaction/Drawer/Drawer.tsx +7 -3
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +24 -19
- package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +4 -4
- package/src/components/interaction/Popover/Popover.module.css +17 -12
- package/src/components/interaction/Popover/Popover.spec.tsx +25 -4
- package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
- package/src/components/interaction/Popover/Popover.tsx +5 -2
- package/src/components/interaction/Rating/Rating.module.css +3 -3
- package/src/components/interaction/Toast/Toast.module.css +19 -19
- package/src/components/interaction/Toast/Toast.tsx +1 -1
- package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +22 -18
- package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +29 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +87 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +62 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +46 -0
- package/src/components/interaction/form/CheckboxGroup/index.ts +2 -0
- package/src/components/interaction/form/Combobox/Combobox.module.css +44 -34
- package/src/components/interaction/form/Combobox/Combobox.tsx +0 -1
- package/src/components/interaction/form/FileInput/FileInput.module.css +24 -16
- package/src/components/interaction/form/FileInput/FileInput.tsx +5 -2
- package/src/components/interaction/form/FormInput/FormInput.module.css +21 -16
- package/src/components/interaction/form/FormInput/FormInput.tsx +3 -0
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +25 -19
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +5 -2
- package/src/components/interaction/form/Radio/Radio.mdx +3 -3
- package/src/components/interaction/form/Radio/Radio.module.css +28 -24
- package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
- package/src/components/interaction/form/Radio/Radio.tsx +2 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +29 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +75 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +64 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +46 -0
- package/src/components/interaction/form/RadioGroup/index.ts +2 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +5 -5
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +26 -26
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -0
- package/src/components/interaction/form/Select/Select.module.css +24 -19
- package/src/components/interaction/form/Select/Select.tsx +3 -0
- package/src/components/interaction/form/Slider/Slider.mdx +1 -1
- package/src/components/interaction/form/Slider/Slider.module.css +24 -24
- package/src/components/interaction/form/Slider/Slider.tsx +1 -0
- package/src/components/interaction/form/Switch/Switch.mdx +4 -4
- package/src/components/interaction/form/Switch/Switch.module.css +22 -16
- package/src/components/interaction/form/Switch/Switch.tsx +1 -1
- package/src/components/interaction/form/Textarea/Textarea.module.css +23 -18
- package/src/components/interaction/form/Textarea/Textarea.tsx +3 -0
- package/src/components/interaction/form/atoms/InputContainer.module.css +2 -2
- package/src/components/interaction/form/atoms/Label.module.css +2 -2
- package/src/components/interaction/form/atoms/Message.module.css +2 -2
- package/src/components/layout/ButtonGroup/ButtonGroup.module.css +2 -2
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +2 -2
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +8 -3
- package/src/components/layout/ButtonGroup/index.ts +1 -0
- package/src/components/layout/Card/Card.module.css +30 -16
- package/src/components/layout/Card/Card.tsx +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.module.css +5 -5
- package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -3
- package/src/components/layout/IconWrapper/index.ts +1 -0
- package/src/components/layout/SectionHeader/SectionHeader.module.css +23 -23
- package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +2 -2
- package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
- package/src/components/ui/Accordion/Accordion.module.css +23 -18
- package/src/components/ui/Alert/Alert.module.css +31 -27
- package/src/components/ui/Avatar/Avatar.module.css +6 -6
- package/src/components/ui/Avatar/Avatar.tsx +1 -1
- package/src/components/ui/Badge/Badge.mdx +2 -2
- package/src/components/ui/Badge/Badge.module.css +15 -15
- package/src/components/ui/Breadcrumb/Breadcrumb.module.css +11 -11
- package/src/components/ui/Calendar/Calendar.module.css +39 -29
- package/src/components/ui/Calendar/Calendar.tsx +6 -2
- package/src/components/ui/Carousel/Carousel.module.css +15 -10
- package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
- package/src/components/ui/Collapsible/Collapsible.mdx +6 -6
- package/src/components/ui/Collapsible/Collapsible.module.css +18 -18
- package/src/components/ui/DescriptionList/DescriptionList.module.css +8 -8
- package/src/components/ui/Link/Link.module.css +14 -14
- package/src/components/ui/Loading/Loading.module.css +8 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +27 -23
- package/src/components/ui/Pagination/Pagination.module.css +22 -17
- package/src/components/ui/Progress/Progress.mdx +1 -1
- package/src/components/ui/Progress/Progress.module.css +10 -10
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +8 -8
- package/src/components/ui/Separator/Separator.module.css +1 -1
- package/src/components/ui/Separator/Separator.tsx +1 -1
- package/src/components/ui/Skeleton/Skeleton.module.css +5 -5
- package/src/components/ui/SkipLink/SkipLink.module.css +17 -12
- package/src/components/ui/Table/Table.module.css +35 -30
- package/src/components/ui/Table/Table.tsx +2 -1
- package/src/components/ui/Tabs/Tabs.module.css +17 -17
- package/src/components/ui/Tabs/Tabs.tsx +0 -1
- package/src/components/ui/Tooltip/Tooltip.module.css +12 -12
- package/src/components/ui/Tooltip/Tooltip.spec.tsx +3 -3
- package/src/components/ui/Tooltip/Tooltip.tsx +10 -2
- package/src/components/ui/Typography/Typography.module.css +18 -18
- package/src/css/bdc.css +61 -0
- package/src/css/index.css +5 -0
- package/src/index.ts +7 -0
- package/src/stories/DesignSystem/Borders.mdx +158 -0
- package/src/stories/DesignSystem/Colors.mdx +196 -0
- package/src/stories/DesignSystem/Elevation.mdx +127 -0
- package/src/stories/DesignSystem/Grid.mdx +140 -0
- package/src/stories/DesignSystem/Motion.mdx +96 -0
- package/src/stories/DesignSystem/Overview.mdx +99 -0
- package/src/stories/DesignSystem/Spacing.mdx +74 -0
- package/src/stories/DesignSystem/Typography.mdx +110 -0
- package/src/stories/Introduction.css +2 -2
- 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.
|
|
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.
|
|
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": ">=
|
|
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>`--
|
|
60
|
-
<tr><td>`--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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
|
-
|
|
43
|
-
--
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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 .
|
|
106
|
-
animation: 2s
|
|
125
|
+
.button:hover .iconStart svg {
|
|
126
|
+
animation: fadeZoom 2s ease-out infinite;
|
|
107
127
|
}
|
|
108
128
|
|
|
109
|
-
.button:hover .
|
|
110
|
-
animation: 2s
|
|
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
|
|
116
|
-
outline: var(--
|
|
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:
|
|
123
|
-
--button_text:
|
|
124
|
-
--
|
|
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: ['
|
|
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
|
|
18
|
-
export const
|
|
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
|
-
{(['
|
|
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?: '
|
|
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 = '
|
|
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.
|
|
61
|
+
{Boolean(iconStart) && <span className={css.iconStart}>{iconStart}</span>}
|
|
62
62
|
{children}
|
|
63
|
-
{Boolean(iconEnd) && <span className={css.
|
|
63
|
+
{Boolean(iconEnd) && <span className={css.iconEnd}>{iconEnd}</span>}
|
|
64
64
|
</>
|
|
65
65
|
);
|
|
66
66
|
|
|
@@ -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(--
|
|
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(--
|
|
22
|
-
color: var(--command_on-color, var(--
|
|
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:
|
|
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(--
|
|
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(--
|
|
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(--
|
|
63
|
+
color: var(--bds-on-bg);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
.escHint {
|
|
62
|
-
font-size: var(--font_size--body--s);
|
|
63
|
-
color: var(--
|
|
64
|
-
border:
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
119
|
-
border:
|
|
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(--
|
|
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(--
|
|
39
|
-
<tr><td>`--dialog_on-color`</td><td>`var(--
|
|
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:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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(--
|
|
42
|
+
background-color: var(--bds-bg);
|
|
37
43
|
color: currentcolor;
|
|
38
|
-
border:
|
|
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(--
|
|
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
|
});
|