@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
@@ -1,4 +1,4 @@
1
- import { InputHTMLAttributes, useId } from 'react';
1
+ import { InputHTMLAttributes, ReactNode, useId } from 'react';
2
2
  import css from './Checkbox.module.css';
3
3
  import { Message } from '../atoms/Message';
4
4
  import { Label } from '../atoms/Label';
@@ -6,7 +6,7 @@ import { cn } from '@boostdev/design-system-foundation';
6
6
  import { InputContainer } from '../atoms/InputContainer';
7
7
 
8
8
  interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
9
- label: string;
9
+ label: ReactNode;
10
10
  name: string;
11
11
  error?: string;
12
12
  hint?: string;
@@ -0,0 +1,29 @@
1
+ @layer component {
2
+ .group {
3
+ border: none;
4
+ padding: 0;
5
+ margin: 0;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--bds-space_xs);
9
+ }
10
+
11
+ .legend {
12
+ font-family: var(--bds-font_family--body);
13
+ font-size: var(--bds-font_size--body--s);
14
+ font-weight: var(--bds-font_weight--medium);
15
+ color: var(--bds-on-bg);
16
+ padding: 0;
17
+ margin-block-end: var(--bds-space_xs);
18
+ }
19
+
20
+ .required {
21
+ color: var(--bds-error);
22
+ }
23
+
24
+ .items {
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: var(--bds-space_xs);
28
+ }
29
+ }
@@ -0,0 +1,87 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { Checkbox } from '../Checkbox';
3
+ import { CheckboxGroup } from './CheckboxGroup';
4
+
5
+ describe('CheckboxGroup', () => {
6
+ it('renders a fieldset with a legend', () => {
7
+ render(
8
+ <CheckboxGroup legend="Notifications">
9
+ <Checkbox label="Email" name="email" />
10
+ </CheckboxGroup>
11
+ );
12
+ expect(screen.getByRole('group', { name: 'Notifications' })).toBeInTheDocument();
13
+ expect(screen.getByText('Notifications')).toBeInTheDocument();
14
+ });
15
+
16
+ it('renders child checkboxes', () => {
17
+ render(
18
+ <CheckboxGroup legend="Options">
19
+ <Checkbox label="Email" name="email" />
20
+ <Checkbox label="SMS" name="sms" />
21
+ </CheckboxGroup>
22
+ );
23
+ expect(screen.getByRole('checkbox', { name: 'Email' })).toBeInTheDocument();
24
+ expect(screen.getByRole('checkbox', { name: 'SMS' })).toBeInTheDocument();
25
+ });
26
+
27
+ it('shows an error message', () => {
28
+ render(
29
+ <CheckboxGroup legend="Required" error="Select at least one option">
30
+ <Checkbox label="Option" name="opt" />
31
+ </CheckboxGroup>
32
+ );
33
+ expect(screen.getByText('Select at least one option')).toBeInTheDocument();
34
+ });
35
+
36
+ it('shows a hint message', () => {
37
+ render(
38
+ <CheckboxGroup legend="Preferences" hint="You can select multiple">
39
+ <Checkbox label="Option" name="opt" />
40
+ </CheckboxGroup>
41
+ );
42
+ expect(screen.getByText('You can select multiple')).toBeInTheDocument();
43
+ });
44
+
45
+ it('links error message via aria-describedby', () => {
46
+ render(
47
+ <CheckboxGroup legend="Required" error="Required field">
48
+ <Checkbox label="Option" name="opt" />
49
+ </CheckboxGroup>
50
+ );
51
+ const fieldset = screen.getByRole('group');
52
+ const errorMsg = screen.getByText('Required field');
53
+ expect(fieldset).toHaveAttribute('aria-describedby');
54
+ expect(fieldset.getAttribute('aria-describedby')).toContain(errorMsg.id);
55
+ });
56
+
57
+ it('links hint message via aria-describedby', () => {
58
+ render(
59
+ <CheckboxGroup legend="Options" hint="Pick any">
60
+ <Checkbox label="Option" name="opt" />
61
+ </CheckboxGroup>
62
+ );
63
+ const fieldset = screen.getByRole('group');
64
+ const hintMsg = screen.getByText('Pick any');
65
+ expect(fieldset).toHaveAttribute('aria-describedby');
66
+ expect(fieldset.getAttribute('aria-describedby')).toContain(hintMsg.id);
67
+ });
68
+
69
+ it('marks fieldset as required when required prop is set', () => {
70
+ render(
71
+ <CheckboxGroup legend="Required" required>
72
+ <Checkbox label="Option" name="opt" />
73
+ </CheckboxGroup>
74
+ );
75
+ expect(screen.getByRole('group')).toHaveAttribute('aria-required', 'true');
76
+ expect(screen.getByText('*')).toBeInTheDocument();
77
+ });
78
+
79
+ it('disables the fieldset when disabled prop is set', () => {
80
+ render(
81
+ <CheckboxGroup legend="Disabled" disabled>
82
+ <Checkbox label="Option" name="opt" />
83
+ </CheckboxGroup>
84
+ );
85
+ expect(screen.getByRole('group')).toBeDisabled();
86
+ });
87
+ });
@@ -0,0 +1,62 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Checkbox } from '../Checkbox';
3
+ import { CheckboxGroup } from './CheckboxGroup';
4
+
5
+ const meta = {
6
+ title: 'Form/CheckboxGroup',
7
+ component: CheckboxGroup,
8
+ } satisfies Meta<typeof CheckboxGroup>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Default: Story = {
14
+ args: { legend: 'Notifications' },
15
+ render: args => (
16
+ <CheckboxGroup {...args}>
17
+ <Checkbox label="Email" name="notify_email" />
18
+ <Checkbox label="SMS" name="notify_sms" />
19
+ <Checkbox label="Push" name="notify_push" />
20
+ </CheckboxGroup>
21
+ ),
22
+ };
23
+
24
+ export const WithError: Story = {
25
+ args: { legend: 'Required options', error: 'Select at least one option' },
26
+ render: args => (
27
+ <CheckboxGroup {...args}>
28
+ <Checkbox label="Email" name="notify_email" />
29
+ <Checkbox label="SMS" name="notify_sms" />
30
+ </CheckboxGroup>
31
+ ),
32
+ };
33
+
34
+ export const WithHint: Story = {
35
+ args: { legend: 'Preferences', hint: 'You can select multiple options' },
36
+ render: args => (
37
+ <CheckboxGroup {...args}>
38
+ <Checkbox label="Dark mode" name="pref_dark" />
39
+ <Checkbox label="Compact view" name="pref_compact" />
40
+ </CheckboxGroup>
41
+ ),
42
+ };
43
+
44
+ export const Required: Story = {
45
+ args: { legend: 'Agreement', required: true },
46
+ render: args => (
47
+ <CheckboxGroup {...args}>
48
+ <Checkbox label="I agree to the terms" name="terms" />
49
+ <Checkbox label="I agree to the privacy policy" name="privacy" />
50
+ </CheckboxGroup>
51
+ ),
52
+ };
53
+
54
+ export const Disabled: Story = {
55
+ args: { legend: 'Unavailable options', disabled: true },
56
+ render: args => (
57
+ <CheckboxGroup {...args}>
58
+ <Checkbox label="Email" name="notify_email" defaultChecked />
59
+ <Checkbox label="SMS" name="notify_sms" />
60
+ </CheckboxGroup>
61
+ ),
62
+ };
@@ -0,0 +1,46 @@
1
+ import { ReactNode, useId } from 'react';
2
+ import css from './CheckboxGroup.module.css';
3
+ import { Message } from '../atoms/Message';
4
+ import { cn } from '@boostdev/design-system-foundation';
5
+
6
+ export interface CheckboxGroupProps {
7
+ legend: string;
8
+ children: ReactNode;
9
+ error?: string;
10
+ hint?: string;
11
+ required?: boolean;
12
+ disabled?: boolean;
13
+ className?: string;
14
+ }
15
+
16
+ export function CheckboxGroup({
17
+ legend,
18
+ children,
19
+ error,
20
+ hint,
21
+ required,
22
+ disabled,
23
+ className,
24
+ }: Readonly<CheckboxGroupProps>) {
25
+ const id = useId();
26
+ const hintId = id + 'hint';
27
+ const errorId = id + 'error';
28
+ const describedBy = [error && errorId, hint && hintId].filter(Boolean).join(' ') || undefined;
29
+
30
+ return (
31
+ <fieldset
32
+ className={cn(css.group, className)}
33
+ disabled={disabled}
34
+ aria-required={required || undefined}
35
+ aria-describedby={describedBy}
36
+ >
37
+ <legend className={css.legend}>
38
+ {legend}
39
+ {required && <span className={css.required} aria-hidden="true"> *</span>}
40
+ </legend>
41
+ <div className={css.items}>{children}</div>
42
+ <Message inputId={id} type="error" message={error} />
43
+ <Message inputId={id} type="hint" message={hint} />
44
+ </fieldset>
45
+ );
46
+ }
@@ -0,0 +1,2 @@
1
+ export { CheckboxGroup } from './CheckboxGroup';
2
+ export type { CheckboxGroupProps } from './CheckboxGroup';
@@ -13,22 +13,31 @@
13
13
  .input {
14
14
  appearance: none;
15
15
  width: 100%;
16
- font-family: var(--font_family--body);
17
- font-size: var(--font_size--body);
18
- padding: var(--space_s);
19
- padding-inline-end: var(--space_xxl);
20
- border: 1px solid var(--combobox_border-color, currentcolor);
21
- border-radius: var(--border_radius--xs);
22
- background-color: var(--combobox_color_bg, var(--color_bg));
23
- color: var(--combobox_color, var(--color_on-bg));
24
- transition: var(--animation_transition);
16
+ font-family: var(--bds-font_family--body);
17
+ font-size: var(--bds-font_size--body);
18
+ padding: var(--bds-space_s);
19
+ padding-inline-end: var(--bds-space_xxl);
20
+ border: var(--combobox_border, none);
21
+ --bdc_color: currentcolor;
22
+ --bdc_radius: var(--bds-border_radius--xs);
23
+
24
+ outline: var(--bdc_width) solid var(--bdc_color);
25
+ outline-offset: var(--bdc_offset);
26
+ border-radius: var(--combobox_radius, var(--bdc_radius));
27
+ box-shadow: var(--combobox_shadow, var(--bdc_shadow));
28
+ background-color: var(--combobox_color_bg, var(--bds-bg));
29
+ color: var(--combobox_color, var(--bds-on-bg));
30
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing),
31
+ background-color var(--bds-animation_transition-duration) var(--bds-animation_easing);
25
32
  }
