@boostdev/design-system-components 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -10
- package/dist/client.cjs +77 -99
- package/dist/client.css +611 -580
- package/dist/client.d.cts +13 -16
- package/dist/client.d.ts +13 -16
- package/dist/client.js +87 -109
- package/dist/index.cjs +77 -99
- package/dist/index.css +611 -580
- package/dist/index.d.cts +13 -16
- package/dist/index.d.ts +13 -16
- package/dist/index.js +87 -109
- package/dist/native/index.cjs +9 -8
- package/dist/native/index.d.cts +1 -1
- package/dist/native/index.d.ts +1 -1
- package/dist/native/index.js +9 -8
- package/dist/web-components/{chunk-O4UFI2RX.js → chunk-3REOIRDW.js} +13 -11
- package/dist/web-components/{chunk-K5CE6HSB.js → chunk-6MH5UWUD.js} +74 -17
- package/dist/web-components/{chunk-VM3YHGQ2.js → chunk-AJSXNDAP.js} +1 -2
- package/dist/web-components/{chunk-LSC4N56Q.js → chunk-BX3IPVOB.js} +1 -2
- package/dist/web-components/{chunk-CJ5ZOO42.js → chunk-DI46Q2EA.js} +50 -1
- package/dist/web-components/{chunk-CAPI25CU.js → chunk-DZRSJGPB.js} +3 -3
- package/dist/web-components/{chunk-VSS5ITNE.js → chunk-EBJM3VD4.js} +1 -3
- package/dist/web-components/{chunk-O7I63SQX.js → chunk-HHHRF2PS.js} +6 -12
- package/dist/web-components/{chunk-OTX4COSV.js → chunk-JUKB3BUP.js} +3 -4
- package/dist/web-components/{chunk-IQGLIQSO.js → chunk-N6IMUOY4.js} +2 -5
- package/dist/web-components/{chunk-QDRH46TF.js → chunk-QLZ2362S.js} +1 -3
- package/dist/web-components/{chunk-IK3PTFZ4.js → chunk-T6AETZRP.js} +1 -3
- package/dist/web-components/{chunk-4BX6AWXX.js → chunk-X3FKVHLK.js} +2 -4
- package/dist/web-components/{chunk-A67LCT7C.js → chunk-XNA6WTXG.js} +3 -4
- package/dist/web-components/{chunk-XIENXR7H.js → chunk-YRXCVKHV.js} +1 -3
- package/dist/web-components/{chunk-6T3RKUKG.js → chunk-ZTC6GRP7.js} +1 -2
- package/dist/web-components/globals.js +1 -1
- package/dist/web-components/index.js +16 -16
- package/dist/web-components/interaction/bds-accordion.js +1 -1
- package/dist/web-components/interaction/bds-button.d.ts +2 -2
- package/dist/web-components/interaction/bds-button.js +1 -1
- package/dist/web-components/interaction/bds-dialog.js +1 -1
- package/dist/web-components/interaction/bds-drawer.js +1 -1
- package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
- package/dist/web-components/interaction/form/bds-combobox.js +1 -1
- package/dist/web-components/interaction/form/bds-number-input.js +1 -1
- package/dist/web-components/interaction/form/bds-radio.js +1 -1
- package/dist/web-components/interaction/form/bds-segmented-control.d.ts +20 -3
- package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
- package/dist/web-components/interaction/form/bds-select.js +1 -1
- package/dist/web-components/interaction/form/bds-switch.js +1 -1
- package/dist/web-components/interaction/form/bds-textarea.js +1 -1
- package/dist/web-components/ui/bds-alert.js +1 -1
- package/dist/web-components/ui/bds-card.js +1 -1
- package/dist/web-components/ui/bds-notification-banner.js +1 -1
- package/package.json +2 -2
- package/src/components/interaction/Button/Button.module.css +12 -11
- package/src/components/interaction/Button/Button.native.tsx +10 -9
- package/src/components/interaction/Button/Button.tsx +1 -1
- package/src/components/interaction/Command/Command.module.css +3 -7
- package/src/components/interaction/Dialog/Dialog.module.css +7 -5
- package/src/components/interaction/Drawer/Drawer.module.css +49 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -3
- package/src/components/interaction/Popover/Popover.module.css +1 -3
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -3
- package/src/components/interaction/form/Combobox/Combobox.module.css +2 -6
- package/src/components/interaction/form/FormInput/FormInput.module.css +1 -3
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +16 -4
- package/src/components/interaction/form/Radio/Radio.module.css +6 -16
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +70 -25
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +55 -33
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +127 -56
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +89 -72
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +40 -62
- package/src/components/interaction/form/SegmentedControl/index.ts +1 -1
- package/src/components/interaction/form/Select/Select.module.css +2 -4
- package/src/components/interaction/form/Switch/Switch.module.css +5 -7
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +9 -9
- package/src/components/interaction/form/Switch/Switch.native.tsx +2 -2
- package/src/components/interaction/form/Textarea/Textarea.module.css +1 -3
- package/src/components/interaction/form/atoms/InputContainer.stories.tsx +64 -0
- package/src/components/interaction/form/atoms/Label.stories.tsx +33 -0
- package/src/components/interaction/form/atoms/Message.stories.tsx +33 -0
- package/src/components/layout/Card/Card.module.css +1 -3
- package/src/components/ui/Accordion/Accordion.module.css +1 -2
- package/src/components/ui/Alert/Alert.module.css +1 -2
- package/src/components/ui/Calendar/Calendar.module.css +2 -5
- package/src/components/ui/Carousel/Carousel.module.css +1 -3
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -2
- package/src/components/ui/Pagination/Pagination.module.css +1 -2
- package/src/components/ui/SkipLink/SkipLink.module.css +1 -2
- package/src/components/ui/Table/Table.module.css +1 -2
- package/src/css/bdc.css +16 -11
- package/src/index.ts +1 -1
- package/src/web-components/globals.ts +3 -3
- package/src/web-components/interaction/BdsAccordion.mdx +51 -0
- package/src/web-components/interaction/BdsAccordion.stories.tsx +85 -0
- package/src/web-components/interaction/BdsCollapsible.mdx +46 -0
- package/src/web-components/interaction/BdsCollapsible.stories.tsx +87 -0
- package/src/web-components/interaction/BdsDialog.mdx +49 -0
- package/src/web-components/interaction/BdsDialog.stories.tsx +75 -0
- package/src/web-components/interaction/BdsDrawer.mdx +50 -0
- package/src/web-components/interaction/BdsDrawer.stories.tsx +66 -0
- package/src/web-components/interaction/BdsSkipLink.mdx +34 -0
- package/src/web-components/interaction/BdsSkipLink.stories.tsx +55 -0
- package/src/web-components/interaction/BdsTabs.mdx +47 -0
- package/src/web-components/interaction/BdsTabs.stories.tsx +80 -0
- package/src/web-components/interaction/BdsTooltip.mdx +38 -0
- package/src/web-components/interaction/BdsTooltip.stories.tsx +68 -0
- package/src/web-components/interaction/bds-accordion.ts +1 -2
- package/src/web-components/interaction/bds-button.ts +15 -13
- package/src/web-components/interaction/bds-dialog.ts +2 -5
- package/src/web-components/interaction/bds-drawer.ts +50 -1
- package/src/web-components/interaction/form/BdsCheckbox.mdx +39 -0
- package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +73 -0
- package/src/web-components/interaction/form/BdsCombobox.mdx +52 -0
- package/src/web-components/interaction/form/BdsCombobox.stories.tsx +91 -0
- package/src/web-components/interaction/form/BdsFileInput.mdx +44 -0
- package/src/web-components/interaction/form/BdsFileInput.stories.tsx +84 -0
- package/src/web-components/interaction/form/BdsNumberInput.mdx +41 -0
- package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +90 -0
- package/src/web-components/interaction/form/BdsRadio.mdx +41 -0
- package/src/web-components/interaction/form/BdsRadio.stories.tsx +90 -0
- package/src/web-components/interaction/form/BdsSegmentedControl.mdx +42 -0
- package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +92 -0
- package/src/web-components/interaction/form/BdsSelect.mdx +38 -0
- package/src/web-components/interaction/form/BdsSelect.stories.tsx +93 -0
- package/src/web-components/interaction/form/BdsSlider.mdx +49 -0
- package/src/web-components/interaction/form/BdsSlider.stories.tsx +89 -0
- package/src/web-components/interaction/form/BdsSwitch.mdx +39 -0
- package/src/web-components/interaction/form/BdsSwitch.stories.tsx +73 -0
- package/src/web-components/interaction/form/BdsTextarea.mdx +41 -0
- package/src/web-components/interaction/form/BdsTextarea.stories.tsx +79 -0
- package/src/web-components/interaction/form/bds-checkbox.ts +1 -3
- package/src/web-components/interaction/form/bds-combobox.ts +1 -3
- package/src/web-components/interaction/form/bds-number-input.ts +3 -4
- package/src/web-components/interaction/form/bds-radio.ts +6 -12
- package/src/web-components/interaction/form/bds-segmented-control.ts +91 -20
- package/src/web-components/interaction/form/bds-select.ts +2 -4
- package/src/web-components/interaction/form/bds-switch.ts +3 -4
- package/src/web-components/interaction/form/bds-textarea.ts +1 -3
- package/src/web-components/ui/BdsAvatar.mdx +67 -0
- package/src/web-components/ui/BdsAvatar.stories.tsx +62 -0
- package/src/web-components/ui/BdsBreadcrumb.mdx +63 -0
- package/src/web-components/ui/BdsBreadcrumb.stories.tsx +59 -0
- package/src/web-components/ui/BdsCard.mdx +83 -0
- package/src/web-components/ui/BdsCard.stories.tsx +90 -0
- package/src/web-components/ui/BdsDescriptionList.mdx +67 -0
- package/src/web-components/ui/BdsDescriptionList.stories.tsx +79 -0
- package/src/web-components/ui/BdsIconWrapper.mdx +70 -0
- package/src/web-components/ui/BdsIconWrapper.stories.tsx +76 -0
- package/src/web-components/ui/BdsLink.mdx +75 -0
- package/src/web-components/ui/BdsLink.stories.tsx +64 -0
- package/src/web-components/ui/BdsLoading.mdx +62 -0
- package/src/web-components/ui/BdsLoading.stories.tsx +37 -0
- package/src/web-components/ui/BdsNotificationBanner.mdx +90 -0
- package/src/web-components/ui/BdsNotificationBanner.stories.tsx +93 -0
- package/src/web-components/ui/BdsProgress.mdx +63 -0
- package/src/web-components/ui/BdsProgress.stories.tsx +70 -0
- package/src/web-components/ui/BdsProgressCircle.mdx +64 -0
- package/src/web-components/ui/BdsProgressCircle.stories.tsx +69 -0
- package/src/web-components/ui/BdsRating.mdx +61 -0
- package/src/web-components/ui/BdsRating.stories.tsx +39 -0
- package/src/web-components/ui/BdsSectionHeader.mdx +69 -0
- package/src/web-components/ui/BdsSectionHeader.stories.tsx +56 -0
- package/src/web-components/ui/BdsSeparator.mdx +63 -0
- package/src/web-components/ui/BdsSeparator.stories.tsx +47 -0
- package/src/web-components/ui/BdsSkeleton.mdx +67 -0
- package/src/web-components/ui/BdsSkeleton.stories.tsx +50 -0
- package/src/web-components/ui/BdsTypography.mdx +83 -0
- package/src/web-components/ui/BdsTypography.stories.tsx +51 -0
- package/src/web-components/ui/bds-alert.ts +1 -2
- package/src/web-components/ui/bds-card.ts +1 -3
- package/src/web-components/ui/bds-notification-banner.ts +1 -2
|
@@ -6,105 +6,122 @@ const meta = {
|
|
|
6
6
|
title: 'React/Form/SegmentedControl',
|
|
7
7
|
component: SegmentedControl,
|
|
8
8
|
argTypes: {
|
|
9
|
-
size:
|
|
9
|
+
size: { control: 'radio', options: ['small', 'medium', 'large'] },
|
|
10
|
+
variant: { control: 'radio', options: ['outline', 'filled'] },
|
|
10
11
|
},
|
|
11
12
|
} satisfies Meta<typeof SegmentedControl>;
|
|
12
13
|
|
|
13
14
|
export default meta;
|
|
14
15
|
type Story = StoryObj<typeof meta>;
|
|
15
16
|
|
|
17
|
+
/** Default: outline variant (default), buttons driven by selectedIndex */
|
|
16
18
|
export const Default: Story = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
render: ({ size, variant, disabled }) => {
|
|
20
|
+
const [tab, setTab] = useState(1);
|
|
21
|
+
return (
|
|
22
|
+
<SegmentedControl selectedIndex={tab} size={size} variant={variant} disabled={disabled}>
|
|
23
|
+
<button onClick={() => setTab(0)}>Day</button>
|
|
24
|
+
<button onClick={() => setTab(1)}>Week</button>
|
|
25
|
+
<button onClick={() => setTab(2)}>Month</button>
|
|
26
|
+
</SegmentedControl>
|
|
27
|
+
);
|
|
25
28
|
},
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
/** Filled variant: sliding filled thumb */
|
|
32
|
+
export const Filled: Story = {
|
|
33
|
+
render: ({ size, disabled }) => {
|
|
34
|
+
const [tab, setTab] = useState(1);
|
|
35
|
+
return (
|
|
36
|
+
<SegmentedControl selectedIndex={tab} size={size} variant="filled" disabled={disabled}>
|
|
37
|
+
<button onClick={() => setTab(0)}>Day</button>
|
|
38
|
+
<button onClick={() => setTab(1)}>Week</button>
|
|
39
|
+
<button onClick={() => setTab(2)}>Month</button>
|
|
40
|
+
</SegmentedControl>
|
|
41
|
+
);
|
|
37
42
|
},
|
|
38
43
|
};
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Links: active item is auto-detected from aria-current="page".
|
|
47
|
+
* No selectedIndex needed — the component reads the ARIA attribute.
|
|
48
|
+
*/
|
|
49
|
+
export const WithLinks: Story = {
|
|
50
|
+
render: ({ size, variant, disabled }) => {
|
|
51
|
+
const [active, setActive] = useState('overview');
|
|
52
|
+
return (
|
|
53
|
+
<SegmentedControl size={size} variant={variant} disabled={disabled}>
|
|
54
|
+
<a href="#" aria-current={active === 'overview' ? 'page' : undefined} onClick={() => setActive('overview')}>Overview</a>
|
|
55
|
+
<a href="#" aria-current={active === 'usage' ? 'page' : undefined} onClick={() => setActive('usage')}>Usage</a>
|
|
56
|
+
<a href="#" aria-current={active === 'api' ? 'page' : undefined} onClick={() => setActive('api')}>API</a>
|
|
57
|
+
</SegmentedControl>
|
|
58
|
+
);
|
|
50
59
|
},
|
|
51
60
|
};
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
/** Small size */
|
|
63
|
+
export const Small: Story = {
|
|
64
|
+
render: () => {
|
|
65
|
+
const [tab, setTab] = useState(0);
|
|
66
|
+
return (
|
|
67
|
+
<SegmentedControl selectedIndex={tab} size="small">
|
|
68
|
+
<button onClick={() => setTab(0)}>List</button>
|
|
69
|
+
<button onClick={() => setTab(1)}>Grid</button>
|
|
70
|
+
</SegmentedControl>
|
|
71
|
+
);
|
|
63
72
|
},
|
|
64
|
-
decorators: [Story => <div style={{ maxWidth: '360px' }}><Story /></div>],
|
|
65
73
|
};
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
/** Large size with wrapping labels */
|
|
76
|
+
export const Large: Story = {
|
|
77
|
+
render: () => {
|
|
78
|
+
const [tab, setTab] = useState(1);
|
|
79
|
+
return (
|
|
80
|
+
<SegmentedControl selectedIndex={tab} size="large">
|
|
81
|
+
<button onClick={() => setTab(0)}>Personal plan</button>
|
|
82
|
+
<button onClick={() => setTab(1)}>Team plan</button>
|
|
83
|
+
<button onClick={() => setTab(2)}>Enterprise plan</button>
|
|
84
|
+
</SegmentedControl>
|
|
85
|
+
);
|
|
76
86
|
},
|
|
77
87
|
};
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
/** Large with wrapping text in a constrained container */
|
|
90
|
+
export const WrappingLabels: Story = {
|
|
91
|
+
render: () => {
|
|
92
|
+
const [tab, setTab] = useState(2);
|
|
93
|
+
return (
|
|
94
|
+
<div style={{ maxWidth: '360px' }}>
|
|
95
|
+
<SegmentedControl selectedIndex={tab} size="large">
|
|
96
|
+
<button onClick={() => setTab(0)}>Solar energy only</button>
|
|
97
|
+
<button onClick={() => setTab(1)}>Wind energy only</button>
|
|
98
|
+
<button onClick={() => setTab(2)}>Mix of sustainable sources</button>
|
|
99
|
+
</SegmentedControl>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
88
102
|
},
|
|
89
103
|
};
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
/** Disable the entire control */
|
|
106
|
+
export const AllDisabled: Story = {
|
|
107
|
+
render: () => (
|
|
108
|
+
<SegmentedControl selectedIndex={0} disabled>
|
|
109
|
+
<button>Option A</button>
|
|
110
|
+
<button>Option B</button>
|
|
111
|
+
</SegmentedControl>
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** aria-pressed drives auto-detection — no selectedIndex needed */
|
|
116
|
+
export const WithAriaPressed: Story = {
|
|
92
117
|
render: () => {
|
|
93
|
-
const [
|
|
118
|
+
const [tab, setTab] = useState('week');
|
|
94
119
|
return (
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
options={[
|
|
101
|
-
{ value: 'day', label: 'Day' },
|
|
102
|
-
{ value: 'week', label: 'Week' },
|
|
103
|
-
{ value: 'month', label: 'Month' },
|
|
104
|
-
]}
|
|
105
|
-
/>
|
|
106
|
-
<span style={{ fontSize: '0.875rem', opacity: 0.6 }}>Selected: {value}</span>
|
|
107
|
-
</div>
|
|
120
|
+
<SegmentedControl>
|
|
121
|
+
<button onClick={() => setTab('day')} aria-pressed={tab === 'day'}>Day</button>
|
|
122
|
+
<button onClick={() => setTab('week')} aria-pressed={tab === 'week'}>Week</button>
|
|
123
|
+
<button onClick={() => setTab('month')} aria-pressed={tab === 'month'}>Month</button>
|
|
124
|
+
</SegmentedControl>
|
|
108
125
|
);
|
|
109
126
|
},
|
|
110
127
|
};
|
|
@@ -1,92 +1,70 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Children, ReactElement, ReactNode, 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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
name: string;
|
|
15
|
-
options: SegmentedControlOption[];
|
|
16
|
-
/** Controlled selected value */
|
|
17
|
-
value?: string;
|
|
18
|
-
/** Uncontrolled initial value (defaults to first option) */
|
|
19
|
-
defaultValue?: string;
|
|
20
|
-
onChange?: (value: string) => void;
|
|
6
|
+
export interface SegmentedControlProps extends WithClassName {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Zero-based index of the active item.
|
|
10
|
+
* When omitted, auto-detected from the first child that has
|
|
11
|
+
* aria-current="page", aria-pressed={true}, or aria-selected={true}.
|
|
12
|
+
*/
|
|
13
|
+
selectedIndex?: number;
|
|
21
14
|
disabled?: boolean;
|
|
22
15
|
size?: 'small' | 'medium' | 'large';
|
|
16
|
+
/** "outline" (default) — sliding inset border; "filled" — sliding filled thumb */
|
|
17
|
+
variant?: 'outline' | 'filled';
|
|
23
18
|
'aria-label'?: string;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
export function SegmentedControl({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
value,
|
|
30
|
-
defaultValue,
|
|
31
|
-
onChange,
|
|
22
|
+
children,
|
|
23
|
+
selectedIndex: selectedIndexProp,
|
|
32
24
|
disabled,
|
|
33
25
|
size = 'medium',
|
|
26
|
+
variant = 'outline',
|
|
34
27
|
className,
|
|
35
28
|
'aria-label': ariaLabel,
|
|
36
|
-
...rest
|
|
37
29
|
}: Readonly<SegmentedControlProps>) {
|
|
38
|
-
const
|
|
30
|
+
const validChildren = Children.toArray(children).filter(isValidElement) as ReactElement<Record<string, unknown>>[];
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
32
|
+
// Auto-detect active index from standard ARIA attributes if not explicitly provided.
|
|
33
|
+
const autoIndex = selectedIndexProp === undefined
|
|
34
|
+
? validChildren.findIndex(child => {
|
|
35
|
+
const p = child.props as Record<string, unknown>;
|
|
36
|
+
return p['aria-current'] === 'page' || p['aria-pressed'] === true || p['aria-selected'] === true;
|
|
37
|
+
})
|
|
38
|
+
: -1;
|
|
46
39
|
|
|
47
|
-
const
|
|
48
|
-
if (value === undefined) setInternalValue(optionValue);
|
|
49
|
-
onChange?.(optionValue);
|
|
50
|
-
};
|
|
40
|
+
const activeIndex = Math.max(0, selectedIndexProp ?? (autoIndex >= 0 ? autoIndex : 0));
|
|
51
41
|
|
|
52
42
|
return (
|
|
53
43
|
<div
|
|
54
44
|
role="group"
|
|
55
45
|
aria-label={ariaLabel}
|
|
56
|
-
className={cn(css.control, css[`--size_${size}`], className)}
|
|
46
|
+
className={cn(css.control, css[`--size_${size}`], css[`--variant_${variant}`], className)}
|
|
57
47
|
style={{
|
|
58
|
-
'--control_count':
|
|
59
|
-
'--control_selected-index':
|
|
48
|
+
'--control_count': validChildren.length,
|
|
49
|
+
'--control_selected-index': activeIndex,
|
|
60
50
|
} as React.CSSProperties}
|
|
61
51
|
>
|
|
62
|
-
{/* Sliding thumb — purely visual */}
|
|
63
52
|
<span className={css.thumb} aria-hidden="true" />
|
|
53
|
+
<span className={css.indicator} aria-hidden="true" />
|
|
64
54
|
|
|
65
|
-
{
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const isDisabled = disabled ||
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
type="radio"
|
|
79
|
-
id={id}
|
|
80
|
-
name={name}
|
|
81
|
-
value={option.value}
|
|
82
|
-
checked={isChecked}
|
|
83
|
-
disabled={isDisabled}
|
|
84
|
-
className={css.radio}
|
|
85
|
-
onChange={() => handleChange(option.value)}
|
|
86
|
-
/>
|
|
87
|
-
<span className={css.label}>{option.label}</span>
|
|
88
|
-
</label>
|
|
89
|
-
);
|
|
55
|
+
{validChildren.map((child, index) => {
|
|
56
|
+
const isActive = index === activeIndex;
|
|
57
|
+
const p = child.props as Record<string, unknown>;
|
|
58
|
+
const isDisabled = disabled || !!p.disabled || p['aria-disabled'] === true;
|
|
59
|
+
return cloneElement(child, {
|
|
60
|
+
key: child.key ?? index,
|
|
61
|
+
className: cn(
|
|
62
|
+
css.item,
|
|
63
|
+
isActive && css['--active'],
|
|
64
|
+
isDisabled && css['--disabled'],
|
|
65
|
+
p.className as string | undefined,
|
|
66
|
+
),
|
|
67
|
+
});
|
|
90
68
|
})}
|
|
91
69
|
</div>
|
|
92
70
|
);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { SegmentedControl } from './SegmentedControl';
|
|
2
|
-
export type { SegmentedControlProps
|
|
2
|
+
export type { SegmentedControlProps } from './SegmentedControl';
|
|
@@ -21,10 +21,8 @@
|
|
|
21
21
|
--bdc_color: currentcolor;
|
|
22
22
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
23
23
|
|
|
24
|
-
outline: var(--bdc-outline_width) solid var(--bdc_color);
|
|
25
|
-
outline-offset: var(--bdc-outline_offset);
|
|
26
24
|
border-radius: var(--select_radius, var(--bdc-outline_radius));
|
|
27
|
-
box-shadow: var(--select_shadow, var(--bdc-outline_shadow));
|
|
25
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--select_shadow, var(--bdc-outline_shadow));
|
|
28
26
|
background-color: var(--select_color_bg, var(--bds-color_bg));
|
|
29
27
|
color: var(--select_color, var(--bds-color_on-bg));
|
|
30
28
|
cursor: pointer;
|
|
@@ -56,7 +54,7 @@
|
|
|
56
54
|
display: flex;
|
|
57
55
|
align-items: center;
|
|
58
56
|
pointer-events: none;
|
|
59
|
-
color: var(--
|
|
57
|
+
color: var(--select_chevron-color, var(--bds-color_interactive));
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
.chevron svg {
|
|
@@ -56,14 +56,12 @@
|
|
|
56
56
|
background-color: var(--switch_track-bg, var(--bds-color_grey--subtle));
|
|
57
57
|
padding-inline: var(--switch_track-pad);
|
|
58
58
|
pointer-events: none;
|
|
59
|
-
|
|
60
|
-
outline-offset: var(--bds-outline_offset);
|
|
61
|
-
box-shadow: var(--switch_shadow, var(--bdc-outline_shadow));
|
|
59
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--switch_shadow, var(--bdc-outline_shadow));
|
|
62
60
|
transition: --bdc_color var(--bds-animation_transition-duration) var(--bds-animation_easing),
|
|
63
61
|
background-color var(--bds-animation_transition-duration) var(--bds-animation_easing),
|
|
64
62
|
transform var(--bds-animation_transition-duration) var(--bds-animation_easing);
|
|
65
63
|
|
|
66
|
-
--bdc_color: var(--switch_thumb-bg, var(--bds-
|
|
64
|
+
--bdc_color: var(--switch_thumb-bg, var(--bds-color_interactive));
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
/* Thumb */
|
|
@@ -72,7 +70,7 @@
|
|
|
72
70
|
inline-size: var(--switch_thumb-size);
|
|
73
71
|
block-size: var(--switch_thumb-size);
|
|
74
72
|
border-radius: 50%;
|
|
75
|
-
background-color: var(--switch_thumb-bg, var(--bds-
|
|
73
|
+
background-color: var(--switch_thumb-bg, var(--bds-color_interactive));
|
|
76
74
|
box-shadow: var(--bds-shadow_s);
|
|
77
75
|
transition: var(--bds-animation_transition);
|
|
78
76
|
transform: translateX(0);
|
|
@@ -87,11 +85,11 @@
|
|
|
87
85
|
.switch:checked + .track {
|
|
88
86
|
background-color: var(--switch_track-bg--active, var(--bdc-color_active--subtle));
|
|
89
87
|
|
|
90
|
-
--bdc_color: var(--switch_thumb-bg--active, var(--
|
|
88
|
+
--bdc_color: var(--switch_thumb-bg--active, var(--bds-color_interactive));
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
.switch:checked + .track .thumb {
|
|
94
|
-
background-color: var(--switch_thumb-bg--active, var(--
|
|
92
|
+
background-color: var(--switch_thumb-bg--active, var(--bds-color_interactive));
|
|
95
93
|
transform: translateX(var(--switch_thumb-size));
|
|
96
94
|
}
|
|
97
95
|
|
|
@@ -38,17 +38,17 @@ describe('Switch (native)', () => {
|
|
|
38
38
|
expect(getSwitchInput(container)).toBeChecked();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('thumb
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
const { container } = renderWithTheme(
|
|
41
|
+
it('renders thumb in both on and off states without error (thumb always uses colorInteractive)', () => {
|
|
42
|
+
// thumbColor is always colorInteractive — interactive affordance is visible in both states.
|
|
43
|
+
// Track color provides the on/off visual feedback instead.
|
|
44
|
+
const { container: offContainer } = renderWithTheme(
|
|
45
45
|
<Switch label="Toggle" name="toggle" value={false} />,
|
|
46
46
|
);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(
|
|
47
|
+
const { container: onContainer } = renderWithTheme(
|
|
48
|
+
<Switch label="Toggle2" name="toggle2" value={true} />,
|
|
49
|
+
);
|
|
50
|
+
expect(offContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
|
|
51
|
+
expect(onContainer.querySelector('[style*="background-color"]')).toBeInTheDocument();
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it('renders hint message', () => {
|
|
@@ -47,8 +47,8 @@ export function Switch({
|
|
|
47
47
|
accessibilityLabel={label}
|
|
48
48
|
accessibilityRole="switch"
|
|
49
49
|
accessibilityState={{ checked: value, disabled }}
|
|
50
|
-
thumbColor={
|
|
51
|
-
trackColor={{ false: colors.colorBgSubtle, true: colors.
|
|
50
|
+
thumbColor={colors.colorInteractive}
|
|
51
|
+
trackColor={{ false: colors.colorBgSubtle, true: colors.colorBlueSubtle }}
|
|
52
52
|
/>
|
|
53
53
|
<Label label={label} />
|
|
54
54
|
</View>
|
|
@@ -13,10 +13,8 @@
|
|
|
13
13
|
--bdc_color: currentcolor;
|
|
14
14
|
--bdc-outline_radius: var(--bds-border_radius--xs);
|
|
15
15
|
|
|
16
|
-
outline: var(--bdc-outline_width) solid var(--bdc_color);
|
|
17
|
-
outline-offset: var(--bdc-outline_offset);
|
|
18
16
|
border-radius: var(--textarea_radius, var(--bdc-outline_radius));
|
|
19
|
-
box-shadow: var(--textarea_shadow, var(--bdc-outline_shadow));
|
|
17
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--textarea_shadow, var(--bdc-outline_shadow));
|
|
20
18
|
background-color: var(--textarea_color_bg, var(--bds-color_bg));
|
|
21
19
|
color: var(--textarea_color, var(--bds-color_on-bg));
|
|
22
20
|
resize: vertical;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { InputContainer } from './InputContainer';
|
|
4
|
+
import { Label } from './Label';
|
|
5
|
+
import { Message } from './Message';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'React/Form/Atoms/InputContainer',
|
|
9
|
+
component: InputContainer,
|
|
10
|
+
parameters: { layout: 'padded' },
|
|
11
|
+
} satisfies Meta<typeof InputContainer>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<InputContainer>
|
|
19
|
+
<Label id="demo-input" label="Email address" />
|
|
20
|
+
<input id="demo-input" type="email" placeholder="you@example.com" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
21
|
+
</InputContainer>
|
|
22
|
+
),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithHint: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<InputContainer>
|
|
28
|
+
<Label id="demo-hint" label="Username" />
|
|
29
|
+
<input id="demo-hint" type="text" placeholder="Enter username" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
30
|
+
<Message inputId="demo-hint" type="hint" message="At least 3 characters." />
|
|
31
|
+
</InputContainer>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithError: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<InputContainer>
|
|
38
|
+
<Label id="demo-error" label="Password" />
|
|
39
|
+
<input id="demo-error" type="password" placeholder="Enter password" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
40
|
+
<Message inputId="demo-error" type="error" message="Password is too short." />
|
|
41
|
+
</InputContainer>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const AllVariants: Story = {
|
|
46
|
+
render: () => (
|
|
47
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxWidth: '320px' }}>
|
|
48
|
+
<InputContainer>
|
|
49
|
+
<Label id="v1" label="Plain input" />
|
|
50
|
+
<input id="v1" type="text" placeholder="Default" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
51
|
+
</InputContainer>
|
|
52
|
+
<InputContainer>
|
|
53
|
+
<Label id="v2" label="With hint" />
|
|
54
|
+
<input id="v2" type="text" placeholder="Hint below" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
55
|
+
<Message inputId="v2" type="hint" message="Helpful hint text." />
|
|
56
|
+
</InputContainer>
|
|
57
|
+
<InputContainer>
|
|
58
|
+
<Label id="v3" label="With error" />
|
|
59
|
+
<input id="v3" type="text" placeholder="Error state" style={{ display: 'block', width: '100%', padding: '8px', boxSizing: 'border-box' }} />
|
|
60
|
+
<Message inputId="v3" type="error" message="This field has an error." />
|
|
61
|
+
</InputContainer>
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Label } from './Label';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'React/Form/Atoms/Label',
|
|
6
|
+
component: Label,
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
} satisfies Meta<typeof Label>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = { args: { id: 'input-1', label: 'Email address' } };
|
|
14
|
+
export const Required: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
id: 'input-required',
|
|
17
|
+
label: (
|
|
18
|
+
<>
|
|
19
|
+
Full name <span aria-hidden="true" style={{ color: 'red' }}>*</span>
|
|
20
|
+
</>
|
|
21
|
+
),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const AllVariants: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
28
|
+
<Label id="a" label="Plain label" />
|
|
29
|
+
<Label id="b" label="Label for a text input" />
|
|
30
|
+
<Label id="c" label={<><strong>Bold</strong> label with markup</>} />
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Message } from './Message';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'React/Form/Atoms/Message',
|
|
6
|
+
component: Message,
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
argTypes: {
|
|
9
|
+
type: { control: 'select', options: ['hint', 'error'] },
|
|
10
|
+
},
|
|
11
|
+
} satisfies Meta<typeof Message>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Hint: Story = {
|
|
17
|
+
args: { inputId: 'field-1', type: 'hint', message: 'Password must be at least 8 characters.' },
|
|
18
|
+
};
|
|
19
|
+
export const Error: Story = {
|
|
20
|
+
args: { inputId: 'field-2', type: 'error', message: 'This field is required.' },
|
|
21
|
+
};
|
|
22
|
+
export const NoMessage: Story = {
|
|
23
|
+
args: { inputId: 'field-3', type: 'hint', message: undefined },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const AllVariants: Story = {
|
|
27
|
+
render: () => (
|
|
28
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
29
|
+
<Message inputId="v1" type="hint" message="Hint: Enter your full legal name." />
|
|
30
|
+
<Message inputId="v2" type="error" message="Error: This field is required." />
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
@@ -19,9 +19,7 @@
|
|
|
19
19
|
border: var(--card_border, none);
|
|
20
20
|
--bdc_color: currentcolor;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
outline-offset: var(--bdc-outline_offset);
|
|
24
|
-
box-shadow: var(--bds-shadow_s);
|
|
22
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--bds-shadow_s);
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
.card.--clickable {
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
--bdc_color: var(--bds-color_bg--subtle);
|
|
7
7
|
--bdc-outline_radius: var(--bds-border_radius--s);
|
|
8
8
|
|
|
9
|
-
outline: var(--bdc-outline_width) solid var(--bdc_color);
|
|
10
|
-
outline-offset: var(--bdc-outline_offset);
|
|
11
9
|
border-radius: var(--accordion_radius, var(--bdc-outline_radius));
|
|
10
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
|
|
12
11
|
overflow: hidden;
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -8,11 +8,9 @@
|
|
|
8
8
|
--bdc_color: var(--bds-color_bg--subtle);
|
|
9
9
|
--bdc-outline_radius: var(--bds-border_radius--m);
|
|
10
10
|
|
|
11
|
-
outline: var(--bdc-outline_width) solid var(--bdc_color);
|
|
12
|
-
outline-offset: var(--bdc-outline_offset);
|
|
13
11
|
border-radius: var(--calendar_radius, var(--bdc-outline_radius));
|
|
14
12
|
padding: var(--bds-space_m);
|
|
15
|
-
box-shadow: var(--calendar_shadow, var(--bds-shadow_s));
|
|
13
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--calendar_shadow, var(--bds-shadow_s));
|
|
16
14
|
inline-size: 20rem;
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -107,8 +105,7 @@
|
|
|
107
105
|
--bdc_color: var(--bds-color_interactive);
|
|
108
106
|
|
|
109
107
|
border: var(--calendar_today-border, none);
|
|
110
|
-
|
|
111
|
-
outline-offset: var(--bdc-outline_offset);
|
|
108
|
+
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
.selected {
|