@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.
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 +1380 -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 +1380 -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 +13 -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
@@ -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(--color_bg--subtle);
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(--color_on-bg));
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(--color_interactive);
30
- --tab_border-color: var(--color_interactive);
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(--color_interactive);
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
  }
@@ -87,7 +87,6 @@ export function Tabs({ tabs, defaultTab, className }: Readonly<TabsProps>) {
87
87
  id={panelId}
88
88
  role="tabpanel"
89
89
  aria-labelledby={tabId}
90
- tabIndex={0}
91
90
  hidden={!isActive}
92
91
  className={css.panel}
93
92
  >
@@ -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(--color_on-bg));
14
- color: var(--tooltip_text, var(--color_bg));
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 className={cn(css.wrapper, className)}>
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(--color_on-bg));
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
  }
@@ -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
+ ```