@transferwise/components 46.133.0 → 46.133.1
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/build/chips/Chips.js.map +1 -1
- package/build/chips/Chips.mjs.map +1 -1
- package/build/label/Label.js +1 -1
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.mjs +1 -1
- package/build/label/Label.mjs.map +1 -1
- package/build/logo/Logo.js +6 -0
- package/build/logo/Logo.js.map +1 -1
- package/build/logo/Logo.mjs +6 -0
- package/build/logo/Logo.mjs.map +1 -1
- package/build/main.css +4 -4
- package/build/styles/listItem/ListItem.css +4 -4
- package/build/styles/listItem/ListItem.grid.css +3 -3
- package/build/styles/main.css +4 -4
- package/build/types/chips/Chips.d.ts +1 -1
- package/build/types/chips/Chips.d.ts.map +1 -1
- package/build/types/common/commonProps.d.ts +0 -6
- package/build/types/common/commonProps.d.ts.map +1 -1
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/logo/Logo.d.ts +10 -1
- package/build/types/logo/Logo.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/button/_stories/Button.story.tsx +15 -5
- package/src/checkboxButton/CheckboxButton.story.tsx +125 -44
- package/src/checkboxButton/CheckboxButton.test.story.tsx +236 -0
- package/src/chips/Chips.story.tsx +141 -102
- package/src/chips/Chips.test.story.tsx +177 -0
- package/src/chips/Chips.tsx +1 -1
- package/src/circularButton/CircularButton.story.tsx +261 -49
- package/src/circularButton/CircularButton.test.story.tsx +192 -2
- package/src/common/commonProps.ts +0 -6
- package/src/iconButton/IconButton.story.tsx +315 -110
- package/src/iconButton/IconButton.test.story.tsx +217 -44
- package/src/label/Label.tsx +1 -2
- package/src/listItem/ListItem.css +4 -4
- package/src/listItem/ListItem.grid.css +3 -3
- package/src/listItem/ListItem.grid.less +5 -3
- package/src/listItem/ListItem.less +1 -1
- package/src/listItem/ListItem.vars.less +2 -2
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +55 -0
- package/src/logo/Logo.story.tsx +181 -21
- package/src/logo/Logo.test.story.tsx +40 -7
- package/src/logo/Logo.tsx +10 -1
- package/src/main.css +4 -4
- package/src/switch/Switch.story.tsx +64 -42
- package/src/switch/Switch.test.story.tsx +123 -0
package/src/logo/Logo.story.tsx
CHANGED
|
@@ -2,36 +2,196 @@ import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
|
2
2
|
|
|
3
3
|
import Logo, { LogoType } from '.';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* **Design guidance**: <a href="https://wise.design/foundations/logo" target="_blank">wise.design/foundations/logo</a>
|
|
7
|
+
*/
|
|
8
|
+
const meta: Meta<typeof Logo> = {
|
|
6
9
|
component: Logo,
|
|
7
10
|
title: 'Content/Logo',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
args: {
|
|
12
|
+
type: 'WISE',
|
|
13
|
+
inverse: false,
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
type: {
|
|
17
|
+
control: 'radio',
|
|
18
|
+
options: Object.values(LogoType),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: { toc: true },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof Logo>;
|
|
29
|
+
|
|
30
|
+
/** Explore all props via the controls panel. */
|
|
31
|
+
export const Playground: Story = {
|
|
32
|
+
render: (args) => (
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
padding: '2rem',
|
|
36
|
+
...(args.inverse
|
|
37
|
+
? { backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)' }
|
|
38
|
+
: {}),
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Logo {...args} />
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The three logo types: standard Wise, Wise Business, and Wise Platform. <br />
|
|
48
|
+
* `WISE_BUSINESS` renders the same SVG as `WISE` but with the accessible label "Wise Business".
|
|
49
|
+
*/
|
|
50
|
+
export const Types: Story = {
|
|
51
|
+
argTypes: {
|
|
52
|
+
type: { table: { disable: true } },
|
|
53
|
+
},
|
|
54
|
+
render: (args) => (
|
|
55
|
+
<div
|
|
56
|
+
style={{
|
|
57
|
+
display: 'flex',
|
|
58
|
+
gap: '3rem',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
padding: '2rem',
|
|
61
|
+
borderRadius: '8px',
|
|
62
|
+
...(args.inverse
|
|
63
|
+
? { backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)' }
|
|
64
|
+
: {}),
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{Object.values(LogoType).map((type) => (
|
|
68
|
+
<div
|
|
69
|
+
key={type}
|
|
70
|
+
style={{
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'column',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
gap: '0.5rem',
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<Logo {...args} type={type} />
|
|
78
|
+
<span
|
|
79
|
+
style={{
|
|
80
|
+
fontSize: '12px',
|
|
81
|
+
...(args.inverse ? { color: 'rgba(255,255,255,0.6)' } : { opacity: 0.6 }),
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{type}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
parameters: {
|
|
91
|
+
docs: {
|
|
92
|
+
source: {
|
|
93
|
+
code: `<Logo type="WISE" />
|
|
94
|
+
<Logo type="WISE_BUSINESS" />
|
|
95
|
+
<Logo type="WISE_PLATFORM" />`,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Below `576px` the flag-only mark is shown; at `576px` and above the full wordmark is displayed.
|
|
103
|
+
*/
|
|
104
|
+
export const Responsive: Story = {
|
|
105
|
+
argTypes: {
|
|
106
|
+
type: { table: { disable: true } },
|
|
107
|
+
inverse: { table: { disable: true } },
|
|
108
|
+
},
|
|
109
|
+
render: (args) => (
|
|
110
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
|
111
|
+
<div>
|
|
112
|
+
<span style={{ fontSize: '12px', opacity: 0.6, display: 'block', marginBottom: '0.5rem' }}>
|
|
113
|
+
{'< 576px (flag only)'}
|
|
114
|
+
</span>
|
|
115
|
+
<div style={{ display: 'flex', gap: '3rem', alignItems: 'center' }}>
|
|
12
116
|
{Object.values(LogoType).map((type) => (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
117
|
+
<span key={type} className="np-logo" style={{ display: 'inline-block' }}>
|
|
118
|
+
<style>{`.responsive-sm .np-logo-svg--size-md { display: none !important; } .responsive-sm .np-logo-svg--size-sm { display: block !important; }`}</style>
|
|
119
|
+
<span className="responsive-sm">
|
|
120
|
+
<Logo type={type} />
|
|
121
|
+
</span>
|
|
122
|
+
</span>
|
|
16
123
|
))}
|
|
17
124
|
</div>
|
|
18
|
-
|
|
125
|
+
</div>
|
|
126
|
+
<div>
|
|
127
|
+
<span style={{ fontSize: '12px', opacity: 0.6, display: 'block', marginBottom: '0.5rem' }}>
|
|
128
|
+
{'\u2265 576px (full wordmark)'}
|
|
129
|
+
</span>
|
|
130
|
+
<div style={{ display: 'flex', gap: '3rem', alignItems: 'center' }}>
|
|
19
131
|
{Object.values(LogoType).map((type) => (
|
|
20
|
-
<
|
|
21
|
-
<Logo type={type} inverse />
|
|
22
|
-
</div>
|
|
132
|
+
<Logo key={type} type={type} />
|
|
23
133
|
))}
|
|
24
134
|
</div>
|
|
25
|
-
|
|
26
|
-
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
),
|
|
138
|
+
parameters: {
|
|
139
|
+
docs: {
|
|
140
|
+
source: { code: null },
|
|
141
|
+
},
|
|
27
142
|
},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Story = StoryObj<typeof Logo>;
|
|
143
|
+
};
|
|
31
144
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
145
|
+
/**
|
|
146
|
+
* All logo types on a dark background using the `inverse` prop, which renders a
|
|
147
|
+
* light-coloured version suited for dark surfaces.
|
|
148
|
+
*/
|
|
149
|
+
export const Inverse: Story = {
|
|
150
|
+
argTypes: {
|
|
151
|
+
type: { table: { disable: true } },
|
|
152
|
+
inverse: { table: { disable: true } },
|
|
153
|
+
},
|
|
154
|
+
decorators: [
|
|
155
|
+
(Story) => (
|
|
156
|
+
<div
|
|
157
|
+
style={{
|
|
158
|
+
display: 'flex',
|
|
159
|
+
gap: '3rem',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)',
|
|
162
|
+
padding: '2rem',
|
|
163
|
+
borderRadius: '8px',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<Story />
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
],
|
|
170
|
+
render: (args) => (
|
|
171
|
+
<>
|
|
172
|
+
{Object.values(LogoType).map((type) => (
|
|
173
|
+
<div
|
|
174
|
+
key={type}
|
|
175
|
+
style={{
|
|
176
|
+
display: 'flex',
|
|
177
|
+
flexDirection: 'column',
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap: '0.5rem',
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
<Logo type={type} inverse />
|
|
183
|
+
<span style={{ fontSize: '12px', color: 'rgba(255,255,255,0.6)' }}>{type}</span>
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</>
|
|
187
|
+
),
|
|
188
|
+
parameters: {
|
|
189
|
+
docs: {
|
|
190
|
+
source: {
|
|
191
|
+
code: `<Logo type="WISE" inverse />
|
|
192
|
+
<Logo type="WISE_BUSINESS" inverse />
|
|
193
|
+
<Logo type="WISE_PLATFORM" inverse />`,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
36
196
|
},
|
|
37
197
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import Logo, { LogoType } from '.';
|
|
3
4
|
import { withVariantConfig } from '../../.storybook/helpers';
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Logo> = {
|
|
@@ -7,14 +8,46 @@ const meta: Meta<typeof Logo> = {
|
|
|
7
8
|
tags: ['!autodocs', '!manifest'],
|
|
8
9
|
title: 'Content/Logo/Tests',
|
|
9
10
|
};
|
|
11
|
+
|
|
10
12
|
export default meta;
|
|
11
13
|
|
|
12
14
|
type Story = StoryObj<typeof Logo>;
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const AllVariants = () => (
|
|
17
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
|
18
|
+
<div style={{ display: 'flex', gap: '2rem', alignItems: 'center' }}>
|
|
19
|
+
{Object.values(LogoType).map((type) => (
|
|
20
|
+
<Logo key={type} type={type} />
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
display: 'flex',
|
|
26
|
+
gap: '2rem',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
backgroundColor: 'var(--color-content-primary)',
|
|
29
|
+
padding: '1rem',
|
|
30
|
+
borderRadius: '8px',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{Object.values(LogoType).map((type) => (
|
|
34
|
+
<Logo key={type} type={type} inverse />
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const Variants: Story = {
|
|
41
|
+
render: () => <AllVariants />,
|
|
42
|
+
...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const RTL: Story = {
|
|
46
|
+
render: () => <AllVariants />,
|
|
47
|
+
...withVariantConfig(['rtl']),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Zoom400: Story = {
|
|
51
|
+
render: () => <AllVariants />,
|
|
52
|
+
...withVariantConfig(['400%']),
|
|
20
53
|
};
|
package/src/logo/Logo.tsx
CHANGED
|
@@ -33,7 +33,10 @@ export enum LogoType {
|
|
|
33
33
|
export interface LogoProps {
|
|
34
34
|
/** Extra classes applied to Logo */
|
|
35
35
|
className?: string;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Renders a light-coloured version suited for dark backgrounds.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
37
40
|
inverse?: boolean;
|
|
38
41
|
/**
|
|
39
42
|
* What type of logo to display
|
|
@@ -48,6 +51,12 @@ const labelByType = {
|
|
|
48
51
|
WISE_PLATFORM: 'Wise Platform',
|
|
49
52
|
} satisfies Record<`${LogoType}`, string>;
|
|
50
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Renders the Wise wordmark logo. Responsive — shows the flag-only mark on small viewports
|
|
56
|
+
* and the full wordmark on ≥576px (small breakpoint and above).
|
|
57
|
+
*
|
|
58
|
+
* @see {@link https://wise.design/foundations/logo Design Spec}
|
|
59
|
+
*/
|
|
51
60
|
export default function Logo({ className, inverse, type = 'WISE' }: LogoProps) {
|
|
52
61
|
const LogoSm =
|
|
53
62
|
svgPaths[`WISE_FLAG${type === 'WISE_PLATFORM' ? '_PLATFORM' : ''}${inverse ? '_INVERSE' : ''}`];
|
package/src/main.css
CHANGED
|
@@ -29430,7 +29430,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29430
29430
|
margin-top: -2px;
|
|
29431
29431
|
}
|
|
29432
29432
|
|
|
29433
|
-
@container (
|
|
29433
|
+
@container (width > 308px) {
|
|
29434
29434
|
.wds-list-item-gridWrapper .wds-list-item-control-wrapper {
|
|
29435
29435
|
height: var(--wds-list-item-control-wrapper-height);
|
|
29436
29436
|
align-content: center;
|
|
@@ -29527,7 +29527,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29527
29527
|
}
|
|
29528
29528
|
}
|
|
29529
29529
|
|
|
29530
|
-
@container (
|
|
29530
|
+
@container (240px < width <= 308px) {
|
|
29531
29531
|
.wds-list-item-gridWrapper .wds-list-item-media-image {
|
|
29532
29532
|
-o-object-position: bottom left;
|
|
29533
29533
|
object-position: bottom left;
|
|
@@ -29659,7 +29659,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29659
29659
|
}
|
|
29660
29660
|
}
|
|
29661
29661
|
|
|
29662
|
-
@container (
|
|
29662
|
+
@container (width <= 240px) {
|
|
29663
29663
|
.wds-list-item-gridWrapper .wds-list-item-control-wrapper {
|
|
29664
29664
|
align-content: start;
|
|
29665
29665
|
}
|
|
@@ -29987,7 +29987,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29987
29987
|
justify-content: center;
|
|
29988
29988
|
}
|
|
29989
29989
|
|
|
29990
|
-
@container (
|
|
29990
|
+
@container (width > 308px) {
|
|
29991
29991
|
.wds-list-item-titles,
|
|
29992
29992
|
.wds-list-item-value {
|
|
29993
29993
|
min-height: 100%;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
3
|
|
|
4
|
+
import { storySourceWithoutNoise } from '../../.storybook/helpers';
|
|
4
5
|
import Switch, { SwitchProps } from './Switch';
|
|
5
6
|
import { Field } from '../field/Field';
|
|
6
7
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
7
8
|
import { Label } from '../label';
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* **Design guidance**: <a href="https://wise.design/components/switch" target="_blank">wise.design/components/switch</a>
|
|
12
|
+
*/
|
|
9
13
|
const meta: Meta<typeof Switch> = {
|
|
10
14
|
component: Switch,
|
|
11
15
|
title: 'Actions/Switch',
|
|
@@ -15,59 +19,84 @@ const meta: Meta<typeof Switch> = {
|
|
|
15
19
|
id: 'switchId',
|
|
16
20
|
className: 'switchClassName',
|
|
17
21
|
'aria-labelledby': undefined,
|
|
18
|
-
'aria-label':
|
|
22
|
+
'aria-label': 'Toggle switch',
|
|
19
23
|
onClick: fn(),
|
|
20
24
|
},
|
|
25
|
+
argTypes: {
|
|
26
|
+
id: { description: '', table: { category: 'Common' } },
|
|
27
|
+
className: { description: '', table: { category: 'Common' } },
|
|
28
|
+
onClick: { description: '', table: { category: 'Common', type: { summary: 'function' } } },
|
|
29
|
+
},
|
|
30
|
+
parameters: {
|
|
31
|
+
docs: { toc: true },
|
|
32
|
+
},
|
|
21
33
|
} satisfies Meta<typeof Switch>;
|
|
22
34
|
|
|
23
35
|
export default meta;
|
|
24
36
|
type Story = StoryObj<typeof Switch>;
|
|
25
37
|
|
|
26
|
-
export const
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
export const Playground: Story = storySourceWithoutNoise({
|
|
39
|
+
render: function Render(args: SwitchProps) {
|
|
40
|
+
const [checked, setChecked] = useState(args.checked ?? false);
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setChecked(args.checked ?? false);
|
|
44
|
+
}, [args.checked]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Field id="playground-field" label="Switch label">
|
|
48
|
+
<Switch
|
|
49
|
+
{...args}
|
|
50
|
+
checked={checked}
|
|
51
|
+
onClick={() => setChecked((value: boolean) => !value)}
|
|
52
|
+
/>
|
|
53
|
+
</Field>
|
|
54
|
+
);
|
|
34
55
|
},
|
|
35
|
-
};
|
|
56
|
+
});
|
|
36
57
|
|
|
37
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Three labelling patterns: <a href="?path=/docs/forms-label--docs">Label</a>, `aria-labelledby`, and <a href="?path=/docs/forms-field--docs">Field</a>. <br />
|
|
60
|
+
* Use Label for simple label-input pairs. Use `aria-labelledby` to reference existing text as the accessible name. Use Field for form layouts with labels, descriptions, and validation.
|
|
61
|
+
*/
|
|
62
|
+
export const LabelPatterns: Story = {
|
|
38
63
|
render: function Render({ disabled }: SwitchProps) {
|
|
39
|
-
const [checked1, setCheck1] = useState(
|
|
40
|
-
const [checked2, setCheck2] = useState(
|
|
41
|
-
const [checked3, setCheck3] = useState(
|
|
64
|
+
const [checked1, setCheck1] = useState(true);
|
|
65
|
+
const [checked2, setCheck2] = useState(false);
|
|
66
|
+
const [checked3, setCheck3] = useState(true);
|
|
42
67
|
|
|
43
68
|
return (
|
|
44
|
-
|
|
45
|
-
<Field id="fieldId" label="Using Field component">
|
|
46
|
-
<Switch checked={checked1} disabled={disabled} onClick={() => setCheck1(!checked1)} />
|
|
47
|
-
</Field>
|
|
48
|
-
|
|
69
|
+
<div className="d-flex flex-column" style={{ gap: '1rem' }}>
|
|
49
70
|
<div>
|
|
50
|
-
<Label htmlFor="labelId">
|
|
71
|
+
<Label htmlFor="labelId">Enable two-factor authentication</Label>
|
|
51
72
|
<Switch
|
|
52
73
|
id="labelId"
|
|
53
|
-
checked={
|
|
74
|
+
checked={checked1}
|
|
54
75
|
disabled={disabled}
|
|
55
|
-
onClick={() =>
|
|
76
|
+
onClick={() => setCheck1(!checked1)}
|
|
56
77
|
/>
|
|
57
78
|
</div>
|
|
58
79
|
|
|
59
80
|
<div>
|
|
60
81
|
<strong id="ariaId" className="d-block">
|
|
61
|
-
|
|
82
|
+
Marketing emails
|
|
62
83
|
</strong>
|
|
63
84
|
<Switch
|
|
64
85
|
aria-labelledby="ariaId"
|
|
65
|
-
checked={
|
|
86
|
+
checked={checked2}
|
|
66
87
|
disabled={disabled}
|
|
67
|
-
onClick={() =>
|
|
88
|
+
onClick={() => setCheck2(!checked2)}
|
|
68
89
|
/>
|
|
69
90
|
</div>
|
|
70
|
-
|
|
91
|
+
|
|
92
|
+
<Field
|
|
93
|
+
id="fieldId"
|
|
94
|
+
label="Enable notifications"
|
|
95
|
+
description="Get alerts when transfers complete"
|
|
96
|
+
>
|
|
97
|
+
<Switch checked={checked3} disabled={disabled} onClick={() => setCheck3(!checked3)} />
|
|
98
|
+
</Field>
|
|
99
|
+
</div>
|
|
71
100
|
);
|
|
72
101
|
},
|
|
73
102
|
parameters: {
|
|
@@ -78,7 +107,7 @@ function Render() {
|
|
|
78
107
|
const [checked1, setCheck1] = useState(false);
|
|
79
108
|
const [checked2, setCheck2] = useState(true);
|
|
80
109
|
const [checked3, setCheck3] = useState(false);
|
|
81
|
-
|
|
110
|
+
|
|
82
111
|
return (
|
|
83
112
|
<>
|
|
84
113
|
<Field id="fieldId" label="Using Field component">
|
|
@@ -87,7 +116,7 @@ function Render() {
|
|
|
87
116
|
onClick={() => setCheck1(!checked1)}
|
|
88
117
|
/>
|
|
89
118
|
</Field>
|
|
90
|
-
|
|
119
|
+
|
|
91
120
|
<div>
|
|
92
121
|
<Label htmlFor="labelId">Using standalone Label component</Label>
|
|
93
122
|
<Switch
|
|
@@ -96,7 +125,7 @@ function Render() {
|
|
|
96
125
|
onClick={() => setCheck2(!checked2)}
|
|
97
126
|
/>
|
|
98
127
|
</div>
|
|
99
|
-
|
|
128
|
+
|
|
100
129
|
<div>
|
|
101
130
|
<strong id="ariaId" className="d-block">Using \`aria-labelledby\`</strong>
|
|
102
131
|
<Switch
|
|
@@ -111,13 +140,6 @@ function Render() {
|
|
|
111
140
|
},
|
|
112
141
|
},
|
|
113
142
|
},
|
|
114
|
-
decorators: [
|
|
115
|
-
(Story: any) => (
|
|
116
|
-
<div className="d-flex flex-column" style={{ gap: '1rem' }}>
|
|
117
|
-
<Story />
|
|
118
|
-
</div>
|
|
119
|
-
),
|
|
120
|
-
],
|
|
121
143
|
args: {},
|
|
122
144
|
argTypes: {
|
|
123
145
|
checked: { table: { disable: true } },
|
|
@@ -126,17 +148,17 @@ function Render() {
|
|
|
126
148
|
};
|
|
127
149
|
|
|
128
150
|
export const Disabled: Story = {
|
|
129
|
-
render: function Render(
|
|
151
|
+
render: function Render() {
|
|
130
152
|
const [checked1, setCheck1] = useState(false);
|
|
131
153
|
const [checked2, setCheck2] = useState(true);
|
|
132
154
|
|
|
133
155
|
return (
|
|
134
156
|
<>
|
|
135
|
-
<Field label="
|
|
157
|
+
<Field label="Disabled unchecked">
|
|
136
158
|
<Switch checked={checked1} disabled onClick={() => setCheck1(!checked1)} />
|
|
137
159
|
</Field>
|
|
138
160
|
|
|
139
|
-
<Field label="
|
|
161
|
+
<Field label="Disabled checked">
|
|
140
162
|
<Switch checked={checked2} disabled onClick={() => setCheck2(!checked2)} />
|
|
141
163
|
</Field>
|
|
142
164
|
</>
|
|
@@ -152,14 +174,14 @@ function Render() {
|
|
|
152
174
|
|
|
153
175
|
return (
|
|
154
176
|
<>
|
|
155
|
-
<Field label="
|
|
177
|
+
<Field label="Disabled unchecked">
|
|
156
178
|
<Switch
|
|
157
179
|
checked={checked1}
|
|
158
180
|
disabled
|
|
159
181
|
onClick={() => setCheck1(!checked1)}
|
|
160
182
|
/>
|
|
161
|
-
</
|
|
162
|
-
<Field label="
|
|
183
|
+
</Field>
|
|
184
|
+
<Field label="Disabled checked">
|
|
163
185
|
<Switch
|
|
164
186
|
checked={checked2}
|
|
165
187
|
disabled
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
6
|
+
import { Field } from '../field/Field';
|
|
7
|
+
import Switch from './Switch';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
component: Switch,
|
|
11
|
+
title: 'Actions/Switch/Tests',
|
|
12
|
+
tags: ['!autodocs', '!manifest'],
|
|
13
|
+
} satisfies Meta<typeof Switch>;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof Switch>;
|
|
16
|
+
|
|
17
|
+
/** All switch states across all themes. */
|
|
18
|
+
export const Variants: Story = {
|
|
19
|
+
render: function Render() {
|
|
20
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
21
|
+
const [checked, setChecked] = useState(true);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', padding: '16px' }}>
|
|
25
|
+
<Field label="Unchecked">
|
|
26
|
+
<Switch checked={unchecked} onClick={() => setUnchecked((v) => !v)} />
|
|
27
|
+
</Field>
|
|
28
|
+
<Field label="Checked">
|
|
29
|
+
<Switch checked={checked} onClick={() => setChecked((v) => !v)} />
|
|
30
|
+
</Field>
|
|
31
|
+
<Field label="Disabled (off)">
|
|
32
|
+
<Switch checked={false} disabled onClick={() => {}} />
|
|
33
|
+
</Field>
|
|
34
|
+
<Field label="Disabled (on)">
|
|
35
|
+
<Switch checked disabled onClick={() => {}} />
|
|
36
|
+
</Field>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tests keyboard navigation and interaction:
|
|
45
|
+
* - Tab focuses the switch
|
|
46
|
+
* - Space toggles the switch on and off
|
|
47
|
+
*/
|
|
48
|
+
export const KeyboardInteraction: Story = {
|
|
49
|
+
render: function Render() {
|
|
50
|
+
const [checked, setChecked] = useState(false);
|
|
51
|
+
return (
|
|
52
|
+
<Field id="notif-field" label="Email notifications">
|
|
53
|
+
<Switch checked={checked} onClick={() => setChecked((v) => !v)} />
|
|
54
|
+
</Field>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
play: async ({ canvasElement, step }) => {
|
|
58
|
+
const wait = async (ms: number) =>
|
|
59
|
+
new Promise<void>((resolve) => {
|
|
60
|
+
setTimeout(resolve, ms);
|
|
61
|
+
});
|
|
62
|
+
const canvas = within(canvasElement);
|
|
63
|
+
const switchEl = canvas.getByRole('switch');
|
|
64
|
+
|
|
65
|
+
await step('tab focuses the switch', async () => {
|
|
66
|
+
await userEvent.tab();
|
|
67
|
+
await expect(switchEl).toHaveFocus();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await wait(400);
|
|
71
|
+
|
|
72
|
+
await step('space toggles the switch on', async () => {
|
|
73
|
+
await userEvent.keyboard(' ');
|
|
74
|
+
await expect(switchEl).toHaveAttribute('aria-checked', 'true');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await wait(400);
|
|
78
|
+
|
|
79
|
+
await step('space toggles the switch off', async () => {
|
|
80
|
+
await userEvent.keyboard(' ');
|
|
81
|
+
await expect(switchEl).toHaveAttribute('aria-checked', 'false');
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Switch rendered in RTL mode. */
|
|
87
|
+
export const RTL: Story = {
|
|
88
|
+
render: function Render() {
|
|
89
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
90
|
+
const [checked, setChecked] = useState(true);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', padding: '16px' }}>
|
|
94
|
+
<Field label="Unchecked">
|
|
95
|
+
<Switch checked={unchecked} onClick={() => setUnchecked((v) => !v)} />
|
|
96
|
+
</Field>
|
|
97
|
+
<Field label="Checked">
|
|
98
|
+
<Switch checked={checked} onClick={() => setChecked((v) => !v)} />
|
|
99
|
+
</Field>
|
|
100
|
+
<Field label="Disabled (off)">
|
|
101
|
+
<Switch checked={false} disabled onClick={() => {}} />
|
|
102
|
+
</Field>
|
|
103
|
+
<Field label="Disabled (on)">
|
|
104
|
+
<Switch checked disabled onClick={() => {}} />
|
|
105
|
+
</Field>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
...withVariantConfig(['rtl']),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Switch at 400% zoom for accessibility testing. */
|
|
113
|
+
export const Zoom400: Story = {
|
|
114
|
+
render: function Render() {
|
|
115
|
+
const [checked, setChecked] = useState(false);
|
|
116
|
+
return (
|
|
117
|
+
<Field label="Email notifications">
|
|
118
|
+
<Switch checked={checked} onClick={() => setChecked((v) => !v)} />
|
|
119
|
+
</Field>
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
...withVariantConfig(['400%']),
|
|
123
|
+
};
|