@boostdev/design-system-components 1.2.0 → 1.2.2

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 (168) hide show
  1. package/AGENTS.md +46 -4
  2. package/README.md +15 -15
  3. package/dist/client.cjs +149 -124
  4. package/dist/client.css +503 -525
  5. package/dist/client.d.cts +71 -96
  6. package/dist/client.d.ts +71 -96
  7. package/dist/client.js +149 -124
  8. package/dist/index.cjs +149 -124
  9. package/dist/index.css +503 -525
  10. package/dist/index.d.cts +71 -96
  11. package/dist/index.d.ts +71 -96
  12. package/dist/index.js +149 -124
  13. package/dist/web-components/{chunk-6MH5UWUD.js → chunk-2FGATTGT.js} +5 -0
  14. package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
  15. package/dist/web-components/{chunk-DI46Q2EA.js → chunk-OCODKRVZ.js} +57 -82
  16. package/dist/web-components/index.d.ts +571 -1
  17. package/dist/web-components/index.js +1501 -4
  18. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  19. package/dist/web-components/interaction/bds-drawer.js +1 -1
  20. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  21. package/package.json +1 -1
  22. package/src/components/interaction/Command/Command.tsx +4 -3
  23. package/src/components/interaction/Dialog/Dialog.tsx +4 -5
  24. package/src/components/interaction/Drawer/Drawer.module.css +37 -62
  25. package/src/components/interaction/Drawer/Drawer.spec.tsx +3 -3
  26. package/src/components/interaction/Drawer/Drawer.tsx +25 -24
  27. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +4 -2
  28. package/src/components/interaction/Popover/Popover.tsx +4 -3
  29. package/src/components/interaction/Rating/Rating.tsx +4 -2
  30. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +4 -6
  31. package/src/components/interaction/form/Combobox/Combobox.tsx +4 -2
  32. package/src/components/interaction/form/FileInput/FileInput.tsx +4 -3
  33. package/src/components/interaction/form/NumberInput/NumberInput.tsx +4 -3
  34. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +4 -6
  35. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +8 -4
  36. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +13 -6
  37. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -6
  38. package/src/components/interaction/form/atoms/InputContainer.tsx +2 -2
  39. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +4 -6
  40. package/src/components/layout/Card/Card.tsx +4 -10
  41. package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -5
  42. package/src/components/layout/SectionHeader/SectionHeader.tsx +5 -4
  43. package/src/components/ui/Accordion/Accordion.tsx +4 -3
  44. package/src/components/ui/Alert/Alert.tsx +4 -3
  45. package/src/components/ui/Avatar/Avatar.tsx +5 -3
  46. package/src/components/ui/Badge/Badge.tsx +4 -5
  47. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +4 -3
  48. package/src/components/ui/Calendar/Calendar.tsx +4 -4
  49. package/src/components/ui/Carousel/Carousel.tsx +4 -4
  50. package/src/components/ui/DescriptionList/DescriptionList.tsx +4 -4
  51. package/src/components/ui/Loading/Loading.tsx +4 -3
  52. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +4 -2
  53. package/src/components/ui/Pagination/Pagination.tsx +4 -2
  54. package/src/components/ui/Progress/Progress.tsx +4 -2
  55. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +4 -1
  56. package/src/components/ui/Separator/Separator.tsx +5 -3
  57. package/src/components/ui/Skeleton/Skeleton.tsx +4 -3
  58. package/src/components/ui/SkipLink/SkipLink.tsx +4 -5
  59. package/src/components/ui/Tabs/Tabs.tsx +4 -4
  60. package/src/components/ui/Tooltip/Tooltip.tsx +4 -2
  61. package/src/components/ui/Typography/Typography.tsx +4 -5
  62. package/src/stories/DesignSystem/Grid.mdx +2 -0
  63. package/src/stories/Introduction.mdx +2 -1
  64. package/src/web-components/index.ts +12 -0
  65. package/src/web-components/interaction/BdsAccordion.mdx +80 -28
  66. package/src/web-components/interaction/BdsAccordion.stories.tsx +67 -58
  67. package/src/web-components/interaction/BdsCollapsible.mdx +93 -23
  68. package/src/web-components/interaction/BdsCollapsible.stories.tsx +29 -48
  69. package/src/web-components/interaction/BdsDialog.mdx +88 -27
  70. package/src/web-components/interaction/BdsDialog.stories.tsx +129 -47
  71. package/src/web-components/interaction/BdsDrawer.mdx +85 -27
  72. package/src/web-components/interaction/BdsDrawer.stories.tsx +161 -31
  73. package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
  74. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
  75. package/src/web-components/interaction/BdsSkipLink.mdx +72 -16
  76. package/src/web-components/interaction/BdsSkipLink.stories.tsx +47 -34
  77. package/src/web-components/interaction/BdsTabs.mdx +97 -27
  78. package/src/web-components/interaction/BdsTabs.stories.tsx +69 -55
  79. package/src/web-components/interaction/BdsTooltip.mdx +84 -18
  80. package/src/web-components/interaction/BdsTooltip.stories.tsx +49 -30
  81. package/src/web-components/interaction/bds-collapsible.ts +1 -0
  82. package/src/web-components/interaction/bds-drawer.ts +59 -82
  83. package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
  84. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
  85. package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
  86. package/src/web-components/interaction/form/BdsCheckbox.mdx +57 -20
  87. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +35 -36
  88. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
  89. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
  90. package/src/web-components/interaction/form/BdsCombobox.mdx +52 -33
  91. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +55 -53
  92. package/src/web-components/interaction/form/BdsFileInput.mdx +59 -24
  93. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +38 -53
  94. package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
  95. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
  96. package/src/web-components/interaction/form/BdsNumberInput.mdx +51 -22
  97. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +24 -46
  98. package/src/web-components/interaction/form/BdsRadio.mdx +53 -22
  99. package/src/web-components/interaction/form/BdsRadio.stories.tsx +19 -51
  100. package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
  101. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
  102. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +76 -23
  103. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +64 -54
  104. package/src/web-components/interaction/form/BdsSelect.mdx +59 -21
  105. package/src/web-components/interaction/form/BdsSelect.stories.tsx +47 -56
  106. package/src/web-components/interaction/form/BdsSlider.mdx +55 -27
  107. package/src/web-components/interaction/form/BdsSlider.stories.tsx +23 -44
  108. package/src/web-components/interaction/form/BdsSwitch.mdx +53 -20
  109. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +22 -37
  110. package/src/web-components/interaction/form/BdsTextarea.mdx +54 -22
  111. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +35 -44
  112. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
  113. package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
  114. package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
  115. package/src/web-components/interaction/form/bds-form-input.ts +268 -0
  116. package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
  117. package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
  118. package/src/web-components/interaction/form/bds-segmented-control.ts +5 -0
  119. package/src/web-components/ui/BdsAvatar.mdx +29 -20
  120. package/src/web-components/ui/BdsAvatar.stories.tsx +14 -28
  121. package/src/web-components/ui/BdsBreadcrumb.mdx +22 -19
  122. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +11 -21
  123. package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
  124. package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
  125. package/src/web-components/ui/BdsCalendar.mdx +93 -0
  126. package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
  127. package/src/web-components/ui/BdsCard.mdx +30 -25
  128. package/src/web-components/ui/BdsCard.stories.tsx +19 -42
  129. package/src/web-components/ui/BdsCarousel.mdx +83 -0
  130. package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
  131. package/src/web-components/ui/BdsDescriptionList.mdx +13 -16
  132. package/src/web-components/ui/BdsDescriptionList.stories.tsx +34 -49
  133. package/src/web-components/ui/BdsIconWrapper.mdx +20 -20
  134. package/src/web-components/ui/BdsIconWrapper.stories.tsx +46 -41
  135. package/src/web-components/ui/BdsLink.mdx +21 -17
  136. package/src/web-components/ui/BdsLink.stories.tsx +19 -20
  137. package/src/web-components/ui/BdsLoading.mdx +9 -13
  138. package/src/web-components/ui/BdsLoading.stories.tsx +8 -10
  139. package/src/web-components/ui/BdsNotificationBanner.mdx +32 -37
  140. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +38 -47
  141. package/src/web-components/ui/BdsPagination.mdx +74 -0
  142. package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
  143. package/src/web-components/ui/BdsProgress.mdx +24 -17
  144. package/src/web-components/ui/BdsProgress.stories.tsx +55 -29
  145. package/src/web-components/ui/BdsProgressCircle.mdx +24 -18
  146. package/src/web-components/ui/BdsProgressCircle.stories.tsx +17 -29
  147. package/src/web-components/ui/BdsRating.mdx +22 -22
  148. package/src/web-components/ui/BdsRating.stories.tsx +19 -13
  149. package/src/web-components/ui/BdsSectionHeader.mdx +31 -28
  150. package/src/web-components/ui/BdsSectionHeader.stories.tsx +21 -23
  151. package/src/web-components/ui/BdsSeparator.mdx +22 -8
  152. package/src/web-components/ui/BdsSeparator.stories.tsx +27 -19
  153. package/src/web-components/ui/BdsSkeleton.mdx +20 -26
  154. package/src/web-components/ui/BdsSkeleton.stories.tsx +23 -23
  155. package/src/web-components/ui/BdsTable.mdx +81 -0
  156. package/src/web-components/ui/BdsTable.stories.tsx +83 -0
  157. package/src/web-components/ui/BdsTypography.mdx +20 -29
  158. package/src/web-components/ui/BdsTypography.stories.tsx +19 -18
  159. package/src/web-components/ui/bds-button-group.spec.ts +40 -0
  160. package/src/web-components/ui/bds-button-group.ts +78 -0
  161. package/src/web-components/ui/bds-calendar.spec.ts +91 -0
  162. package/src/web-components/ui/bds-calendar.ts +427 -0
  163. package/src/web-components/ui/bds-carousel.spec.ts +64 -0
  164. package/src/web-components/ui/bds-carousel.ts +296 -0
  165. package/src/web-components/ui/bds-pagination.spec.ts +67 -0
  166. package/src/web-components/ui/bds-pagination.ts +197 -0
  167. package/src/web-components/ui/bds-table.spec.ts +45 -0
  168. package/src/web-components/ui/bds-table.ts +96 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsCollapsible
