@boostdev/design-system-components 0.1.17 → 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 +1380 -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 +1380 -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 +13 -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
|
@@ -57,6 +57,7 @@ export function Table<Row extends Record<string, unknown>>({
|
|
|
57
57
|
type="button"
|
|
58
58
|
className={css.sortButton}
|
|
59
59
|
onClick={() => handleSort(col.key)}
|
|
60
|
+
aria-label={`Sort by ${String(col.header)}${sortKey === col.key ? `, currently ${sortDirection === 'asc' ? 'ascending' : 'descending'}` : ''}`}
|
|
60
61
|
>
|
|
61
62
|
{col.header}
|
|
62
63
|
<svg
|
|
@@ -83,7 +84,7 @@ export function Table<Row extends Record<string, unknown>>({
|
|
|
83
84
|
</thead>
|
|
84
85
|
<tbody className={css.tbody}>
|
|
85
86
|
{rows.map((row, rowIndex) => (
|
|
86
|
-
<tr key={rowIndex} className={css.tr}>
|
|
87
|
+
<tr key={JSON.stringify(row) || rowIndex} className={css.tr}>
|
|
87
88
|
{columns.map(col => (
|
|
88
89
|
<td key={col.key} className={css.td}>
|
|
89
90
|
{col.render ? col.render(row) : String(row[col.key] ?? '')}
|
|
@@ -6,28 +6,28 @@
|
|
|
6
6
|
|
|
7
7
|
.tabList {
|
|
8
8
|
display: flex;
|
|
9
|
-
border-bottom: 1px solid var(--
|
|
10
|
-
gap: var(--space_xxs);
|
|
9
|
+
border-bottom: 1px solid var(--bds-bg--subtle);
|
|
10
|
+
gap: var(--bds-space_xxs);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.tab {
|
|
14
14
|
all: unset;
|
|
15
15
|
display: inline-flex;
|
|
16
16
|
align-items: center;
|
|
17
|
-
padding: var(--space_s) var(--space_m);
|
|
18
|
-
font-family: var(--font_family--body);
|
|
19
|
-
font-size: var(--font_size--body);
|
|
20
|
-
font-weight: var(--font_weight--semibold);
|
|
21
|
-
color: var(--tab_color, var(--
|
|
17
|
+
padding: var(--bds-space_s) var(--bds-space_m);
|
|
18
|
+
font-family: var(--bds-font_family--body);
|
|
19
|
+
font-size: var(--bds-font_size--body);
|
|
20
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
21
|
+
color: var(--tab_color, var(--bds-on-bg));
|
|
22
22
|
border-bottom: 2px solid var(--tab_border-color, transparent);
|
|
23
23
|
margin-bottom: -1px;
|
|
24
24
|
cursor: pointer;
|
|
25
|
-
transition: var(--animation_transition);
|
|
25
|
+
transition: var(--bds-animation_transition);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
.tab.--active {
|
|
29
|
-
--tab_color: var(--
|
|
30
|
-
--tab_border-color: var(--
|
|
29
|
+
--tab_color: var(--bds-interactive);
|
|
30
|
+
--tab_border-color: var(--bds-interactive);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.tab:disabled {
|
|
@@ -36,23 +36,23 @@
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
.tab:focus-visible {
|
|
39
|
-
outline: var(--outline_default);
|
|
40
|
-
outline-offset: var(--outline_offset);
|
|
41
|
-
border-radius: var(--border_radius--xs);
|
|
39
|
+
outline: var(--bds-outline_default);
|
|
40
|
+
outline-offset: var(--bds-outline_offset);
|
|
41
|
+
border-radius: var(--bds-border_radius--xs);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
@media (hover: hover) and (pointer: fine) {
|
|
45
45
|
.tab:not(:disabled, .--active):hover {
|
|
46
|
-
--tab_color: var(--
|
|
46
|
+
--tab_color: var(--bds-interactive);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.panel {
|
|
51
|
-
padding-block-start: var(--space_m);
|
|
51
|
+
padding-block-start: var(--bds-space_m);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
.panel:focus-visible {
|
|
55
|
-
outline: var(--outline_default);
|
|
56
|
-
outline-offset: var(--outline_offset);
|
|
55
|
+
outline: var(--bds-outline_default);
|
|
56
|
+
outline-offset: var(--bds-outline_offset);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -7,20 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
.tooltip {
|
|
9
9
|
position: absolute;
|
|
10
|
-
z-index: var(--z-index_popover);
|
|
11
|
-
padding: var(--space_xxs) var(--space_xs);
|
|
12
|
-
border-radius: var(--border_radius--xs);
|
|
13
|
-
background-color: var(--tooltip_bg, var(--
|
|
14
|
-
color: var(--tooltip_text, var(--
|
|
15
|
-
font-size: var(--font_size--body--s);
|
|
16
|
-
line-height: var(--font_line-height--body);
|
|
10
|
+
z-index: var(--bds-z-index_popover);
|
|
11
|
+
padding: var(--bds-space_xxs) var(--bds-space_xs);
|
|
12
|
+
border-radius: var(--bds-border_radius--xs);
|
|
13
|
+
background-color: var(--tooltip_bg, var(--bds-on-bg));
|
|
14
|
+
color: var(--tooltip_text, var(--bds-bg));
|
|
15
|
+
font-size: var(--bds-font_size--body--s);
|
|
16
|
+
line-height: var(--bds-font_line-height--body);
|
|
17
17
|
white-space: nowrap;
|
|
18
18
|
pointer-events: none;
|
|
19
19
|
|
|
20
20
|
/* Hidden by default */
|
|
21
21
|
visibility: hidden;
|
|
22
22
|
opacity: 0;
|
|
23
|
-
transition: opacity var(--animation_transition-duration) var(--animation_easing);
|
|
23
|
+
transition: opacity var(--bds-animation_transition-duration) var(--bds-animation_easing);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/* Show on hover and keyboard focus */
|
|
@@ -32,28 +32,28 @@
|
|
|
32
32
|
|
|
33
33
|
/* Placement: top (default) */
|
|
34
34
|
.--placement_top {
|
|
35
|
-
bottom: calc(100% + var(--space_xs));
|
|
35
|
+
bottom: calc(100% + var(--bds-space_xs));
|
|
36
36
|
left: 50%;
|
|
37
37
|
transform: translateX(-50%);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/* Placement: bottom */
|
|
41
41
|
.--placement_bottom {
|
|
42
|
-
top: calc(100% + var(--space_xs));
|
|
42
|
+
top: calc(100% + var(--bds-space_xs));
|
|
43
43
|
left: 50%;
|
|
44
44
|
transform: translateX(-50%);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/* Placement: left */
|
|
48
48
|
.--placement_left {
|
|
49
|
-
right: calc(100% + var(--space_xs));
|
|
49
|
+
right: calc(100% + var(--bds-space_xs));
|
|
50
50
|
top: 50%;
|
|
51
51
|
transform: translateY(-50%);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/* Placement: right */
|
|
55
55
|
.--placement_right {
|
|
56
|
-
left: calc(100% + var(--space_xs));
|
|
56
|
+
left: calc(100% + var(--bds-space_xs));
|
|
57
57
|
top: 50%;
|
|
58
58
|
transform: translateY(-50%);
|
|
59
59
|
}
|
|
@@ -18,8 +18,8 @@ describe('Tooltip', () => {
|
|
|
18
18
|
<button type="button">Trigger</button>
|
|
19
19
|
</Tooltip>
|
|
20
20
|
);
|
|
21
|
-
expect(screen.getByRole('tooltip')).toBeInTheDocument();
|
|
22
|
-
expect(screen.getByRole('tooltip')).toHaveTextContent('Tooltip text');
|
|
21
|
+
expect(screen.getByRole('tooltip', { hidden: true })).toBeInTheDocument();
|
|
22
|
+
expect(screen.getByRole('tooltip', { hidden: true })).toHaveTextContent('Tooltip text');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('links trigger to tooltip via aria-describedby', () => {
|
|
@@ -29,7 +29,7 @@ describe('Tooltip', () => {
|
|
|
29
29
|
</Tooltip>
|
|
30
30
|
);
|
|
31
31
|
const trigger = screen.getByRole('button', { name: 'Action' });
|
|
32
|
-
const tooltip = screen.getByRole('tooltip');
|
|
32
|
+
const tooltip = screen.getByRole('tooltip', { hidden: true });
|
|
33
33
|
expect(trigger).toHaveAttribute('aria-describedby', tooltip.id);
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactElement, ReactNode, cloneElement, isValidElement, useId } from 'react';
|
|
1
|
+
import { ReactElement, ReactNode, cloneElement, isValidElement, useId, useState } from 'react';
|
|
2
2
|
import css from './Tooltip.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
|
|
@@ -16,6 +16,7 @@ export function Tooltip({
|
|
|
16
16
|
className,
|
|
17
17
|
}: Readonly<TooltipProps>) {
|
|
18
18
|
const tooltipId = useId();
|
|
19
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
19
20
|
|
|
20
21
|
const trigger = isValidElement(children)
|
|
21
22
|
? cloneElement(children as ReactElement<Record<string, unknown>>, {
|
|
@@ -24,11 +25,18 @@ export function Tooltip({
|
|
|
24
25
|
: children;
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
|
-
<span
|
|
28
|
+
<span
|
|
29
|
+
className={cn(css.wrapper, className)}
|
|
30
|
+
onMouseEnter={() => setIsVisible(true)}
|
|
31
|
+
onMouseLeave={() => setIsVisible(false)}
|
|
32
|
+
onFocus={() => setIsVisible(true)}
|
|
33
|
+
onBlur={() => setIsVisible(false)}
|
|
34
|
+
>
|
|
28
35
|
{trigger}
|
|
29
36
|
<span
|
|
30
37
|
id={tooltipId}
|
|
31
38
|
role="tooltip"
|
|
39
|
+
aria-hidden={!isVisible}
|
|
32
40
|
className={cn(css.tooltip, css[`--placement_${placement}`])}
|
|
33
41
|
>
|
|
34
42
|
{content}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
@layer component {
|
|
2
2
|
.typography {
|
|
3
3
|
margin: 0;
|
|
4
|
-
font-family: var(--font_family--body);
|
|
5
|
-
color: var(--typography_color, var(--
|
|
4
|
+
font-family: var(--bds-font_family--body);
|
|
5
|
+
color: var(--typography_color, var(--bds-on-bg));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.--h1 {
|
|
9
|
-
font-family: var(--font_family--heading);
|
|
10
|
-
font-size: var(--font_size--heading-1);
|
|
11
|
-
font-weight: var(--font_weight--bold);
|
|
12
|
-
line-height: var(--font_line-height--heading);
|
|
9
|
+
font-family: var(--bds-font_family--heading);
|
|
10
|
+
font-size: var(--bds-font_size--heading-1);
|
|
11
|
+
font-weight: var(--bds-font_weight--bold);
|
|
12
|
+
line-height: var(--bds-font_line-height--heading);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.--h2 {
|
|
16
|
-
font-family: var(--font_family--heading);
|
|
17
|
-
font-size: var(--font_size--heading-2);
|
|
18
|
-
font-weight: var(--font_weight--semibold);
|
|
19
|
-
line-height: var(--font_line-height--heading);
|
|
16
|
+
font-family: var(--bds-font_family--heading);
|
|
17
|
+
font-size: var(--bds-font_size--heading-2);
|
|
18
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
19
|
+
line-height: var(--bds-font_line-height--heading);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.--h3 {
|
|
23
|
-
font-family: var(--font_family--heading);
|
|
24
|
-
font-size: var(--font_size--heading-3);
|
|
25
|
-
font-weight: var(--font_weight--semibold);
|
|
26
|
-
line-height: var(--font_line-height--heading);
|
|
23
|
+
font-family: var(--bds-font_family--heading);
|
|
24
|
+
font-size: var(--bds-font_size--heading-3);
|
|
25
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
26
|
+
line-height: var(--bds-font_line-height--heading);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.--body {
|
|
30
|
-
font-size: var(--font_size--body);
|
|
31
|
-
line-height: var(--font_line-height--body);
|
|
30
|
+
font-size: var(--bds-font_size--body);
|
|
31
|
+
line-height: var(--bds-font_line-height--body);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.--body_s {
|
|
35
|
-
font-size: var(--font_size--body--s);
|
|
36
|
-
line-height: var(--font_line-height--body);
|
|
35
|
+
font-size: var(--bds-font_size--body--s);
|
|
36
|
+
line-height: var(--bds-font_line-height--body);
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/css/bdc.css
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* ─── bdc: Border Decoration Tokens ────────────────────────────────────────────
|
|
2
|
+
*
|
|
3
|
+
* Registered CSS custom properties for consistent borders across all components.
|
|
4
|
+
* Components use outline (not border) for their visual edge so that the decoration
|
|
5
|
+
* has zero layout impact and can animate via registered <color> transitions.
|
|
6
|
+
*
|
|
7
|
+
* @property declarations must be top-level (not inside @layer).
|
|
8
|
+
* inherits: false means each element owns its own instance — setting --bdc_color
|
|
9
|
+
* on :focus animates that element's border colour independently.
|
|
10
|
+
* ─────────────────────────────────────────────────────────────────────────────── */
|
|
11
|
+
|
|
12
|
+
@property --bdc_color {
|
|
13
|
+
syntax: '<color>';
|
|
14
|
+
inherits: false;
|
|
15
|
+
initial-value: currentcolor;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@property --bdc_width {
|
|
19
|
+
syntax: '<length>';
|
|
20
|
+
inherits: false;
|
|
21
|
+
initial-value: 1px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@property --bdc_offset {
|
|
25
|
+
syntax: '<length>';
|
|
26
|
+
inherits: false;
|
|
27
|
+
initial-value: -1px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@property --bdc_radius {
|
|
31
|
+
syntax: '<length-percentage>';
|
|
32
|
+
inherits: false;
|
|
33
|
+
initial-value: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* '*' syntax cannot be interpolated but gives a guaranteed none default */
|
|
37
|
+
@property --bdc_shadow {
|
|
38
|
+
syntax: '*';
|
|
39
|
+
inherits: false;
|
|
40
|
+
initial-value: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@layer component {
|
|
44
|
+
:root {
|
|
45
|
+
/* border: none by default — outline provides the visual edge */
|
|
46
|
+
--bdc_border: none;
|
|
47
|
+
|
|
48
|
+
/* Colour tokens */
|
|
49
|
+
--bdc_color: currentcolor;
|
|
50
|
+
--bdc_color--focus: var(--bds-interactive);
|
|
51
|
+
--bdc_color--error: var(--bds-error);
|
|
52
|
+
|
|
53
|
+
/* Geometry */
|
|
54
|
+
--bdc_width: 1px;
|
|
55
|
+
--bdc_offset: -1px; /* inset — appears inside the element like a traditional border */
|
|
56
|
+
--bdc_radius: var(--bds-border_radius--xs);
|
|
57
|
+
|
|
58
|
+
/* Shadow: opt-in, none by default */
|
|
59
|
+
--bdc_shadow: none;
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/css/index.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* @boostdev/components — framework-agnostic CSS barrel */
|
|
2
|
+
@import './bdc.css';
|
|
2
3
|
|
|
3
4
|
/* UI */
|
|
4
5
|
@import '../components/ui/Accordion/Accordion.module.css';
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
@import '../components/ui/Breadcrumb/Breadcrumb.module.css';
|
|
9
10
|
@import '../components/ui/Calendar/Calendar.module.css';
|
|
10
11
|
@import '../components/ui/Carousel/Carousel.module.css';
|
|
12
|
+
@import '../components/ui/Collapsible/Collapsible.module.css';
|
|
11
13
|
@import '../components/ui/DescriptionList/DescriptionList.module.css';
|
|
12
14
|
@import '../components/ui/Link/Link.module.css';
|
|
13
15
|
@import '../components/ui/Loading/Loading.module.css';
|
|
@@ -35,12 +37,15 @@
|
|
|
35
37
|
|
|
36
38
|
/* Form */
|
|
37
39
|
@import '../components/interaction/form/Checkbox/Checkbox.module.css';
|
|
40
|
+
@import '../components/interaction/form/CheckboxGroup/CheckboxGroup.module.css';
|
|
38
41
|
@import '../components/interaction/form/Combobox/Combobox.module.css';
|
|
39
42
|
@import '../components/interaction/form/FileInput/FileInput.module.css';
|
|
40
43
|
@import '../components/interaction/form/FormInput/FormInput.module.css';
|
|
41
44
|
@import '../components/interaction/form/NumberInput/NumberInput.module.css';
|
|
42
45
|
@import '../components/interaction/form/Radio/Radio.module.css';
|
|
46
|
+
@import '../components/interaction/form/RadioGroup/RadioGroup.module.css';
|
|
43
47
|
@import '../components/interaction/form/Select/Select.module.css';
|
|
48
|
+
@import '../components/interaction/form/SegmentedControl/SegmentedControl.module.css';
|
|
44
49
|
@import '../components/interaction/form/Slider/Slider.module.css';
|
|
45
50
|
@import '../components/interaction/form/Switch/Switch.module.css';
|
|
46
51
|
@import '../components/interaction/form/Textarea/Textarea.module.css';
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { Typography } from './components/ui/Typography';
|
|
|
30
30
|
|
|
31
31
|
// Interaction
|
|
32
32
|
export { Button } from './components/interaction/Button';
|
|
33
|
+
export type { ButtonProps } from './components/interaction/Button';
|
|
33
34
|
export { Command } from './components/interaction/Command';
|
|
34
35
|
export type { CommandItem } from './components/interaction/Command';
|
|
35
36
|
export { Dialog } from './components/interaction/Dialog';
|
|
@@ -42,12 +43,16 @@ export { ToastProvider, useToast } from './components/interaction/Toast';
|
|
|
42
43
|
|
|
43
44
|
// Form
|
|
44
45
|
export { Checkbox } from './components/interaction/form/Checkbox';
|
|
46
|
+
export { CheckboxGroup } from './components/interaction/form/CheckboxGroup';
|
|
47
|
+
export type { CheckboxGroupProps } from './components/interaction/form/CheckboxGroup';
|
|
45
48
|
export { Combobox } from './components/interaction/form/Combobox';
|
|
46
49
|
export type { ComboboxOption } from './components/interaction/form/Combobox';
|
|
47
50
|
export { FileInput } from './components/interaction/form/FileInput';
|
|
48
51
|
export { FormInput } from './components/interaction/form/FormInput';
|
|
49
52
|
export { NumberInput } from './components/interaction/form/NumberInput';
|
|
50
53
|
export { Radio } from './components/interaction/form/Radio';
|
|
54
|
+
export { RadioGroup } from './components/interaction/form/RadioGroup';
|
|
55
|
+
export type { RadioGroupProps } from './components/interaction/form/RadioGroup';
|
|
51
56
|
export { SegmentedControl } from './components/interaction/form/SegmentedControl';
|
|
52
57
|
export type { SegmentedControlProps, SegmentedControlOption } from './components/interaction/form/SegmentedControl';
|
|
53
58
|
export { Select } from './components/interaction/form/Select';
|
|
@@ -58,9 +63,11 @@ export { Textarea } from './components/interaction/form/Textarea';
|
|
|
58
63
|
|
|
59
64
|
// Layout
|
|
60
65
|
export { ButtonGroup } from './components/layout/ButtonGroup';
|
|
66
|
+
export type { ButtonGroupProps } from './components/layout/ButtonGroup';
|
|
61
67
|
export { Card } from './components/layout/Card';
|
|
62
68
|
export { SectionHeader } from './components/layout/SectionHeader';
|
|
63
69
|
export { IconWrapper } from './components/layout/IconWrapper';
|
|
70
|
+
export type { IconWrapperProps } from './components/layout/IconWrapper';
|
|
64
71
|
|
|
65
72
|
// Utilities
|
|
66
73
|
export { cn } from '@boostdev/design-system-foundation';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Design System/Borders" />
|
|
4
|
+
|
|
5
|
+
export const BorderDemo = ({ label, token, color = 'currentcolor', offset = '-1px', width = '1px' }) => (
|
|
6
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', alignItems: 'center' }}>
|
|
7
|
+
<div style={{
|
|
8
|
+
width: '8rem',
|
|
9
|
+
height: '5rem',
|
|
10
|
+
background: 'var(--bds-bg)',
|
|
11
|
+
borderRadius: '0.5rem',
|
|
12
|
+
outline: `${width} solid ${color}`,
|
|
13
|
+
outlineOffset: offset,
|
|
14
|
+
}} />
|
|
15
|
+
<div style={{ textAlign: 'center' }}>
|
|
16
|
+
<div style={{ fontSize: '0.8rem', fontWeight: 600 }}>{label}</div>
|
|
17
|
+
<div style={{ fontSize: '0.7rem', fontFamily: 'monospace', opacity: 0.6, marginTop: '0.1rem' }}>{token}</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const TokenRow = ({ token, type, defaultValue, description }) => (
|
|
23
|
+
<tr>
|
|
24
|
+
<td><code>{token}</code></td>
|
|
25
|
+
<td style={{ fontFamily: 'monospace' }}>{type}</td>
|
|
26
|
+
<td style={{ fontFamily: 'monospace' }}>{defaultValue}</td>
|
|
27
|
+
<td>{description}</td>
|
|
28
|
+
</tr>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
# Borders
|
|
32
|
+
|
|
33
|
+
The `bdc` system unifies all component borders behind five registered CSS custom properties. Every form control, card, and interactive surface draws its border from these tokens — ensuring consistent visual weight, smooth animated state transitions, and a single override point for theming.
|
|
34
|
+
|
|
35
|
+
## Why outline, not border?
|
|
36
|
+
|
|
37
|
+
Components use `outline` rather than `border` for their decorative edge. The reasons:
|
|
38
|
+
|
|
39
|
+
- **Zero layout impact** — `outline` lives outside the box model, so changing width or color never shifts surrounding elements.
|
|
40
|
+
- **Inset appearance** — `outline-offset: -1px` pulls the ring inside the element, making it visually indistinguishable from a traditional border.
|
|
41
|
+
- **Animatable colour** — `@property --bdc_color { syntax: '<color>'; }` registers the token as a typed custom property, which lets the browser interpolate it directly. A single `transition: --bdc_color 150ms ease-out` handles focus, hover, and error state changes without JavaScript.
|
|
42
|
+
|
|
43
|
+
## Tokens
|
|
44
|
+
|
|
45
|
+
<table>
|
|
46
|
+
<thead>
|
|
47
|
+
<tr>
|
|
48
|
+
<th>Token</th>
|
|
49
|
+
<th>Type</th>
|
|
50
|
+
<th>Default</th>
|
|
51
|
+
<th>Description</th>
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
<TokenRow token="--bdc_color" type="<color>" defaultValue="currentcolor" description="Border colour — transitions smoothly between states" />
|
|
56
|
+
<TokenRow token="--bdc_width" type="<length>" defaultValue="1px" description="Border width" />
|
|
57
|
+
<TokenRow token="--bdc_offset" type="<length>" defaultValue="-1px" description="Negative = inset (border-like); positive = outer halo" />
|
|
58
|
+
<TokenRow token="--bdc_radius" type="<length-percentage>" defaultValue="0" description="Border radius, synced with the element's own radius" />
|
|
59
|
+
<TokenRow token="--bdc_shadow" type="*" defaultValue="none" description="Box shadow — opt-in accent, e.g. focus glow" />
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Visual examples
|
|
66
|
+
|
|
67
|
+
<div style={{ display: 'flex', gap: '2.5rem', flexWrap: 'wrap', padding: '2rem', background: 'var(--bds-bg--subtle)', borderRadius: '0.75rem', marginBottom: '2rem' }}>
|
|
68
|
+
<BorderDemo label="Default" token="currentcolor" color="currentcolor" />
|
|
69
|
+
<BorderDemo label="Focus" token="--bdc_color--focus" color="var(--bds-interactive)" />
|
|
70
|
+
<BorderDemo label="Error" token="--bdc_color--error" color="var(--bds-error)" />
|
|
71
|
+
<BorderDemo label="Outer halo" token="--bdc_offset: 2px" color="var(--bds-interactive)" offset="2px" width="2px" />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
| State | `--bdc_color` resolves to | Trigger |
|
|
75
|
+
|-------|--------------------------|---------|
|
|
76
|
+
| Default | `currentcolor` | — |
|
|
77
|
+
| Focus | `var(--bdc_color--focus)` → `--bds-interactive` | `:focus` |
|
|
78
|
+
| Error | `var(--bdc_color--error)` → `--bds-error` | `[aria-invalid="true"]` |
|
|
79
|
+
| Keyboard ring | `var(--bds-outline_default)` | `:focus-visible` |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Component pattern
|
|
84
|
+
|
|
85
|
+
Every component that renders a bordered element follows this CSS pattern:
|
|
86
|
+
|
|
87
|
+
```css
|
|
88
|
+
.element {
|
|
89
|
+
/* Suppress any browser default border — consumer can re-enable
|
|
90
|
+
via the --component_border custom property if needed. */
|
|
91
|
+
border: none;
|
|
92
|
+
|
|
93
|
+
/* bdc defaults */
|
|
94
|
+
--bdc_color: currentcolor;
|
|
95
|
+
|
|
96
|
+
/* Rendered border (zero layout impact) */
|
|
97
|
+
outline: var(--bdc_width) solid var(--bdc_color);
|
|
98
|
+
outline-offset: var(--bdc_offset);
|
|
99
|
+
border-radius: var(--bdc_radius);
|
|
100
|
+
box-shadow: var(--bdc_shadow);
|
|
101
|
+
|
|
102
|
+
/* Smooth colour transitions via registered @property */
|
|
103
|
+
transition: --bdc_color 150ms ease-out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* State overrides — only the colour token changes */
|
|
107
|
+
.element:focus { --bdc_color: var(--bdc_color--focus); }
|
|
108
|
+
.element[aria-invalid="true"] { --bdc_color: var(--bdc_color--error); }
|
|
109
|
+
|
|
110
|
+
/* Keyboard accessibility ring — replaces outline on focus-visible */
|
|
111
|
+
.element:focus-visible {
|
|
112
|
+
outline: var(--bds-outline_default);
|
|
113
|
+
outline-offset: var(--bds-outline_offset);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Consumer overrides
|
|
120
|
+
|
|
121
|
+
Override a component's border colour for a specific context by reassigning `--bdc_color`:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
/* Scoped to a single component instance */
|
|
125
|
+
.my-context .my-input {
|
|
126
|
+
--bdc_color: var(--bds-warning);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Override globally via the token layer */
|
|
130
|
+
@layer tokens.override {
|
|
131
|
+
:root {
|
|
132
|
+
--bdc_color: var(--bds-brand);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
To change width or radius independently:
|
|
138
|
+
|
|
139
|
+
```css
|
|
140
|
+
.my-card {
|
|
141
|
+
--bdc_width: 2px;
|
|
142
|
+
--bdc_radius: var(--bds-border_radius--m);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Import
|
|
149
|
+
|
|
150
|
+
The `bdc` tokens are included automatically when you import the component stylesheet:
|
|
151
|
+
|
|
152
|
+
```css
|
|
153
|
+
/* Included automatically with the full component CSS */
|
|
154
|
+
@import '@boostdev/components/css';
|
|
155
|
+
|
|
156
|
+
/* Or load the token layer standalone */
|
|
157
|
+
@import '@boostdev/components/css/bdc';
|
|
158
|
+
```
|