@transferwise/components 46.133.0 → 46.134.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/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 +47 -9
- package/build/moneyInput/MoneyInput.js +28 -12
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +30 -14
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/moneyInput/currencyFormatting.js +8 -2
- package/build/moneyInput/currencyFormatting.js.map +1 -1
- package/build/moneyInput/currencyFormatting.mjs +5 -4
- package/build/moneyInput/currencyFormatting.mjs.map +1 -1
- package/build/statusIcon/StatusIcon.js +1 -12
- package/build/statusIcon/StatusIcon.js.map +1 -1
- package/build/statusIcon/StatusIcon.mjs +1 -12
- package/build/statusIcon/StatusIcon.mjs.map +1 -1
- package/build/styles/listItem/ListItem.css +4 -4
- package/build/styles/listItem/ListItem.grid.css +3 -3
- package/build/styles/main.css +47 -9
- package/build/styles/sentimentSurface/SentimentSurface.css +1 -1
- package/build/styles/statusIcon/StatusIcon.css +35 -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/build/types/moneyInput/MoneyInput.d.ts +6 -0
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/moneyInput/currencyFormatting.d.ts +4 -3
- package/build/types/moneyInput/currencyFormatting.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
- package/package.json +8 -8
- 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 +47 -9
- package/src/moneyInput/MoneyInput.story.tsx +10 -1
- package/src/moneyInput/MoneyInput.test.story.tsx +141 -1
- package/src/moneyInput/MoneyInput.test.tsx +45 -0
- package/src/moneyInput/MoneyInput.tsx +27 -3
- package/src/moneyInput/currencyFormatting.ts +11 -5
- package/src/sentimentSurface/SentimentSurface.css +1 -1
- package/src/sentimentSurface/SentimentSurface.less +1 -1
- package/src/statusIcon/StatusIcon.css +35 -4
- package/src/statusIcon/StatusIcon.less +35 -4
- package/src/statusIcon/StatusIcon.story.tsx +119 -79
- package/src/statusIcon/StatusIcon.test.story.tsx +125 -0
- package/src/statusIcon/StatusIcon.test.tsx +16 -23
- package/src/statusIcon/StatusIcon.tsx +2 -16
- package/src/switch/Switch.story.tsx +64 -42
- package/src/switch/Switch.test.story.tsx +123 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
4
|
+
|
|
5
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
6
|
+
import CheckboxButton from './CheckboxButton';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
component: CheckboxButton,
|
|
10
|
+
title: 'Actions/CheckboxButton/Tests',
|
|
11
|
+
tags: ['!autodocs', '!manifest'],
|
|
12
|
+
} satisfies Meta<typeof CheckboxButton>;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof CheckboxButton>;
|
|
15
|
+
|
|
16
|
+
/** All checkbox states across all themes. */
|
|
17
|
+
export const Variants: Story = {
|
|
18
|
+
render: function Render() {
|
|
19
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
20
|
+
const [checked, setChecked] = useState(true);
|
|
21
|
+
const [indeterminate, setIndeterminate] = useState(false);
|
|
22
|
+
return (
|
|
23
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', padding: '16px' }}>
|
|
24
|
+
<CheckboxButton
|
|
25
|
+
aria-label="Unchecked"
|
|
26
|
+
checked={unchecked}
|
|
27
|
+
onChange={() => setUnchecked((v) => !v)}
|
|
28
|
+
/>
|
|
29
|
+
<CheckboxButton
|
|
30
|
+
aria-label="Checked"
|
|
31
|
+
checked={checked}
|
|
32
|
+
onChange={() => setChecked((v) => !v)}
|
|
33
|
+
/>
|
|
34
|
+
<CheckboxButton
|
|
35
|
+
aria-label="Indeterminate"
|
|
36
|
+
checked={indeterminate}
|
|
37
|
+
indeterminate
|
|
38
|
+
onChange={() => setIndeterminate((v) => !v)}
|
|
39
|
+
/>
|
|
40
|
+
<CheckboxButton
|
|
41
|
+
aria-label="Disabled unchecked"
|
|
42
|
+
checked={false}
|
|
43
|
+
disabled
|
|
44
|
+
onChange={() => {}}
|
|
45
|
+
/>
|
|
46
|
+
<CheckboxButton aria-label="Disabled checked" checked disabled onChange={() => {}} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `Space` toggles when focused (requires `onChange`). Tab skips disabled checkboxes.
|
|
55
|
+
* Indeterminate resolves to checked on first `Space`, then toggles normally.
|
|
56
|
+
*/
|
|
57
|
+
export const KeyboardInteraction: Story = {
|
|
58
|
+
render: function Render() {
|
|
59
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
60
|
+
const [checked, setChecked] = useState(true);
|
|
61
|
+
const [indeterminate, setIndeterminate] = useState(false);
|
|
62
|
+
const [disabledUnchecked] = useState(false);
|
|
63
|
+
const [disabledChecked] = useState(true);
|
|
64
|
+
|
|
65
|
+
const row: React.CSSProperties = {
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
gap: '8px',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const label: React.CSSProperties = {
|
|
72
|
+
fontSize: '14px',
|
|
73
|
+
color: '#4a5568',
|
|
74
|
+
width: '140px',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
79
|
+
<div style={row}>
|
|
80
|
+
<CheckboxButton
|
|
81
|
+
aria-label="Unchecked"
|
|
82
|
+
checked={unchecked}
|
|
83
|
+
onChange={() => setUnchecked((v: boolean) => !v)}
|
|
84
|
+
/>
|
|
85
|
+
<span style={label}>Unchecked</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div style={row}>
|
|
88
|
+
<CheckboxButton
|
|
89
|
+
aria-label="Checked"
|
|
90
|
+
checked={checked}
|
|
91
|
+
onChange={() => setChecked((v: boolean) => !v)}
|
|
92
|
+
/>
|
|
93
|
+
<span style={label}>Checked</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div style={row}>
|
|
96
|
+
<CheckboxButton
|
|
97
|
+
aria-label="Indeterminate"
|
|
98
|
+
checked={indeterminate}
|
|
99
|
+
indeterminate
|
|
100
|
+
onChange={() => setIndeterminate((v: boolean) => !v)}
|
|
101
|
+
/>
|
|
102
|
+
<span style={label}>Indeterminate</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div style={row}>
|
|
105
|
+
<CheckboxButton
|
|
106
|
+
aria-label="Disabled unchecked"
|
|
107
|
+
checked={disabledUnchecked}
|
|
108
|
+
disabled
|
|
109
|
+
onChange={() => {}}
|
|
110
|
+
/>
|
|
111
|
+
<span style={label}>Disabled unchecked</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div style={row}>
|
|
114
|
+
<CheckboxButton
|
|
115
|
+
aria-label="Disabled checked"
|
|
116
|
+
checked={disabledChecked}
|
|
117
|
+
disabled
|
|
118
|
+
onChange={() => {}}
|
|
119
|
+
/>
|
|
120
|
+
<span style={label}>Disabled checked</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
play: async ({ canvasElement, step }) => {
|
|
126
|
+
const wait = async (ms: number) =>
|
|
127
|
+
new Promise<void>((resolve) => {
|
|
128
|
+
setTimeout(resolve, ms);
|
|
129
|
+
});
|
|
130
|
+
const canvas = within(canvasElement);
|
|
131
|
+
|
|
132
|
+
await step('tab to Unchecked and toggle on then off', async () => {
|
|
133
|
+
await userEvent.tab();
|
|
134
|
+
const cb = canvas.getByRole('checkbox', { name: /^unchecked$/i });
|
|
135
|
+
await expect(cb).toHaveFocus();
|
|
136
|
+
await wait(400);
|
|
137
|
+
await userEvent.keyboard(' ');
|
|
138
|
+
await expect(cb).toBeChecked();
|
|
139
|
+
await wait(400);
|
|
140
|
+
await userEvent.keyboard(' ');
|
|
141
|
+
await expect(cb).not.toBeChecked();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await wait(400);
|
|
145
|
+
|
|
146
|
+
await step('tab to Checked and toggle off then on', async () => {
|
|
147
|
+
await userEvent.tab();
|
|
148
|
+
const cb = canvas.getByRole('checkbox', { name: /^checked$/i });
|
|
149
|
+
await expect(cb).toHaveFocus();
|
|
150
|
+
await wait(400);
|
|
151
|
+
await userEvent.keyboard(' ');
|
|
152
|
+
await expect(cb).not.toBeChecked();
|
|
153
|
+
await wait(400);
|
|
154
|
+
await userEvent.keyboard(' ');
|
|
155
|
+
await expect(cb).toBeChecked();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await wait(400);
|
|
159
|
+
|
|
160
|
+
await step('tab to Indeterminate and toggle on then off', async () => {
|
|
161
|
+
await userEvent.tab();
|
|
162
|
+
const cb = canvas.getByRole('checkbox', { name: /^indeterminate$/i });
|
|
163
|
+
await expect(cb).toHaveFocus();
|
|
164
|
+
await wait(400);
|
|
165
|
+
await userEvent.keyboard(' ');
|
|
166
|
+
await expect(cb).toBeChecked();
|
|
167
|
+
await wait(400);
|
|
168
|
+
await userEvent.keyboard(' ');
|
|
169
|
+
await expect(cb).not.toBeChecked();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await wait(400);
|
|
173
|
+
|
|
174
|
+
await step('tab skips both disabled checkboxes — focus leaves the component', async () => {
|
|
175
|
+
await userEvent.tab();
|
|
176
|
+
const disabledUnchecked = canvas.getByRole('checkbox', { name: /disabled unchecked/i });
|
|
177
|
+
const disabledChecked = canvas.getByRole('checkbox', { name: /disabled checked/i });
|
|
178
|
+
await expect(disabledUnchecked).not.toHaveFocus();
|
|
179
|
+
await expect(disabledChecked).not.toHaveFocus();
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Base checkbox states in right-to-left layout. */
|
|
185
|
+
export const RTL: Story = {
|
|
186
|
+
render: function Render() {
|
|
187
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
188
|
+
const [checked, setChecked] = useState(true);
|
|
189
|
+
const [indeterminate, setIndeterminate] = useState(false);
|
|
190
|
+
return (
|
|
191
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', padding: '16px' }}>
|
|
192
|
+
<CheckboxButton
|
|
193
|
+
aria-label="Unchecked"
|
|
194
|
+
checked={unchecked}
|
|
195
|
+
onChange={() => setUnchecked((v) => !v)}
|
|
196
|
+
/>
|
|
197
|
+
<CheckboxButton
|
|
198
|
+
aria-label="Checked"
|
|
199
|
+
checked={checked}
|
|
200
|
+
onChange={() => setChecked((v) => !v)}
|
|
201
|
+
/>
|
|
202
|
+
<CheckboxButton
|
|
203
|
+
aria-label="Indeterminate"
|
|
204
|
+
checked={indeterminate}
|
|
205
|
+
indeterminate
|
|
206
|
+
onChange={() => setIndeterminate((v) => !v)}
|
|
207
|
+
/>
|
|
208
|
+
<CheckboxButton
|
|
209
|
+
aria-label="Disabled unchecked"
|
|
210
|
+
checked={false}
|
|
211
|
+
disabled
|
|
212
|
+
onChange={() => {}}
|
|
213
|
+
/>
|
|
214
|
+
<CheckboxButton aria-label="Disabled checked" checked disabled onChange={() => {}} />
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
...withVariantConfig(['rtl']),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** Checkbox states at 400% zoom for accessibility testing. */
|
|
222
|
+
export const Zoom400: Story = {
|
|
223
|
+
render: () => (
|
|
224
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', padding: '16px' }}>
|
|
225
|
+
<CheckboxButton aria-label="Unchecked" checked={false} onChange={() => {}} />
|
|
226
|
+
<CheckboxButton aria-label="Checked" checked onChange={() => {}} />
|
|
227
|
+
<CheckboxButton
|
|
228
|
+
aria-label="Indeterminate"
|
|
229
|
+
checked={false}
|
|
230
|
+
indeterminate
|
|
231
|
+
onChange={() => {}}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
),
|
|
235
|
+
...withVariantConfig(['400%']),
|
|
236
|
+
};
|
|
@@ -1,112 +1,151 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
2
3
|
import { useState } from 'react';
|
|
3
4
|
|
|
4
|
-
import Chips, {
|
|
5
|
+
import Chips, { type ChipValue, type ChipsProps } from './Chips';
|
|
6
|
+
import { storySourceWithoutNoise } from '../../.storybook/helpers';
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Can be used for making a <a href="?path=/story/actions-chips--choice">choice</a>, or as a <a href="?path=/story/actions-chips--filter">filter</a> with multiple options.
|
|
10
|
+
*
|
|
11
|
+
* **Design guidance**: <a href="https://wise.design/components/chip" target="_blank">wise.design/components/chip</a>
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
8
14
|
component: Chips,
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
title: 'Actions/Chips',
|
|
16
|
+
args: {
|
|
17
|
+
multiple: false,
|
|
18
|
+
onChange: fn(),
|
|
19
|
+
'aria-label': 'Category filter',
|
|
20
|
+
},
|
|
21
|
+
argTypes: {
|
|
22
|
+
'aria-label': {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'Provides the accessible name for the chip group.',
|
|
25
|
+
},
|
|
26
|
+
className: { table: { category: 'Common' } },
|
|
27
|
+
},
|
|
28
|
+
parameters: {
|
|
29
|
+
docs: { toc: true },
|
|
30
|
+
},
|
|
31
|
+
} satisfies Meta<typeof Chips>;
|
|
11
32
|
|
|
12
|
-
type Story =
|
|
33
|
+
type Story = StoryObj<typeof Chips>;
|
|
13
34
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
selected={selected}
|
|
22
|
-
multiple
|
|
23
|
-
onChange={({ selectedValue, isEnabled }) => {
|
|
24
|
-
if (isEnabled) {
|
|
25
|
-
setSelected([...selected, selectedValue]);
|
|
26
|
-
} else {
|
|
27
|
-
const updatedSelected = selected.filter((value) => selectedValue !== value);
|
|
28
|
-
setSelected(updatedSelected);
|
|
29
|
-
}
|
|
30
|
-
}}
|
|
31
|
-
/>
|
|
32
|
-
);
|
|
33
|
-
};
|
|
35
|
+
const categoryChips = [
|
|
36
|
+
{ value: 'all', label: 'All' },
|
|
37
|
+
{ value: 'accounting', label: 'Accounting' },
|
|
38
|
+
{ value: 'payroll', label: 'Payroll' },
|
|
39
|
+
{ value: 'reporting', label: 'Reporting' },
|
|
40
|
+
{ value: 'payments', label: 'Payments' },
|
|
41
|
+
];
|
|
34
42
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
onChange={({ selectedValue }) => setSelected(selectedValue)}
|
|
42
|
-
/>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
43
|
+
const amountChips = [
|
|
44
|
+
{ value: 100, label: '100 GBP' },
|
|
45
|
+
{ value: 200, label: '200 GBP' },
|
|
46
|
+
{ value: 300, label: '300 GBP' },
|
|
47
|
+
{ value: 500, label: '500 GBP+' },
|
|
48
|
+
];
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
value: 'reporting',
|
|
59
|
-
label: 'Reporting',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
value: 'payments',
|
|
63
|
-
label: 'Payments',
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
};
|
|
50
|
+
/**
|
|
51
|
+
* Toggle `multiple` in the controls panel to switch between filter (multi-select)
|
|
52
|
+
* and choice (single-select) modes.
|
|
53
|
+
*/
|
|
54
|
+
export const Playground: Story = storySourceWithoutNoise<typeof Chips>({
|
|
55
|
+
args: {
|
|
56
|
+
chips: categoryChips,
|
|
57
|
+
multiple: true,
|
|
58
|
+
},
|
|
59
|
+
render: function Render(args: ChipsProps) {
|
|
60
|
+
const [selectedMulti, setSelectedMulti] = useState<readonly ChipValue[]>(['accounting']);
|
|
61
|
+
const [selectedSingle, setSelectedSingle] = useState<ChipValue>('accounting');
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
value: 'payments',
|
|
85
|
-
label: 'Payments',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
selected: ['accounting', 'payments'],
|
|
89
|
-
};
|
|
63
|
+
if (args.multiple) {
|
|
64
|
+
return (
|
|
65
|
+
<Chips
|
|
66
|
+
chips={args.chips}
|
|
67
|
+
multiple
|
|
68
|
+
selected={selectedMulti}
|
|
69
|
+
aria-label={args['aria-label']}
|
|
70
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
71
|
+
setSelectedMulti((prev) =>
|
|
72
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
73
|
+
);
|
|
74
|
+
args.onChange({ selectedValue, isEnabled });
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
80
|
+
return (
|
|
81
|
+
<Chips
|
|
82
|
+
chips={args.chips}
|
|
83
|
+
selected={selectedSingle}
|
|
84
|
+
aria-label={args['aria-label']}
|
|
85
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
86
|
+
setSelectedSingle(selectedValue);
|
|
87
|
+
args.onChange({ selectedValue, isEnabled });
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The user can select any number of chips with the selected prop as an array (`multiple` prop set to `true`). <br />
|
|
96
|
+
* <a href="https://wise.design/components/chip#filter-chips" target="_blank">Design documentation</a>
|
|
97
|
+
*/
|
|
98
|
+
export const Filter: Story = storySourceWithoutNoise<typeof Chips>({
|
|
99
|
+
args: {
|
|
100
|
+
chips: categoryChips,
|
|
101
|
+
},
|
|
102
|
+
argTypes: {
|
|
103
|
+
multiple: { table: { disable: true } },
|
|
104
|
+
},
|
|
105
|
+
render: function Render(args: ChipsProps) {
|
|
106
|
+
const [selected, setSelected] = useState<readonly ChipValue[]>(['accounting', 'payments']);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Chips
|
|
110
|
+
chips={args.chips}
|
|
111
|
+
selected={selected}
|
|
112
|
+
multiple
|
|
113
|
+
aria-label="Category filter"
|
|
114
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
115
|
+
setSelected((prev) =>
|
|
116
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
117
|
+
);
|
|
118
|
+
args.onChange({ selectedValue, isEnabled });
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The user can only select one chip with the selected prop as a single value (`multiple` prop set to `false`). <br />
|
|
127
|
+
* <a href="https://wise.design/components/chip#choice-chips" target="_blank">Design documentation</a>
|
|
128
|
+
*/
|
|
129
|
+
export const Choice: Story = storySourceWithoutNoise<typeof Chips>({
|
|
130
|
+
args: {
|
|
131
|
+
chips: amountChips,
|
|
132
|
+
},
|
|
133
|
+
argTypes: {
|
|
134
|
+
multiple: { table: { disable: true } },
|
|
135
|
+
},
|
|
136
|
+
render: function Render(args: ChipsProps) {
|
|
137
|
+
const [selected, setSelected] = useState<ChipValue>(300);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Chips
|
|
141
|
+
chips={args.chips}
|
|
142
|
+
selected={selected}
|
|
143
|
+
aria-label="Transfer amount"
|
|
144
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
145
|
+
setSelected(selectedValue);
|
|
146
|
+
args.onChange({ selectedValue, isEnabled });
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { userEvent } from 'storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
6
|
+
import Chips, { type ChipValue } from './Chips';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
component: Chips,
|
|
10
|
+
title: 'Actions/Chips/Tests',
|
|
11
|
+
tags: ['!autodocs', '!manifest'],
|
|
12
|
+
} satisfies Meta<typeof Chips>;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof Chips>;
|
|
15
|
+
|
|
16
|
+
const wait = async (ms: number) =>
|
|
17
|
+
new Promise<void>((resolve) => {
|
|
18
|
+
setTimeout(resolve, ms);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const filterChips = [
|
|
22
|
+
{ value: 'all', label: 'All' },
|
|
23
|
+
{ value: 'accounting', label: 'Accounting' },
|
|
24
|
+
{ value: 'payroll', label: 'Payroll' },
|
|
25
|
+
{ value: 'payments', label: 'Payments' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const choiceChips = [
|
|
29
|
+
{ value: 100, label: '100 GBP' },
|
|
30
|
+
{ value: 200, label: '200 GBP' },
|
|
31
|
+
{ value: 300, label: '300 GBP' },
|
|
32
|
+
{ value: 500, label: '500 GBP+' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Filter and choice chips across all themes. */
|
|
36
|
+
export const Variants: Story = {
|
|
37
|
+
render: function Render() {
|
|
38
|
+
const [filterSelected, setFilterSelected] = useState<readonly ChipValue[]>([
|
|
39
|
+
'accounting',
|
|
40
|
+
'payments',
|
|
41
|
+
]);
|
|
42
|
+
const [choiceSelected, setChoiceSelected] = useState<ChipValue>(300);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
46
|
+
<Chips
|
|
47
|
+
chips={filterChips}
|
|
48
|
+
multiple
|
|
49
|
+
selected={filterSelected}
|
|
50
|
+
aria-label="Category filter"
|
|
51
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
52
|
+
setFilterSelected((prev) =>
|
|
53
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
54
|
+
);
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
<Chips
|
|
58
|
+
chips={choiceChips}
|
|
59
|
+
selected={choiceSelected}
|
|
60
|
+
aria-label="Transfer amount"
|
|
61
|
+
onChange={({ selectedValue }) => setChoiceSelected(selectedValue)}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tab moves between chips. Enter or Space toggles selection.
|
|
71
|
+
*/
|
|
72
|
+
export const ChoiceKeyboardInteraction: Story = {
|
|
73
|
+
render: function Render() {
|
|
74
|
+
const [selected, setSelected] = useState<ChipValue>(300);
|
|
75
|
+
return (
|
|
76
|
+
<Chips
|
|
77
|
+
chips={choiceChips}
|
|
78
|
+
selected={selected}
|
|
79
|
+
aria-label="Transfer amount"
|
|
80
|
+
onChange={({ selectedValue }) => setSelected(selectedValue)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
play: async () => {
|
|
85
|
+
await userEvent.tab();
|
|
86
|
+
await wait(400);
|
|
87
|
+
await userEvent.tab();
|
|
88
|
+
await wait(400);
|
|
89
|
+
await userEvent.keyboard('{Enter}');
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Tab moves between chips. Enter or Space toggles selection.
|
|
95
|
+
*/
|
|
96
|
+
export const FilterKeyboardInteraction: Story = {
|
|
97
|
+
render: function Render() {
|
|
98
|
+
const [selected, setSelected] = useState<readonly ChipValue[]>(['accounting']);
|
|
99
|
+
return (
|
|
100
|
+
<Chips
|
|
101
|
+
chips={filterChips}
|
|
102
|
+
multiple
|
|
103
|
+
selected={selected}
|
|
104
|
+
aria-label="Category filter"
|
|
105
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
106
|
+
setSelected((prev) =>
|
|
107
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
108
|
+
);
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
play: async () => {
|
|
114
|
+
await userEvent.tab();
|
|
115
|
+
await wait(400);
|
|
116
|
+
await userEvent.tab();
|
|
117
|
+
await wait(400);
|
|
118
|
+
await userEvent.tab();
|
|
119
|
+
await wait(400);
|
|
120
|
+
await userEvent.keyboard('{Enter}');
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Filter and choice chips in right-to-left layout. */
|
|
125
|
+
export const RTL: Story = {
|
|
126
|
+
render: function Render() {
|
|
127
|
+
const [filterSelected, setFilterSelected] = useState<readonly ChipValue[]>([
|
|
128
|
+
'accounting',
|
|
129
|
+
'payments',
|
|
130
|
+
]);
|
|
131
|
+
const [choiceSelected, setChoiceSelected] = useState<ChipValue>(300);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
135
|
+
<Chips
|
|
136
|
+
chips={filterChips}
|
|
137
|
+
multiple
|
|
138
|
+
selected={filterSelected}
|
|
139
|
+
aria-label="Category filter"
|
|
140
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
141
|
+
setFilterSelected((prev) =>
|
|
142
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
143
|
+
);
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
<Chips
|
|
147
|
+
chips={choiceChips}
|
|
148
|
+
selected={choiceSelected}
|
|
149
|
+
aria-label="Transfer amount"
|
|
150
|
+
onChange={({ selectedValue }) => setChoiceSelected(selectedValue)}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
...withVariantConfig(['rtl']),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** Filter chips at 400% zoom for accessibility testing. */
|
|
159
|
+
export const Zoom400: Story = {
|
|
160
|
+
render: function Render() {
|
|
161
|
+
const [selected, setSelected] = useState<readonly ChipValue[]>(['accounting']);
|
|
162
|
+
return (
|
|
163
|
+
<Chips
|
|
164
|
+
chips={filterChips}
|
|
165
|
+
multiple
|
|
166
|
+
selected={selected}
|
|
167
|
+
aria-label="Category filter"
|
|
168
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
169
|
+
setSelected((prev) =>
|
|
170
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
171
|
+
);
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
...withVariantConfig(['400%']),
|
|
177
|
+
};
|
package/src/chips/Chips.tsx
CHANGED
|
@@ -27,7 +27,7 @@ export type ChipsProps = CommonProps &
|
|
|
27
27
|
}) => void;
|
|
28
28
|
/** Used to manage which chips are selected */
|
|
29
29
|
selected: ChipValue | readonly ChipValue[];
|
|
30
|
-
/**
|
|
30
|
+
/** True turns on Filter-mode, False is Choice */
|
|
31
31
|
multiple?: boolean;
|
|
32
32
|
};
|
|
33
33
|
|