26
33
 
27
34
  .input:focus {
28
- --combobox_border-color: var(--color_interactive);
35
+ --bdc_color: var(--bdc_color--focus);
36
+ }
29
37
 
30
- outline: none;
31
- box-shadow: var(--combobox_focus-ring, 0 0 0 2px rgb(from var(--color_interactive) r g b / 20%));
38
+ .input:focus-visible {
39
+ outline: var(--bds-outline_default);
40
+ outline-offset: var(--bds-outline_offset);
32
41
  }
33
42
 
34
43
  .input:disabled {
@@ -37,20 +46,16 @@
37
46
  }
38
47
 
39
48
  .inputError {
40
- --combobox_border-color: var(--color_error);
41
- }
42
-
43
- .inputError:focus {
44
- --combobox_focus-ring: 0 0 0 2px rgb(from var(--color_error) r g b / 20%);
49
+ --bdc_color: var(--bdc_color--error);
45
50
  }
46
51
 
47
52
  .chevron {
48
53
  position: absolute;
49
- inset-inline-end: var(--space_s);
54
+ inset-inline-end: var(--bds-space_s);
50
55
  display: flex;
51
56
  align-items: center;
52
57
  pointer-events: none;
53
- color: var(--combobox_color, var(--color_on-bg));
58
+ color: var(--combobox_color, var(--bds-on-bg));
54
59
  }