3
- } from "../chunk-5IPHEONG.js";
3
+ } from "../chunk-6I2DBFQ7.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsCollapsible
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsDrawer
3
- } from "../chunk-DI46Q2EA.js";
3
+ } from "../chunk-OCODKRVZ.js";
4
4
  import "../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsDrawer
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BdsSegmentedControl
3
- } from "../../chunk-6MH5UWUD.js";
3
+ } from "../../chunk-2FGATTGT.js";
4
4
  import "../../chunk-LGEZYFUU.js";
5
5
  export {
6
6
  BdsSegmentedControl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
5
  "keywords": [
6
6
  "React",
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef, useId, KeyboardEvent, useMemo } from 'react';
1
+ import { HTMLAttributes, useState, useEffect, useRef, useId, KeyboardEvent, useMemo } from 'react';
2
2
  import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-commands';
3
3
 
4
4
  // Install once at module load — no-op if native support is present.
@@ -17,8 +17,7 @@ export interface CommandItem {
17
17
  onSelect: () => void;
18
18
  }
19
19
 
20
- interface CommandProps extends WithClassName {
21
- id?: string;
20
+ interface CommandProps extends WithClassName, HTMLAttributes<HTMLDialogElement> {
22
21
  isOpen: boolean;
23
22
  onOpen?: () => void;
24
23
  onClose: () => void;
@@ -34,6 +33,7 @@ export function Command({
34
33
  items,
35
34
  placeholder = 'Search commands…',
36
35
  className,
36
+ ...rest
37
37
  }: Readonly<CommandProps>) {
38
38
  const [query, setQuery] = useState('');
39
39
  const [activeIndex, setActiveIndex] = useState(0);
@@ -139,6 +139,7 @@ export function Command({
139
139
 
140
140
  return (
141
141
  <dialog
142
+ {...rest}
142
143
  ref={dialogRef}
143
144
  id={dialogId}
144
145
  className={cn(css.dialog, className)}
@@ -1,4 +1,4 @@
1
- import { ReactNode, useEffect, useId, useRef } from 'react';
1
+ import { HTMLAttributes, useEffect, useId, useRef } from 'react';
2
2
  import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-commands';
3
3
 
4
4
  // Install once at module load — no-op if native support is present.
@@ -9,15 +9,13 @@ import css from './Dialog.module.css';
9
9
  import { cn } from '@boostdev/design-system-foundation';
10
10
  import type { WithClassName } from '../../../types';
11
11
 
12
- interface DialogProps extends WithClassName {
13
- children: ReactNode;
14
- id?: string;
12
+ interface DialogProps extends WithClassName, HTMLAttributes<HTMLDialogElement> {
15
13
  isOpen?: boolean;
16
14
  onOpen?: () => void;
17
15
  onClose?: () => void;
18
16
  }
19
17
 
20
- export function Dialog({ children, id: idProp, isOpen = false, className, onOpen, onClose }: DialogProps) {
18
+ export function Dialog({ children, id: idProp, isOpen = false, className, onOpen, onClose, ...rest }: DialogProps) {
21
19
  const generatedId = useId();
22
20
  const id = idProp ?? generatedId;
23
21
 
@@ -92,6 +90,7 @@ export function Dialog({ children, id: idProp, isOpen = false, className, onOpen
92
90
 
93
91
  return (
94
92
  <dialog
93
+ {...rest}
95
94
  ref={dialogRef}
96
95
  id={id}
97
96
  className={cn(className, css.dialog)}
@@ -1,84 +1,65 @@
1
1
  @layer component {
2
2
  .drawer {
3
3
  position: fixed;
4
- inset: 0;
4
+ inset-block: 0;
5
+ inset-inline: auto 0;
6
+ inline-size: min(28rem, 90vw);
7
+ max-inline-size: 100vw;
8
+ block-size: 100dvh;
9
+ max-block-size: 100dvh;
5
10
  margin: 0;
6
11
  padding: 0;
7
- inline-size: 100%;
8
- max-inline-size: 100%;
9
- block-size: 100%;
10
- max-block-size: 100%;
11
- border: none;
12
- background: transparent;
13
- overflow: hidden;
14
- transition:
15
- display var(--bds-animation_transition-duration) allow-discrete,
16
- overlay var(--bds-animation_transition-duration) allow-discrete;
17
- }
18
-
19
- .drawer::backdrop {
20
- background-color: var(--color_backdrop, rgb(0 0 0 / 50%));
21
- backdrop-filter: blur(3px);
22
- transition:
23
- display var(--bds-animation_transition-duration) allow-discrete,
24
- overlay var(--bds-animation_transition-duration) allow-discrete,
25
- background-color var(--bds-animation_transition-duration) var(--bds-animation_easing),
26
- backdrop-filter var(--bds-animation_transition-duration) var(--bds-animation_easing);
27
- }
28
-
29
- @starting-style {
30
- .drawer[open]::backdrop {
31
- background-color: transparent;
32
- backdrop-filter: blur(0);
33
- }
34
- }
35
-
36
- .panel {
37
- position: absolute;
38
- inset-block: 0;
39
- inset-inline: auto;
40
12
  display: flex;
41
13
  flex-direction: column;
42
- inline-size: min(28rem, 90vw);
43
- block-size: 100%;
44
14
  background-color: var(--drawer_color, var(--bds-color_bg));
45
15
  color: var(--drawer_on-color, var(--bds-color_on-bg));
16
+ border: none;
46
17
  box-shadow: var(--bds-shadow_xl);
47
18
  overflow: hidden;
48
19
  translate: 100% 0;
49
- transition: translate var(--bds-animation_transition-duration) var(--bds-animation_easing);
50
- }
51
-
52
- .--side_right .panel {
53
- inset-inline-end: 0;
54
- translate: 100% 0;
20
+ transition:
21
+ translate var(--bds-animation_transition-duration) var(--bds-animation_easing),
22
+ display var(--bds-animation_transition-duration) var(--bds-animation_easing) allow-discrete,
23
+ overlay var(--bds-animation_transition-duration) var(--bds-animation_easing) allow-discrete;
55
24
  }
56
25
 
57
- .--side_left .panel {
58
- inset-inline-start: 0;
26
+ .--side_left.drawer {
27
+ inset-inline: 0 auto;
59
28
  translate: -100% 0;
60
29
  }
61
30
 
62
- .drawer[open] .panel {
31
+ .drawer[open] {
63
32
  translate: 0 0;
64
33
  }
65
34
 
66
- .drawer[open].--side_right .panel {
67
- animation: bdc-drawer-slide-right var(--bds-animation_transition-duration) var(--bds-animation_easing) both;
35
+ @starting-style {
36
+ .drawer[open] {
37
+ translate: 100% 0;
38
+ }
39
+
40
+ .--side_left.drawer[open] {
41
+ translate: -100% 0;
42
+ }
68
43
  }
69
44
 
70
- .drawer[open].--side_left .panel {
71
- animation: bdc-drawer-slide-left var(--bds-animation_transition-duration) var(--bds-animation_easing) both;
45
+ .drawer::backdrop {
46
+ background-color: var(--color_backdrop, rgb(0 0 0 / 50%));
47
+ backdrop-filter: blur(3px);
48
+ opacity: 0;
49
+ transition:
50
+ opacity var(--bds-animation_transition-duration) var(--bds-animation_easing),
51
+ display var(--bds-animation_transition-duration) var(--bds-animation_easing) allow-discrete,
52
+ overlay var(--bds-animation_transition-duration) var(--bds-animation_easing) allow-discrete;
72
53
  }
73
54
 
74
- @keyframes bdc-drawer-slide-right {
75
- from { translate: 100% 0; }
76
- to { translate: 0 0; }
55
+ .drawer[open]::backdrop {
56
+ opacity: 1;
77
57
  }
78
58
 
79
- @keyframes bdc-drawer-slide-left {
80
- from { translate: -100% 0; }
81
- to { translate: 0 0; }
59
+ @starting-style {
60
+ .drawer[open]::backdrop {
61
+ opacity: 0;
62
+ }
82
63
  }
83
64
 
84
65
  .header {
@@ -128,14 +109,8 @@
128
109
 
129
110
  @media (prefers-reduced-motion: reduce) {
130
111
  .drawer,
131
- .drawer::backdrop,
132
- .panel {
112
+ .drawer::backdrop {
133
113
  transition: none;
134
114
  }
135
-
136
- .drawer[open].--side_right .panel,
137
- .drawer[open].--side_left .panel {
138
- animation: none;
139
- }
140
115
  }
141
116
  }
@@ -85,13 +85,13 @@ describe('Drawer', () => {
85
85
  expect(dialog).toHaveAttribute('aria-modal', 'true');
86
86
  });
87
87
 
88
- it('renders aria-label on the dialog when ariaLabel prop is provided', () => {
89
- render(<Drawer isOpen title="Settings" ariaLabel="Settings panel" onClose={() => {}}>Content</Drawer>);
88
+ it('renders aria-label on the dialog when aria-label prop is provided', () => {
89
+ render(<Drawer isOpen title="Settings" aria-label="Settings panel" onClose={() => {}}>Content</Drawer>);
90
90
  const dialog = document.querySelector('dialog');
91
91
  expect(dialog).toHaveAttribute('aria-label', 'Settings panel');
92
92
  });
93
93
 
94
- it('does not render aria-label when ariaLabel prop is not provided', () => {
94
+ it('does not render aria-label when aria-label prop is not provided', () => {
95
95
  render(<Drawer isOpen title="Settings" onClose={() => {}}>Content</Drawer>);
96
96
  const dialog = document.querySelector('dialog');
97
97
  expect(dialog).not.toHaveAttribute('aria-label');
@@ -1,4 +1,4 @@
1
- import { ReactNode, useEffect, useId, useRef } from 'react';
1
+ import { HTMLAttributes, ReactNode, useEffect, useId, useRef } from 'react';
2
2
  import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-commands';
3
3
 
4
4
  // Install once at module load — no-op if native support is present.
@@ -8,15 +8,12 @@ import css from './Drawer.module.css';
8
8
  import { cn } from '@boostdev/design-system-foundation';
9
9
  import type { WithClassName } from '../../../types';
10
10
 
11
- interface DrawerProps extends WithClassName {
12
- id?: string;
11
+ interface DrawerProps extends WithClassName, Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
13
12
  isOpen: boolean;
14
13
  onOpen?: () => void;
15
14
  onClose: () => void;
16
15
  title: ReactNode;
17
- children: ReactNode;
18
16
  side?: 'left' | 'right';
19
- ariaLabel?: string;
20
17
  }
21
18
 
22
19
  export function Drawer({
@@ -27,8 +24,8 @@ export function Drawer({
27
24
  title,
28
25
  children,
29
26
  side = 'right',
30
- ariaLabel,
31
27
  className,
28
+ ...rest
32
29
  }: Readonly<DrawerProps>) {
33
30
  const generatedId = useId();
34
31
  const id = idProp ?? generatedId;
@@ -75,7 +72,13 @@ export function Drawer({
75
72
 
76
73
  // Close on backdrop click
77
74
  const handleClick = (e: React.MouseEvent<HTMLDialogElement>) => {
78
- if (e.target === dialogRef.current) onClose();
75
+ const dialog = dialogRef.current;
76
+ if (!dialog) return;
77
+ const rect = dialog.getBoundingClientRect();
78
+ const outside =
79
+ e.clientX < rect.left || e.clientX > rect.right ||
80
+ e.clientY < rect.top || e.clientY > rect.bottom;
81
+ if (outside) onClose();
79
82
  };
80
83
 
81
84
  // Close on Escape (dialog handles it natively, but we sync state)
@@ -86,31 +89,29 @@ export function Drawer({
86
89
 
87
90
  return (
88
91
  <dialog
92
+ {...rest}
89
93
  ref={dialogRef}
90
94
  id={id}
91
95
  className={cn(css.drawer, css[`--side_${side}`], className)}
92
- aria-label={ariaLabel}
93
96
  aria-modal="true"
94
97
  onClick={handleClick}
95
98
  onCancel={handleCancel}
96
99
  >
97
- <div className={css.panel}>
98
- <div className={css.header}>
99
- {!!title && title}
100
- <button
101
- type="button"
102
- className={css.closeButton}
103
- commandfor={id}
104
- command="close"
105
- aria-label="Close drawer"
106
- >
107
- <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
108
- <path strokeLinecap="round" strokeLinejoin="round" d="M18 6L6 18M6 6l12 12" />
109
- </svg>
110
- </button>
111
- </div>
112
- <div className={css.body}>{children}</div>
100
+ <div className={css.header}>
101
+ {!!title && title}
102
+ <button
103
+ type="button"
104
+ className={css.closeButton}
105
+ commandfor={id}
106
+ command="close"
107
+ aria-label="Close drawer"
108
+ >
109
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
110
+ <path strokeLinecap="round" strokeLinejoin="round" d="M18 6L6 18M6 6l12 12" />
111
+ </svg>
112
+ </button>
113
113
  </div>
114
+ <div className={css.body}>{children}</div>
114
115
  </dialog>
115
116
  );
116
117
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ HTMLAttributes,
2
3
  KeyboardEvent,
3
4
  ReactElement,
4
5
  cloneElement,
@@ -21,7 +22,7 @@ export interface DropdownMenuItem {
21
22
  separator?: boolean;
22
23
  }
23
24
 
24
- interface DropdownMenuProps extends WithClassName {
25
+ interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
25
26
  trigger: ReactElement;
26
27
  items: DropdownMenuItem[];
27
28
  placement?: 'bottom-start' | 'bottom-end';
@@ -32,6 +33,7 @@ export function DropdownMenu({
32
33
  items,
33
34
  placement = 'bottom-start',
34
35
  className,
36
+ ...rest
35
37
  }: Readonly<DropdownMenuProps>) {
36
38
  const [isOpen, setIsOpen] = useState(false);
37
39
  const containerRef = useRef<HTMLDivElement>(null);
@@ -105,7 +107,7 @@ export function DropdownMenu({
105
107
  : trigger;
106
108
 
107
109
  return (
108
- <div ref={containerRef} className={cn(css.wrapper, className)}>
110
+ <div {...rest} ref={containerRef} className={cn(css.wrapper, className)}>
109
111
  {triggerEl}
110
112
  {isOpen && (
111
113
  <ul
@@ -1,4 +1,5 @@
1
1
  import {
2
+ HTMLAttributes,
2
3
  ReactElement,
3
4
  ReactNode,
4
5
  cloneElement,
@@ -25,11 +26,10 @@ if (
25
26
  import('@oddbird/css-anchor-positioning').catch(() => {});
26
27
  }
27
28
 
28
- interface PopoverProps extends WithClassName {
29
+ interface PopoverProps extends WithClassName, Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
29
30
  children: ReactElement;
30
31
  content: ReactNode;
31
32
  placement?: 'top' | 'bottom' | 'left' | 'right';
32
- 'aria-label'?: string;
33
33
  }
34
34
 
35
35
  export function Popover({
@@ -38,6 +38,7 @@ export function Popover({
38
38
  placement = 'bottom',
39
39
  className,
40
40
  'aria-label': ariaLabel,
41
+ ...rest
41
42
  }: Readonly<PopoverProps>) {
42
43
  const [isOpen, setIsOpen] = useState(false);
43
44
  const containerRef = useRef<HTMLSpanElement>(null);
@@ -102,7 +103,7 @@ export function Popover({
102
103
  : children;
103
104
 
104
105
  return (
105
- <span ref={containerRef} className={cn(css.wrapper, className)}>
106
+ <span {...rest} ref={containerRef} className={cn(css.wrapper, className)}>
106
107
  {trigger}
107
108
  <div
108
109
  ref={panelRef}
@@ -1,15 +1,17 @@
1
+ import { HTMLAttributes } from 'react';
1
2
  import css from './Rating.module.css';
2
3
  import { cn } from '@boostdev/design-system-foundation';
3
4
  import type { WithClassName } from '../../../types';
4
5
 
5
- interface RatingProps extends WithClassName {
6
+ interface RatingProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
6
7
  value: number;
7
8
  max?: number;
8
9
  }
9
10
 
10
- export function Rating({ value, max = 5, className }: RatingProps) {
11
+ export function Rating({ value, max = 5, className, ...rest }: RatingProps) {
11
12
  return (
12
13
  <div
14
+ {...rest}
13
15
  className={cn(css.rating, className)}
14
16
  role="img"
15
17
  aria-label={`${value} out of ${max} stars`}
@@ -1,16 +1,14 @@
1
- import { ReactNode, useId } from 'react';
1
+ import { FieldsetHTMLAttributes, useId } from 'react';
2
2
  import css from './CheckboxGroup.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { cn } from '@boostdev/design-system-foundation';
5
5
  import type { WithClassName } from '../../../../types';
6
6
 
7
- export interface CheckboxGroupProps extends WithClassName {
7
+ export interface CheckboxGroupProps extends WithClassName, FieldsetHTMLAttributes<HTMLFieldSetElement> {
8
8
  legend: string;
9
- children: ReactNode;
10
9
  error?: string;
11
10
  hint?: string;
12
11
  required?: boolean;
13
- disabled?: boolean;
14
12
  }
15
13
 
16
14
  export function CheckboxGroup({
@@ -19,8 +17,8 @@ export function CheckboxGroup({
19
17
  error,
20
18
  hint,
21
19
  required,
22
- disabled,
23
20
  className,
21
+ ...rest
24
22
  }: Readonly<CheckboxGroupProps>) {
25
23
  const id = useId();
26
24
  const hintId = id + 'hint';
@@ -29,8 +27,8 @@ export function CheckboxGroup({
29
27
 
30
28
  return (
31
29
  <fieldset
30
+ {...rest}
32
31
  className={cn(css.group, className)}
33
- disabled={disabled}
34
32
  aria-required={required || undefined}
35
33
  aria-describedby={describedBy}
36
34
  >
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ChangeEvent,
3
+ HTMLAttributes,
3
4
  KeyboardEvent,
4
5
  ReactNode,
5
6
  useEffect,
@@ -21,7 +22,7 @@ export interface ComboboxOption {
21
22
  disabled?: boolean;
22
23
  }
23
24
 
24
- interface ComboboxProps extends WithClassName {
25
+ interface ComboboxProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
25
26
  label: ReactNode;
26
27
  name: string;
27
28
  options: ComboboxOption[];
@@ -44,6 +45,7 @@ export function Combobox({
44
45
  error,
45
46
  hint,
46
47
  className,
48
+ ...rest
47
49
  }: Readonly<ComboboxProps>) {
48
50
  const id = name + useId();
49
51
  const listboxId = id + 'listbox';
@@ -122,7 +124,7 @@ export function Combobox({
122
124
  };
123
125
 
124
126
  return (
125
- <InputContainer className={cn(css.formGroup, className)}>
127
+ <InputContainer {...rest} className={cn(css.formGroup, className)}>
126
128
  <Label id={id} label={label} />
127
129
  <div ref={containerRef} className={css.inputWrapper}>
128
130
  <input
@@ -1,11 +1,11 @@
1
- import { useId, useRef, useState, DragEvent, ChangeEvent } from 'react';
1
+ import { HTMLAttributes, useId, useRef, useState, DragEvent, ChangeEvent } from 'react';
2
2
  import css from './FileInput.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { InputContainer } from '../atoms/InputContainer';
5
5
  import { cn } from '@boostdev/design-system-foundation';
6
6
  import type { WithClassName } from '../../../../types';
7
7
 
8
- interface FileInputProps extends WithClassName {
8
+ interface FileInputProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
9
9
  label: string;
10
10
  name: string;
11
11
  accept?: string;
@@ -28,6 +28,7 @@ export function FileInput({
28
28
  hint,
29
29
  onChange,
30
30
  className,
31
+ ...rest
31
32
  }: Readonly<FileInputProps>) {
32
33
  const uid = name + useId();
33
34
  const hintId = uid + 'hint';
@@ -73,7 +74,7 @@ export function FileInput({
73
74
  const handleDragLeave = () => setIsDragging(false);
74
75
 
75
76
  return (
76
- <InputContainer className={cn(css.formGroup, className)}>
77
+ <InputContainer {...rest} className={cn(css.formGroup, className)}>
77
78
  <label
78
79
  htmlFor={uid}
79
80
  className={cn(css.dropZone, isDragging && css.isDragging, error && css.hasError, disabled && css.isDisabled)}
@@ -1,4 +1,4 @@
1
- import {useId, useRef, useState} from 'react';
1
+ import {HTMLAttributes, useId, useRef, useState} from 'react';
2
2
  import css from './NumberInput.module.css';
3
3
  import {Label} from '../atoms/Label';
4
4
  import {Message} from '../atoms/Message';
@@ -6,7 +6,7 @@ import {InputContainer} from '../atoms/InputContainer';
6
6
  import {cn} from '@boostdev/design-system-foundation';
7
7
  import type { WithClassName } from '../../../../types';
8
8
 
9
- interface NumberInputProps extends WithClassName {
9
+ interface NumberInputProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
10
10
  label: string;
11
11
  name: string;
12
12
  value?: number;
@@ -33,6 +33,7 @@ export function NumberInput({
33
33
  hint,
34
34
  onChange,
35
35
  className,
36
+ ...rest
36
37
  }: Readonly<NumberInputProps>) {
37
38
  const uid = name + useId();
38
39
  const hintId = uid + 'hint';
@@ -62,7 +63,7 @@ export function NumberInput({
62
63
  };
63
64
 
64
65
  return (
65
- <InputContainer className={cn(css.formGroup, className)}>
66
+ <InputContainer {...rest} className={cn(css.formGroup, className)}>
66
67
  <Label id={uid} label={label} />
67
68
  <div className={css.inputRow}>
68
69
  <button
@@ -1,16 +1,14 @@
1
- import { ReactNode, useId } from 'react';
1
+ import { FieldsetHTMLAttributes, useId } from 'react';
2
2
  import css from './RadioGroup.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { cn } from '@boostdev/design-system-foundation';
5
5
  import type { WithClassName } from '../../../../types';
6
6
 
7
- export interface RadioGroupProps extends WithClassName {
7
+ export interface RadioGroupProps extends WithClassName, FieldsetHTMLAttributes<HTMLFieldSetElement> {
8
8
  legend: string;
9
- children: ReactNode;
10
9
  error?: string;
11
10
  hint?: string;
12
11
  required?: boolean;
13
- disabled?: boolean;
14
12
  }
15
13
 
16
14
  export function RadioGroup({
@@ -19,8 +17,8 @@ export function RadioGroup({
19
17
  error,
20
18
  hint,
21
19
  required,
22
- disabled,
23
20
  className,
21
+ ...rest
24
22
  }: Readonly<RadioGroupProps>) {
25
23
  const id = useId();
26
24
  const hintId = id + 'hint';
@@ -29,8 +27,8 @@ export function RadioGroup({
29
27
 
30
28
  return (
31
29
  <fieldset
30
+ {...rest}
32
31
  className={cn(css.group, className)}
33
- disabled={disabled}
34
32
  aria-required={required || undefined}
35
33
  aria-describedby={describedBy}
36
34
  >
@@ -87,15 +87,19 @@ When `selectedIndex` is omitted, the component auto-detects the active item from
87
87
 
88
88
  | Variant | Description |
89
89
  |---------|-------------|
90
- | `outline` (default) | Transparent track, inset-border indicator slides to the active item |
91
- | `filled` | Coloured track, filled thumb slides behind the active item |
90
+ | `outline` (default) | Subtle track background; active item has a `bg` background with inset-border indicator; inactive items are transparent |
91
+ | `filled` | Subtle track background; coloured filled thumb slides behind the active item |
92
92
 
93
93
  ## CSS variables
94
94
 
95
95
  | Variable | Default | Description |
96
96
  |----------|---------|-------------|
97
- | `--control_track-bg` | `var(--bds-color_bg--subtle)` (filled) / `transparent` (outline) | Track background |
98
- | `--control_thumb-bg` | `var(--bds-color_interactive)` | Filled thumb colour |
97
+ | `--control_track-bg` | `var(--bds-color_bg--subtle)` | Track background colour (both variants) |
98
+ | `--control_item-bg` | `transparent` | Inactive item background (outline variant) |
99
+ | `--control_item-bg--active` | `var(--bds-color_bg)` | Active item background (outline variant) |
100
+ | `--control_radius` | `var(--bds-border_radius--m)` | Border radius of the outer track |
101
+ | `--control_thumb-bg` | `var(--bds-color_interactive)` | Filled thumb colour (filled variant) |
102
+ | `--control_thumb-radius` | `var(--bds-border_radius--s)` | Border radius of the thumb, indicator, and item focus ring |
99
103
  | `--control_label-color` | `var(--bds-color_on-bg--subtle)` | Inactive item text colour |
100
104
  | `--control_label-color--active` | `var(--bds-color_on-interactive)` (filled) / `var(--bds-color_interactive)` (outline) | Active item text colour |
101
105
  | `--control_label-color--hover` | `var(--bds-color_on-bg)` | Hovered inactive item text colour |