@boostdev/design-system-components 1.2.1 → 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 (51) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +15 -15
  3. package/dist/client.cjs +141 -120
  4. package/dist/client.css +468 -468
  5. package/dist/client.d.cts +71 -96
  6. package/dist/client.d.ts +71 -96
  7. package/dist/client.js +141 -120
  8. package/dist/index.cjs +141 -120
  9. package/dist/index.css +468 -468
  10. package/dist/index.d.cts +71 -96
  11. package/dist/index.d.ts +71 -96
  12. package/dist/index.js +141 -120
  13. package/package.json +1 -1
  14. package/src/components/interaction/Command/Command.tsx +4 -3
  15. package/src/components/interaction/Dialog/Dialog.tsx +4 -5
  16. package/src/components/interaction/Drawer/Drawer.spec.tsx +3 -3
  17. package/src/components/interaction/Drawer/Drawer.tsx +4 -7
  18. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +4 -2
  19. package/src/components/interaction/Popover/Popover.tsx +4 -3
  20. package/src/components/interaction/Rating/Rating.tsx +4 -2
  21. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +4 -6
  22. package/src/components/interaction/form/Combobox/Combobox.tsx +4 -2
  23. package/src/components/interaction/form/FileInput/FileInput.tsx +4 -3
  24. package/src/components/interaction/form/NumberInput/NumberInput.tsx +4 -3
  25. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +4 -6
  26. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +4 -6
  27. package/src/components/interaction/form/atoms/InputContainer.tsx +2 -2
  28. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +4 -6
  29. package/src/components/layout/Card/Card.tsx +4 -10
  30. package/src/components/layout/IconWrapper/IconWrapper.tsx +4 -5
  31. package/src/components/layout/SectionHeader/SectionHeader.tsx +5 -4
  32. package/src/components/ui/Accordion/Accordion.tsx +4 -3
  33. package/src/components/ui/Alert/Alert.tsx +4 -3
  34. package/src/components/ui/Avatar/Avatar.tsx +5 -3
  35. package/src/components/ui/Badge/Badge.tsx +4 -5
  36. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +4 -3
  37. package/src/components/ui/Calendar/Calendar.tsx +4 -4
  38. package/src/components/ui/Carousel/Carousel.tsx +4 -4
  39. package/src/components/ui/DescriptionList/DescriptionList.tsx +4 -4
  40. package/src/components/ui/Loading/Loading.tsx +4 -3
  41. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +4 -2
  42. package/src/components/ui/Pagination/Pagination.tsx +4 -2
  43. package/src/components/ui/Progress/Progress.tsx +4 -2
  44. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +4 -1
  45. package/src/components/ui/Separator/Separator.tsx +5 -3
  46. package/src/components/ui/Skeleton/Skeleton.tsx +4 -3
  47. package/src/components/ui/SkipLink/SkipLink.tsx +4 -5
  48. package/src/components/ui/Tabs/Tabs.tsx +4 -4
  49. package/src/components/ui/Tooltip/Tooltip.tsx +4 -2
  50. package/src/components/ui/Typography/Typography.tsx +4 -5
  51. package/src/stories/DesignSystem/Grid.mdx +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "1.2.1",
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)}
@@ -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;
@@ -92,10 +89,10 @@ export function Drawer({
92
89
 
93
90
  return (
94
91
  <dialog
92
+ {...rest}
95
93
  ref={dialogRef}
96
94
  id={id}
97
95
  className={cn(css.drawer, css[`--side_${side}`], className)}
98
- aria-label={ariaLabel}
99
96
  aria-modal="true"
100
97
  onClick={handleClick}
101
98
  onCancel={handleCancel}
@@ -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
  >
@@ -1,10 +1,9 @@
1
- import { Children, ReactElement, ReactNode, cloneElement, isValidElement } from 'react';
1
+ import { Children, HTMLAttributes, ReactElement, cloneElement, isValidElement } from 'react';
2
2
  import css from './SegmentedControl.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../../types';
5
5
 
6
- export interface SegmentedControlProps extends WithClassName {
7
- children: ReactNode;
6
+ export interface SegmentedControlProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
8
7
  /**
9
8
  * Zero-based index of the active item.
10
9
  * When omitted, auto-detected from the first child that has
@@ -15,7 +14,6 @@ export interface SegmentedControlProps extends WithClassName {
15
14
  size?: 'small' | 'medium' | 'large';
16
15
  /** "outline" (default) — sliding inset border; "filled" — sliding filled thumb */
17
16
  variant?: 'outline' | 'filled';
18
- 'aria-label'?: string;
19
17
  }
20
18
 
21
19
  export function SegmentedControl({
@@ -25,7 +23,7 @@ export function SegmentedControl({
25
23
  size = 'medium',
26
24
  variant = 'outline',
27
25
  className,
28
- 'aria-label': ariaLabel,
26
+ ...rest
29
27
  }: Readonly<SegmentedControlProps>) {
30
28
  const validChildren = Children.toArray(children).filter(isValidElement) as ReactElement<Record<string, unknown>>[];
31
29
 
@@ -41,8 +39,8 @@ export function SegmentedControl({
41
39
 
42
40
  return (
43
41
  <div
42
+ {...rest}
44
43
  role="group"
45
- aria-label={ariaLabel}
46
44
  className={cn(css.control, css[`--size_${size}`], css[`--variant_${variant}`], className)}
47
45
  style={{
48
46
  '--control_count': validChildren.length,
@@ -5,6 +5,6 @@ import type { WithClassName } from '../../../../types';
5
5
 
6
6
  type ContainerProps = WithClassName & HTMLAttributes<HTMLDivElement>;
7
7
 
8
- export const InputContainer = ({ children, className }: ContainerProps) => {
9
- return <div className={cn(css.container, className)}>{children}</div>;
8
+ export const InputContainer = ({ children, className, ...rest }: ContainerProps) => {
9
+ return <div {...rest} className={cn(css.container, className)}>{children}</div>;
10
10
  };
@@ -1,19 +1,17 @@
1
- import { ReactNode } from 'react';
1
+ import { HTMLAttributes } from 'react';
2
2
  import css from './ButtonGroup.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
- export interface ButtonGroupProps extends WithClassName {
7
- children: ReactNode;
6
+ export interface ButtonGroupProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
8
7
  variant?: 'flow' | 'card' | 'modal' | 'content';
9
- 'aria-label'?: string;
10
8
  }
11
9
 
12
- export function ButtonGroup({ children, className, variant, 'aria-label': ariaLabel }: ButtonGroupProps) {
10
+ export function ButtonGroup({ children, className, variant, ...rest }: ButtonGroupProps) {
13
11
  return (
14
12
  <div
13
+ {...rest}
15
14
  role="group"
16
- aria-label={ariaLabel}
17
15
  className={cn(css.buttonGroup, className, variant && css[`--variant_${variant}`])}
18
16
  >
19
17
  <div className={css.container}>{children}</div>
@@ -1,16 +1,12 @@
1
- import { CSSProperties, ReactNode } from 'react';
1
+ import { HTMLAttributes } from 'react';
2
2
  import css from './Card.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
- interface CardProps extends WithClassName {
7
- children: ReactNode;
6
+ interface CardProps extends WithClassName, HTMLAttributes<HTMLElement> {
8
7
  variant?: 'default' | 'elevated' | 'outlined';
9
8
  padding?: 'none' | 'small' | 'medium' | 'large';
10
9
  textAlign?: 'start' | 'center' | 'end';
11
- style?: CSSProperties;
12
- onClick?: () => void;
13
- 'aria-label'?: string;
14
10
  }
15
11
 
16
12
  export function Card({
@@ -19,9 +15,8 @@ export function Card({
19
15
  variant = 'default',
20
16
  padding = 'medium',
21
17
  textAlign = 'start',
22
- style,
23
18
  onClick,
24
- 'aria-label': ariaLabel,
19
+ ...rest
25
20
  }: CardProps) {
26
21
  const classNames = cn(
27
22
  css.card,
@@ -36,10 +31,9 @@ export function Card({
36
31
 
37
32
  return (
38
33
  <Component
34
+ {...rest}
39
35
  className={classNames}
40
36
  onClick={onClick}
41
- style={style}
42
- aria-label={ariaLabel}
43
37
  {...(onClick && { type: 'button' as const })}
44
38
  >
45
39
  {children}
@@ -1,15 +1,14 @@
1
- import { ReactNode } from 'react';
1
+ import { HTMLAttributes, ReactNode } from 'react';
2
2
  import css from './IconWrapper.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
- export interface IconWrapperProps extends WithClassName {
6
+ export interface IconWrapperProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
7
7
  children: ReactNode;
8
- 'aria-hidden'?: boolean | 'true' | 'false';
9
8
  }
10
9
 
11
- export function IconWrapper({ children, className, 'aria-hidden': ariaHidden }: IconWrapperProps) {
10
+ export function IconWrapper({ children, className, ...rest }: IconWrapperProps) {
12
11
  return (
13
- <div className={cn(className, css.wrapper)} aria-hidden={ariaHidden}>{children}</div>
12
+ <div {...rest} className={cn(className, css.wrapper)}>{children}</div>
14
13
  );
15
14
  }
@@ -1,11 +1,11 @@
1
- import type { JSX } from 'react';
1
+ import type { HTMLAttributes, JSX } from 'react';
2
2
  import css from './SectionHeader.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
6
  type IntrinsicElement = keyof JSX.IntrinsicElements;
7
7
 
8
- type SectionHeaderProps = WithClassName & {
8
+ type SectionHeaderProps = WithClassName & Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
9
9
  title: string;
10
10
  subtitle?: string;
11
11
  alignment?: 'start' | 'center' | 'end';
@@ -19,11 +19,12 @@ export function SectionHeader({
19
19
  className,
20
20
  alignment = 'start',
21
21
  size = 'medium',
22
- titleAs = 'h2'
22
+ titleAs = 'h2',
23
+ ...rest
23
24
  }: Readonly<SectionHeaderProps>) {
24
25
  const Title = titleAs;
25
26
  return (
26
- <div className={cn(css.sectionHeader, css[`--${alignment}`], css[`--${size}`], className)}>
27
+ <div {...rest} className={cn(css.sectionHeader, css[`--${alignment}`], css[`--${size}`], className)}>
27
28
  <Title className={css.title}>{title}</Title>
28
29
  {subtitle && <p className={css.subtitle}>{subtitle}</p>}
29
30
  </div>
@@ -1,4 +1,4 @@
1
- import { ReactNode, useId, useState } from 'react';
1
+ import { HTMLAttributes, ReactNode, useId, useState } from 'react';
2
2
  import css from './Accordion.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
@@ -10,7 +10,7 @@ export interface AccordionItem {
10
10
  disabled?: boolean;
11
11
  }
12
12
 
13
- interface AccordionProps extends WithClassName {
13
+ interface AccordionProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
14
14
  items: AccordionItem[];
15
15
  allowMultiple?: boolean;
16
16
  defaultOpen?: string[];
@@ -21,6 +21,7 @@ export function Accordion({
21
21
  allowMultiple = false,
22
22
  defaultOpen = [],
23
23
  className,
24
+ ...rest
24
25
  }: Readonly<AccordionProps>) {
25
26
  const baseId = useId();
26
27
  const [openIds, setOpenIds] = useState<string[]>(defaultOpen);
@@ -34,7 +35,7 @@ export function Accordion({
34
35
  };
35
36
 
36
37
  return (
37
- <div className={cn(css.accordion, className)}>
38
+ <div {...rest} className={cn(css.accordion, className)}>
38
39
  {items.map(item => {
39
40
  const isOpen = openIds.includes(item.id);
40
41
  const triggerId = `${baseId}-trigger-${item.id}`;
@@ -1,13 +1,12 @@
1
- import { ReactNode } from 'react';
1
+ import { HTMLAttributes, ReactNode } from 'react';
2
2
  import css from './Alert.module.css';
3
3
  import { cn } from '@boostdev/design-system-foundation';
4
4
  import type { WithClassName } from '../../../types';
5
5
 
6
- interface AlertProps extends WithClassName {
6
+ interface AlertProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
7
7
  variant?: 'info' | 'success' | 'warning' | 'error';
8
8
  icon?: ReactNode;
9
9
  title?: string;
10
- children: ReactNode;
11
10
  onDismiss?: () => void;
12
11
  }
13
12
 
@@ -18,11 +17,13 @@ export function Alert({
18
17
  children,
19
18
  onDismiss,
20
19
  className,
20
+ ...rest
21
21
  }: Readonly<AlertProps>) {
22
22
  const isUrgent = variant === 'error' || variant === 'warning';
23
23
 
24
24
  return (
25
25
  <div
26
+ {...rest}
26
27
  role={isUrgent ? 'alert' : 'status'}
27
28
  aria-live={isUrgent ? 'assertive' : 'polite'}
28
29
  aria-atomic="true"
@@ -1,8 +1,9 @@
1
+ import { HTMLAttributes } from 'react';
1
2
  import css from './Avatar.module.css';
2
3
  import { cn } from '@boostdev/design-system-foundation';
3
4
  import type { WithClassName } from '../../../types';
4
5
 
5
- interface AvatarProps extends WithClassName {
6
+ interface AvatarProps extends WithClassName, HTMLAttributes<HTMLSpanElement> {
6
7
  src?: string;
7
8
  alt?: string;
8
9
  name?: string;
@@ -18,12 +19,12 @@ function getInitials(name: string): string {
18
19
  .join('');
19
20
  }
20
21
 
21
- export function Avatar({ src, alt, name, size = 'medium', className }: Readonly<AvatarProps>) {
22
+ export function Avatar({ src, alt, name, size = 'medium', className, ...rest }: Readonly<AvatarProps>) {
22
23
  const sizeClass = css[`--size_${size}`];
23
24
 
24
25
  if (src) {
25
26
  return (
26
- <span className={cn(css.avatar, sizeClass, className)}>
27
+ <span {...rest} className={cn(css.avatar, sizeClass, className)}>
27
28
  <img src={src} alt={alt ?? name ?? 'User avatar'} className={css.image} />
28
29
  </span>
29
30
  );
@@ -33,6 +34,7 @@ export function Avatar({ src, alt, name, size = 'medium', className }: Readonly<
33
34
 
34
35
  return (
35
36
  <span
37
+ {...rest}
36
38
  role="img"
37
39
  aria-label={name ?? 'User avatar'}
38
40
  className={cn(css.avatar, css['--fallback'], sizeClass, className)}