55
60
 
56
61
  .chevron svg {
@@ -60,39 +65,44 @@
60
65
 
61
66
  .listbox {
62
67
  position: absolute;
63
- top: calc(100% + var(--space_xxs));
68
+ top: calc(100% + var(--bds-space_xxs));
64
69
  left: 0;
65
70
  right: 0;
66
- z-index: var(--z-index_dropdown);
71
+ z-index: var(--bds-z-index_dropdown);
67
72
  max-height: 15rem;
68
73
  overflow-y: auto;
69
74
  list-style: none;
70
75
  margin: 0;
71
- padding: var(--space_xxs) 0;
72
- border-radius: var(--border_radius--xs);
73
- border: 1px solid currentcolor;
74
- background-color: var(--combobox_color, var(--color_bg));
75
- color: var(--combobox_on-color, var(--color_on-bg));
76
- box-shadow: var(--shadow_m);
76
+ padding: var(--bds-space_xxs) 0;
77
+ border: var(--combobox_listbox-border, none);
78
+ --bdc_color: currentcolor;
79
+ --bdc_radius: var(--bds-border_radius--xs);
80
+
81
+ outline: var(--bdc_width) solid var(--bdc_color);
82
+ outline-offset: var(--bdc_offset);
83
+ border-radius: var(--combobox_listbox-radius, var(--bdc_radius));
84
+ background-color: var(--combobox_color, var(--bds-bg));
85
+ color: var(--combobox_on-color, var(--bds-on-bg));
86
+ box-shadow: var(--combobox_listbox-shadow, var(--bds-shadow_m));
77
87
  }
78
88
 
79
89
  .option {
80
90
  display: flex;
81
91
  align-items: center;
82
- padding: var(--space_xs) var(--space_m);
83
- font-size: var(--font_size--body);
84
- color: var(--combobox_color, var(--color_on-bg));
92
+ padding: var(--bds-space_xs) var(--bds-space_m);
93
+ font-size: var(--bds-font_size--body);
94
+ color: var(--combobox_color, var(--bds-on-bg));
85
95
  cursor: pointer;
86
- transition: var(--animation_transition);
96
+ transition: var(--bds-animation_transition);
87
97
  }
88
98
 
89
99
  .option.--highlighted {
90
- background-color: var(--color_bg--subtle);
91
- color: var(--color_interactive);
100
+ background-color: var(--bds-bg--subtle);
101
+ color: var(--bds-interactive);
92
102
  }
93
103
 
94
104
  .option.--selected {
95
- font-weight: var(--font_weight--semibold);
105
+ font-weight: var(--bds-font_weight--semibold);
96
106
  }
97
107
 
98
108
  .option.--disabled {
@@ -156,7 +156,6 @@ export function Combobox({
156
156
  <ul
157
157
  id={listboxId}
158
158
  role="listbox"
159
- aria-label={String(label)}
160
159
  className={css.listbox}
161
160
  >
162
161
  {filtered.map((option, index) => (
@@ -4,24 +4,32 @@
4
4
  flex-direction: column;
5
5
  }
6
6
 
7
+ .fieldLabel {
8
+ display: flex;
9
+ align-items: center;
10
+ font-family: var(--bds-font_family--body);
11
+ color: var(--bds-on-bg);
12
+ margin-block-end: var(--bds-space_xs);
13
+ }
14
+
7
15
  .dropZone {
8
16
  display: flex;
9
17
  flex-direction: column;
10
18
  align-items: center;
11
19
  justify-content: center;
12
- gap: var(--space_xs);
13
- padding: var(--space_l) var(--space_m);
20
+ gap: var(--bds-space_xs);
21
+ padding: var(--bds-space_l) var(--bds-space_m);
14
22
  border: 2px dashed currentcolor;
15
- border-radius: var(--border_radius--s);
16
- color: var(--fileInput_color, var(--color_on-bg));
23
+ border-radius: var(--bds-border_radius--s);
24
+ color: var(--fileInput_color, var(--bds-on-bg));
17
25
  cursor: pointer;
18
26
  text-align: center;
19
- transition: var(--animation_transition);
27
+ transition: var(--bds-animation_transition);
20
28
  }
21
29
 
22
30
  .dropZone:focus-within {
23
- outline: var(--outline_default);
24
- outline-offset: var(--outline_offset);
31
+ outline: var(--bds-outline_default);
32
+ outline-offset: var(--bds-outline_offset);
25
33
  border-color: var(--fileInput_border-color, currentcolor);
26
34
  }
27
35
 
@@ -31,7 +39,7 @@
31
39
  }
32
40
 
33
41
  .hasError {
34
- border-color: var(--color_error);
42
+ border-color: var(--bds-error);
35
43
  }
36
44
 
37
45
  .isDisabled {
@@ -46,18 +54,18 @@
46
54
  }
47
55
 
48
56
  .prompt {
49
- font-size: var(--font_size--body);
50
- color: var(--color_on-bg--muted);
57
+ font-size: var(--bds-font_size--body);
58
+ color: var(--bds-on-bg);
51
59
  }
52
60
 
53
61
  .prompt strong {
54
- color: var(--color_interactive);
55
- font-weight: var(--font_weight--semibold);
62
+ color: var(--bds-interactive);
63
+ font-weight: var(--bds-font_weight--semibold);
56
64
  }
57
65
 
58
66
  .acceptHint {
59
- font-size: var(--font_size--body--s);
60
- color: var(--color_on-bg--muted);
67
+ font-size: var(--bds-font_size--body--s);
68
+ color: var(--bds-on-bg);
61
69
  }
62
70
 
63
71
  .hiddenInput {
@@ -72,8 +80,8 @@
72
80
 
73
81
  @media (hover: hover) and (pointer: fine) {
74
82
  .dropZone:not(.isDisabled):hover {
75
- border-color: var(--color_interactive);
76
- background-color: rgb(from var(--color_interactive) r g b / 4%);
83
+ border-color: var(--bds-interactive);
84
+ background-color: rgb(from var(--bds-interactive) r g b / 4%);
77
85
  }
78
86
  }
79
87
  }
@@ -1,6 +1,5 @@
1
1
  import { useId, useRef, useState, DragEvent, ChangeEvent } from 'react';
2
2
  import css from './FileInput.module.css';
3
- import { Label } from '../atoms/Label';
4
3
  import { Message } from '../atoms/Message';
5
4
  import { InputContainer } from '../atoms/InputContainer';
6
5
  import { cn } from '@boostdev/design-system-foundation';
@@ -11,6 +10,7 @@ interface FileInputProps {
11
10
  accept?: string;
12
11
  multiple?: boolean;
13
12
  disabled?: boolean;
13
+ required?: boolean;
14
14
  error?: string;
15
15
  hint?: string;
16
16
  onChange?: (files: FileList | null) => void;
@@ -23,6 +23,7 @@ export function FileInput({
23
23
  accept,
24
24
  multiple = false,
25
25
  disabled = false,
26
+ required,
26
27
  error,
27
28
  hint,
28
29
  onChange,
@@ -73,7 +74,6 @@ export function FileInput({
73
74
 
74
75
  return (
75
76
  <InputContainer className={cn(css.formGroup, className)}>
76
- <Label id={uid} label={label} />
77
77
  <label
78
78
  htmlFor={uid}
79
79
  className={cn(css.dropZone, isDragging && css.isDragging, error && css.hasError, disabled && css.isDisabled)}
@@ -81,6 +81,7 @@ export function FileInput({
81
81
  onDragOver={handleDragOver}
82
82
  onDragLeave={handleDragLeave}
83
83
  >
84
+ <span className={css.fieldLabel}>{label}</span>
84
85
  <svg aria-hidden="true" className={css.icon} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
85
86
  <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
86
87
  </svg>
@@ -101,6 +102,8 @@ export function FileInput({
101
102
  disabled={disabled}
102
103
  aria-invalid={!!error}
103
104
  aria-describedby={describedBy}
105
+ aria-required={required || undefined}
106
+ required={required}
104
107
  className={css.hiddenInput}
105
108
  onChange={handleChange}
106
109
  />
@@ -7,28 +7,33 @@
7
7
  }
8
8
 
9
9
  .input {
10
- font-family: var(--font_family--body);
11
- font-size: var(--font_size--body);
12
- padding: var(--space_s);
13
- border: 1px solid var(--input_border-color, currentcolor);
14
- border-radius: var(--input_radius, var(--border_radius--xs));
15
- transition: var(--animation_transition);
16
- color: var(--input_color, var(--color_on-bg));
17
- background-color: var(--input_color_bg, var(--color_bg));
10
+ font-family: var(--bds-font_family--body);
11
+ font-size: var(--bds-font_size--body);
12
+ padding: var(--bds-space_s);
13
+ border: var(--input_border, none);
14
+ --bdc_color: currentcolor;
15
+ --bdc_radius: var(--bds-border_radius--xs);
16
+
17
+ outline: var(--bdc_width) solid var(--bdc_color);
18
+ outline-offset: var(--bdc_offset);
19
+ border-radius: var(--input_radius, var(--bdc_radius));
20
+ box-shadow: var(--input_shadow, var(--bdc_shadow));
21
+ color: var(--input_color, var(--bds-on-bg));
22
+ background-color: var(--input_color_bg, var(--bds-bg));
23
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing),
24
+ background-color var(--bds-animation_transition-duration) var(--bds-animation_easing);
18
25
  }
19
26
 
20
27
  .input:focus {
21
- --input_border-color: var(--color_interactive);
22
-
23
- outline: none;
24
- box-shadow: var(--input_focus-ring, 0 0 0 2px rgb(from var(--color_interactive) r g b / 20%));
28
+ --bdc_color: var(--bdc_color--focus);
25
29
  }
26
30
 
27
- .inputError {
28
- --input_border-color: var(--color_error);
31
+ .input:focus-visible {
32
+ outline: var(--bds-outline_default);
33
+ outline-offset: var(--bds-outline_offset);
29
34
  }
30
35
 
31
- .inputError:focus {
32
- --input_focus-ring: 0 0 0 2px rgb(from var(--color_error) r g b / 20%);
36
+ .inputError {
37
+ --bdc_color: var(--bdc_color--error);
33
38
  }
34
39
  }
@@ -22,6 +22,7 @@ export function FormInput({
22
22
  error,
23
23
  hint,
24
24
  className,
25
+ required,
25
26
  ...props
26
27
  }: FormInputProps) {
27
28
  const id = name + useId();
@@ -36,8 +37,10 @@ export function FormInput({
36
37
  aria-invalid={!!error}
37
38
  aria-describedby={describedBy}
38
39
  aria-label={ariaLabel}
40
+ aria-required={required || undefined}
39
41
  id={id}
40
42
  name={name}
43
+ required={required}
41
44
  className={cn(css.input, error && css.inputError)}
42
45
  {...props}
43
46
  />
@@ -7,27 +7,37 @@
7
7
  .inputRow {
8
8
  display: flex;
9
9
  align-items: stretch;
10
- border: 1px solid currentcolor;
11
- border-radius: var(--border_radius--xs);
10
+ border: var(--numberInput_border, none);
11
+ --bdc_color: currentcolor;
12
+ --bdc_radius: var(--bds-border_radius--xs);
13
+
14
+ outline: var(--bdc_width) solid var(--bdc_color);
15
+ outline-offset: var(--bdc_offset);
16
+ border-radius: var(--numberInput_radius, var(--bdc_radius));
17
+ box-shadow: var(--numberInput_shadow, var(--bdc_shadow));
12
18
  overflow: hidden;
13
- transition: var(--animation_transition);
19
+ transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing);
14
20
  }
15
21
 
16
22
  .inputRow:focus-within {
17
- border-color: var(--numberInput_border-color, var(--color_interactive));
18
- box-shadow: var(--numberInput_focus-ring, 0 0 0 2px rgb(from var(--color_interactive) r g b / 20%));
23
+ --bdc_color: var(--bdc_color--focus);
24
+ }
25
+
26
+ .inputRow:focus-within:has(:focus-visible) {
27
+ outline: var(--bds-outline_default);
28
+ outline-offset: var(--bds-outline_offset);
19
29
  }
20
30
 
21
31
  .input {
22
32
  flex: 1;
23
33
  border: none;
24
34
  outline: none;
25
- font-family: var(--font_family--body);
26
- font-size: var(--font_size--body);
35
+ font-family: var(--bds-font_family--body);
36
+ font-size: var(--bds-font_size--body);
27
37
  text-align: center;
28
- padding: var(--space_s);
38
+ padding: var(--bds-space_s);
29
39
  background: transparent;
30
- color: var(--numberInput_color, var(--color_on-bg));
40
+ color: var(--numberInput_color, var(--bds-on-bg));
31
41
  min-width: 0;
32
42
  appearance: textfield;
33
43
  }
@@ -38,11 +48,7 @@
38
48
  }
39
49
 
40
50
  .inputError {
41
- --numberInput_border-color: var(--color_error);
42
- }
43
-
44
- .inputError:focus-within {
45
- --numberInput_focus-ring: 0 0 0 2px rgb(from var(--color_error) r g b / 20%);
51
+ --bdc_color: var(--bdc_color--error);
46
52
  }
47
53
 
48
54
  .stepper {
@@ -50,11 +56,11 @@
50
56
  display: flex;
51
57
  align-items: center;
52
58
  justify-content: center;
53
- padding: var(--space_xs);
54
- background-color: var(--color_bg--subtle);
55
- color: var(--numberInput_color, var(--color_on-bg));
59
+ padding: var(--bds-space_xs);
60
+ background-color: var(--bds-bg--subtle);
61
+ color: var(--numberInput_color, var(--bds-on-bg));
56
62
  cursor: pointer;
57
- transition: var(--animation_transition);
63
+ transition: var(--bds-animation_transition);
58
64
  flex-shrink: 0;
59
65
  }
60
66
 
@@ -70,7 +76,7 @@
70
76
 
71
77
  @media (hover: hover) and (pointer: fine) {
72
78
  .stepper:not(:disabled):hover {
73
- background-color: var(--color_bg--subtle);
79
+ background-color: var(--bds-bg--subtle);
74
80
  opacity: 0.7;
75
81
  }
76
82
  }