@transferwise/components 0.0.0-experimental-ff96697 → 0.0.0-experimental-ffbd2fc
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/package.json +1 -1
- package/src/alert/Alert.story.tsx +6 -0
- package/src/button/_stories/Button.story.tsx +5 -0
- package/src/checkboxButton/CheckboxButton.story.tsx +117 -40
- package/src/checkboxButton/CheckboxButton.test.story.tsx +191 -0
- package/src/chips/Chips.story.tsx +177 -88
- package/src/chips/Chips.test.story.tsx +147 -0
- package/src/circularButton/CircularButton.story.tsx +23 -4
- package/src/circularButton/CircularButton.test.story.tsx +119 -1
- package/src/expressiveMoneyInput/ExpressiveMoneyInput.story.tsx +1 -0
- package/src/header/Header.story.tsx +5 -0
- package/src/iconButton/IconButton.story.tsx +45 -1
- package/src/iconButton/IconButton.test.story.tsx +163 -2
- package/src/inputs/SelectInput/_stories/SelectInput.story.tsx +1 -0
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.story.tsx +5 -1
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.story.tsx +5 -1
- package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +5 -1
- package/src/listItem/Button/ListItemButton.story.tsx +5 -1
- package/src/listItem/Checkbox/ListItemCheckbox.story.tsx +5 -1
- package/src/listItem/IconButton/ListItemIconButton.story.tsx +5 -1
- package/src/listItem/Image/ListItemImage.story.tsx +5 -1
- package/src/listItem/Navigation/ListItemNavigation.story.tsx +5 -1
- package/src/listItem/Prompt/ListItemPrompt.story.tsx +5 -1
- package/src/listItem/Radio/ListItemRadio.story.tsx +5 -1
- package/src/listItem/Switch/ListItemSwitch.story.tsx +5 -1
- package/src/listItem/_stories/ListItem.disabled.story.tsx +1 -0
- package/src/listItem/_stories/ListItem.scenarios.story.tsx +1 -0
- package/src/listItem/_stories/ListItem.story.tsx +5 -1
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +5 -0
- package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +5 -0
- package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +5 -0
- package/src/provider/theme/ThemeProvider.story.tsx +0 -8
- package/src/sentimentSurface/SentimentSurface.story.tsx +5 -0
- package/src/switch/Switch.story.tsx +45 -25
- package/src/switch/Switch.test.story.tsx +101 -0
- package/src/tokens/tokens.story.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
1
2
|
import { action } from 'storybook/actions';
|
|
2
3
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
-
import { useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import CheckboxButton from './CheckboxButton';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* A styled checkbox input for selecting zero, one, or multiple items from a list,
|
|
9
|
+
* or confirming agreement to terms. Extends all standard `<input type="checkbox">`
|
|
10
|
+
* attributes and adds an `indeterminate` prop for mixed-state scenarios (e.g. a
|
|
11
|
+
* parent checkbox where only some children are selected). The `indeterminate`
|
|
12
|
+
* state is purely visual — it does not change the underlying `checked` value.
|
|
13
|
+
*
|
|
14
|
+
* Always pair with an accessible label via `aria-label`, `aria-labelledby`, or
|
|
15
|
+
* a visible `<label>` element.
|
|
16
|
+
*
|
|
17
|
+
* Keyboard: `Space` toggles the checkbox when focused.
|
|
18
|
+
*
|
|
19
|
+
* **Design guide**: [wise.design/components/checkbox](https://wise.design/components/checkbox)
|
|
20
|
+
*/
|
|
7
21
|
export default {
|
|
8
22
|
component: CheckboxButton,
|
|
9
23
|
title: 'Actions/CheckboxButton',
|
|
@@ -14,55 +28,118 @@ export default {
|
|
|
14
28
|
onClick: action('click'),
|
|
15
29
|
onFocus: action('focus'),
|
|
16
30
|
},
|
|
31
|
+
parameters: {
|
|
32
|
+
docs: { toc: true },
|
|
33
|
+
},
|
|
17
34
|
} satisfies Meta<typeof CheckboxButton>;
|
|
18
35
|
|
|
19
36
|
type Story = StoryObj<typeof CheckboxButton>;
|
|
20
37
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
/** All five checkbox states — each is interactive. */
|
|
39
|
+
export const Playground: Story = {
|
|
40
|
+
parameters: {
|
|
41
|
+
docs: {
|
|
42
|
+
source: {
|
|
43
|
+
code: `<label>
|
|
44
|
+
<CheckboxButton checked={false} onChange={handleChange} />
|
|
45
|
+
Unchecked
|
|
46
|
+
</label>
|
|
47
|
+
|
|
48
|
+
<label>
|
|
49
|
+
<CheckboxButton checked onChange={handleChange} />
|
|
50
|
+
Checked
|
|
51
|
+
</label>
|
|
52
|
+
|
|
53
|
+
<label>
|
|
54
|
+
<CheckboxButton checked={false} indeterminate onChange={handleChange} />
|
|
55
|
+
Indeterminate
|
|
56
|
+
</label>
|
|
57
|
+
|
|
58
|
+
<label>
|
|
59
|
+
<CheckboxButton checked={false} disabled onChange={handleChange} />
|
|
60
|
+
Disabled unchecked
|
|
61
|
+
</label>
|
|
62
|
+
|
|
63
|
+
<label>
|
|
64
|
+
<CheckboxButton checked disabled onChange={handleChange} />
|
|
65
|
+
Disabled checked
|
|
66
|
+
</label>`,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
24
69
|
},
|
|
25
|
-
render: (
|
|
70
|
+
render: () => {
|
|
71
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
26
72
|
const [checked, setChecked] = useState(true);
|
|
73
|
+
const [indeterminateChecked, setIndeterminateChecked] = useState(false);
|
|
74
|
+
const [isIndeterminate, setIsIndeterminate] = useState(true);
|
|
75
|
+
const [disabledUnchecked] = useState(false);
|
|
76
|
+
const [disabledChecked] = useState(true);
|
|
27
77
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
78
|
+
const labelStyle: React.CSSProperties = {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
gap: '8px',
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
};
|
|
31
84
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* change the `checked` state of the input itself – it remains either checked or unchecked.
|
|
37
|
-
*
|
|
38
|
-
* In the example below the 1st checkbox is unchecked and the 2nd checkbox is checked.
|
|
39
|
-
*/
|
|
40
|
-
export const Indeterminate: Story = {
|
|
41
|
-
args: {
|
|
42
|
-
indeterminate: true,
|
|
43
|
-
},
|
|
44
|
-
render: (args) => {
|
|
45
|
-
const [checked1, setChecked1] = useState<boolean | undefined>(false);
|
|
46
|
-
const [checked2, setChecked2] = useState<boolean | undefined>(true);
|
|
85
|
+
const captionStyle: React.CSSProperties = {
|
|
86
|
+
fontSize: '14px',
|
|
87
|
+
color: '#4a5568',
|
|
88
|
+
};
|
|
47
89
|
|
|
48
90
|
return (
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
92
|
+
<div style={labelStyle}>
|
|
93
|
+
<CheckboxButton
|
|
94
|
+
aria-label="Unchecked"
|
|
95
|
+
checked={unchecked}
|
|
96
|
+
onChange={() => setUnchecked((v) => !v)}
|
|
97
|
+
/>
|
|
98
|
+
<span style={captionStyle}>Unchecked</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div style={labelStyle}>
|
|
102
|
+
<CheckboxButton
|
|
103
|
+
aria-label="Checked"
|
|
104
|
+
checked={checked}
|
|
105
|
+
onChange={() => setChecked((v) => !v)}
|
|
106
|
+
/>
|
|
107
|
+
<span style={captionStyle}>Checked</span>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div style={labelStyle}>
|
|
111
|
+
<CheckboxButton
|
|
112
|
+
aria-label="Indeterminate"
|
|
113
|
+
checked={indeterminateChecked}
|
|
114
|
+
indeterminate={isIndeterminate}
|
|
115
|
+
onChange={() => {
|
|
116
|
+
setIsIndeterminate(false);
|
|
117
|
+
setIndeterminateChecked((v: boolean) => !v);
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
<span style={captionStyle}>Indeterminate</span>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div style={{ ...labelStyle, cursor: 'not-allowed', opacity: 0.6 }}>
|
|
124
|
+
<CheckboxButton
|
|
125
|
+
aria-label="Disabled unchecked"
|
|
126
|
+
checked={disabledUnchecked}
|
|
127
|
+
disabled
|
|
128
|
+
onChange={() => {}}
|
|
129
|
+
/>
|
|
130
|
+
<span style={captionStyle}>Disabled unchecked</span>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div style={{ ...labelStyle, cursor: 'not-allowed', opacity: 0.6 }}>
|
|
134
|
+
<CheckboxButton
|
|
135
|
+
aria-label="Disabled checked"
|
|
136
|
+
checked={disabledChecked}
|
|
137
|
+
disabled
|
|
138
|
+
onChange={() => {}}
|
|
139
|
+
/>
|
|
140
|
+
<span style={captionStyle}>Disabled checked</span>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
66
143
|
);
|
|
67
144
|
},
|
|
68
145
|
};
|
|
@@ -0,0 +1,191 @@
|
|
|
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 { allModes } from '../../.storybook/modes';
|
|
7
|
+
import CheckboxButton from './CheckboxButton';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
component: CheckboxButton,
|
|
11
|
+
title: 'Actions/CheckboxButton/Tests',
|
|
12
|
+
tags: ['!autodocs', '!manifest'],
|
|
13
|
+
} satisfies Meta<typeof CheckboxButton>;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof CheckboxButton>;
|
|
16
|
+
|
|
17
|
+
/** All checkbox states across all themes. */
|
|
18
|
+
export const Variants: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', padding: '16px' }}>
|
|
21
|
+
<CheckboxButton aria-label="Unchecked" checked={false} onChange={() => {}} />
|
|
22
|
+
<CheckboxButton aria-label="Checked" checked onChange={() => {}} />
|
|
23
|
+
<CheckboxButton
|
|
24
|
+
aria-label="Indeterminate"
|
|
25
|
+
checked={false}
|
|
26
|
+
indeterminate
|
|
27
|
+
onChange={() => {}}
|
|
28
|
+
/>
|
|
29
|
+
<CheckboxButton
|
|
30
|
+
aria-label="Disabled unchecked"
|
|
31
|
+
checked={false}
|
|
32
|
+
disabled
|
|
33
|
+
onChange={() => {}}
|
|
34
|
+
/>
|
|
35
|
+
<CheckboxButton aria-label="Disabled checked" checked disabled onChange={() => {}} />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
parameters: {
|
|
39
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
40
|
+
chromatic: {
|
|
41
|
+
dark: allModes.dark,
|
|
42
|
+
brightGreen: allModes.brightGreen,
|
|
43
|
+
forestGreen: allModes.forestGreen,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Checkbox states at 400% zoom for accessibility testing. */
|
|
49
|
+
export const Zoom400: Story = {
|
|
50
|
+
render: () => (
|
|
51
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', padding: '16px' }}>
|
|
52
|
+
<CheckboxButton aria-label="Unchecked" checked={false} onChange={() => {}} />
|
|
53
|
+
<CheckboxButton aria-label="Checked" checked onChange={() => {}} />
|
|
54
|
+
<CheckboxButton
|
|
55
|
+
aria-label="Indeterminate"
|
|
56
|
+
checked={false}
|
|
57
|
+
indeterminate
|
|
58
|
+
onChange={() => {}}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
),
|
|
62
|
+
...withVariantConfig(['400%']),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Tab through all checkbox variants and toggle each on and off. */
|
|
66
|
+
export const KeyboardInteraction: Story = {
|
|
67
|
+
render: function Render() {
|
|
68
|
+
const [unchecked, setUnchecked] = useState(false);
|
|
69
|
+
const [checked, setChecked] = useState(true);
|
|
70
|
+
const [indeterminate, setIndeterminate] = useState(false);
|
|
71
|
+
const [disabledUnchecked] = useState(false);
|
|
72
|
+
const [disabledChecked] = useState(true);
|
|
73
|
+
|
|
74
|
+
const row: React.CSSProperties = {
|
|
75
|
+
display: 'flex',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
gap: '8px',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const label: React.CSSProperties = {
|
|
81
|
+
fontSize: '14px',
|
|
82
|
+
color: '#4a5568',
|
|
83
|
+
width: '140px',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
88
|
+
<div style={row}>
|
|
89
|
+
<CheckboxButton
|
|
90
|
+
aria-label="Unchecked"
|
|
91
|
+
checked={unchecked}
|
|
92
|
+
onChange={() => setUnchecked((v: boolean) => !v)}
|
|
93
|
+
/>
|
|
94
|
+
<span style={label}>Unchecked</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div style={row}>
|
|
97
|
+
<CheckboxButton
|
|
98
|
+
aria-label="Checked"
|
|
99
|
+
checked={checked}
|
|
100
|
+
onChange={() => setChecked((v: boolean) => !v)}
|
|
101
|
+
/>
|
|
102
|
+
<span style={label}>Checked</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div style={row}>
|
|
105
|
+
<CheckboxButton
|
|
106
|
+
aria-label="Indeterminate"
|
|
107
|
+
checked={indeterminate}
|
|
108
|
+
indeterminate
|
|
109
|
+
onChange={() => setIndeterminate((v: boolean) => !v)}
|
|
110
|
+
/>
|
|
111
|
+
<span style={label}>Indeterminate</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div style={row}>
|
|
114
|
+
<CheckboxButton
|
|
115
|
+
aria-label="Disabled unchecked"
|
|
116
|
+
checked={disabledUnchecked}
|
|
117
|
+
disabled
|
|
118
|
+
onChange={() => {}}
|
|
119
|
+
/>
|
|
120
|
+
<span style={label}>Disabled unchecked</span>
|
|
121
|
+
</div>
|
|
122
|
+
<div style={row}>
|
|
123
|
+
<CheckboxButton
|
|
124
|
+
aria-label="Disabled checked"
|
|
125
|
+
checked={disabledChecked}
|
|
126
|
+
disabled
|
|
127
|
+
onChange={() => {}}
|
|
128
|
+
/>
|
|
129
|
+
<span style={label}>Disabled checked</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
play: async ({ canvasElement, step }) => {
|
|
135
|
+
const wait = async (ms: number) =>
|
|
136
|
+
new Promise<void>((resolve) => {
|
|
137
|
+
setTimeout(resolve, ms);
|
|
138
|
+
});
|
|
139
|
+
const canvas = within(canvasElement);
|
|
140
|
+
|
|
141
|
+
await step('tab to Unchecked and toggle on then off', async () => {
|
|
142
|
+
await userEvent.tab();
|
|
143
|
+
const cb = canvas.getByRole('checkbox', { name: /^unchecked$/i });
|
|
144
|
+
await expect(cb).toHaveFocus();
|
|
145
|
+
await wait(400);
|
|
146
|
+
await userEvent.keyboard(' ');
|
|
147
|
+
await expect(cb).toBeChecked();
|
|
148
|
+
await wait(400);
|
|
149
|
+
await userEvent.keyboard(' ');
|
|
150
|
+
await expect(cb).not.toBeChecked();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await wait(400);
|
|
154
|
+
|
|
155
|
+
await step('tab to Checked and toggle off then on', async () => {
|
|
156
|
+
await userEvent.tab();
|
|
157
|
+
const cb = canvas.getByRole('checkbox', { name: /^checked$/i });
|
|
158
|
+
await expect(cb).toHaveFocus();
|
|
159
|
+
await wait(400);
|
|
160
|
+
await userEvent.keyboard(' ');
|
|
161
|
+
await expect(cb).not.toBeChecked();
|
|
162
|
+
await wait(400);
|
|
163
|
+
await userEvent.keyboard(' ');
|
|
164
|
+
await expect(cb).toBeChecked();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await wait(400);
|
|
168
|
+
|
|
169
|
+
await step('tab to Indeterminate and toggle on then off', async () => {
|
|
170
|
+
await userEvent.tab();
|
|
171
|
+
const cb = canvas.getByRole('checkbox', { name: /^indeterminate$/i });
|
|
172
|
+
await expect(cb).toHaveFocus();
|
|
173
|
+
await wait(400);
|
|
174
|
+
await userEvent.keyboard(' ');
|
|
175
|
+
await expect(cb).toBeChecked();
|
|
176
|
+
await wait(400);
|
|
177
|
+
await userEvent.keyboard(' ');
|
|
178
|
+
await expect(cb).not.toBeChecked();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await wait(400);
|
|
182
|
+
|
|
183
|
+
await step('tab skips both disabled checkboxes — focus leaves the component', async () => {
|
|
184
|
+
await userEvent.tab();
|
|
185
|
+
const disabledUnchecked = canvas.getByRole('checkbox', { name: /disabled unchecked/i });
|
|
186
|
+
const disabledChecked = canvas.getByRole('checkbox', { name: /disabled checked/i });
|
|
187
|
+
await expect(disabledUnchecked).not.toHaveFocus();
|
|
188
|
+
await expect(disabledChecked).not.toHaveFocus();
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
};
|
|
@@ -1,112 +1,201 @@
|
|
|
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 } from './Chips';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Chips allow users to filter content or make a choice from a set of options
|
|
9
|
+
* within a compact area. Two modes are supported:
|
|
10
|
+
*
|
|
11
|
+
* - **Filter** (`multiple`): zero or more chips can be active; selected chips
|
|
12
|
+
* gain a dismiss button. Unselected by default.
|
|
13
|
+
* - **Choice** (default): exactly one chip active at a time, like a radio group.
|
|
14
|
+
* Best practice is to pre-select the most common option.
|
|
15
|
+
*
|
|
16
|
+
* Place chips in a horizontal row, typically beneath a search field or above the
|
|
17
|
+
* content they filter. Always use chips in a set — never a single chip on its own.
|
|
18
|
+
*
|
|
19
|
+
* Keyboard: `Tab` moves between chips; `Space` or `Enter` toggles selection.
|
|
20
|
+
*
|
|
21
|
+
* **Design guide**: [wise.design/components/chip](https://wise.design/components/chip)
|
|
22
|
+
*/
|
|
23
|
+
export default {
|
|
8
24
|
component: Chips,
|
|
25
|
+
title: 'Actions/Chips',
|
|
26
|
+
args: {
|
|
27
|
+
onChange: fn(),
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
selected: { table: { disable: true } },
|
|
31
|
+
},
|
|
32
|
+
parameters: {
|
|
33
|
+
docs: { toc: true },
|
|
34
|
+
},
|
|
35
|
+
} satisfies Meta<typeof Chips>;
|
|
36
|
+
|
|
37
|
+
type Story = StoryObj<typeof Chips>;
|
|
38
|
+
|
|
39
|
+
const categoryChips = [
|
|
40
|
+
{ value: 'all', label: 'All' },
|
|
41
|
+
{ value: 'accounting', label: 'Accounting' },
|
|
42
|
+
{ value: 'payroll', label: 'Payroll' },
|
|
43
|
+
{ value: 'reporting', label: 'Reporting' },
|
|
44
|
+
{ value: 'payments', label: 'Payments' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const amountChips = [
|
|
48
|
+
{ value: 100, label: '100 GBP' },
|
|
49
|
+
{ value: 200, label: '200 GBP' },
|
|
50
|
+
{ value: 300, label: '300 GBP' },
|
|
51
|
+
{ value: 500, label: '500 GBP+' },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Toggle `multiple` in the controls panel to switch between filter (multi-select)
|
|
56
|
+
* and choice (single-select) modes.
|
|
57
|
+
*/
|
|
58
|
+
export const Playground: Story = {
|
|
59
|
+
render: function Render({ onChange, chips, multiple }) {
|
|
60
|
+
const [selectedMulti, setSelectedMulti] = useState<readonly ChipValue[]>(['accounting']);
|
|
61
|
+
const [selectedSingle, setSelectedSingle] = useState<ChipValue>('accounting');
|
|
62
|
+
|
|
63
|
+
if (multiple) {
|
|
64
|
+
return (
|
|
65
|
+
<Chips
|
|
66
|
+
chips={chips}
|
|
67
|
+
multiple
|
|
68
|
+
selected={selectedMulti}
|
|
69
|
+
aria-label="Category filter"
|
|
70
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
71
|
+
setSelectedMulti((prev) =>
|
|
72
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
73
|
+
);
|
|
74
|
+
onChange({ selectedValue, isEnabled });
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Chips
|
|
82
|
+
chips={chips}
|
|
83
|
+
selected={selectedSingle}
|
|
84
|
+
aria-label="Category choice"
|
|
85
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
86
|
+
setSelectedSingle(selectedValue);
|
|
87
|
+
onChange({ selectedValue, isEnabled });
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
args: {
|
|
93
|
+
chips: categoryChips,
|
|
94
|
+
multiple: true,
|
|
95
|
+
},
|
|
9
96
|
};
|
|
10
|
-
export default meta;
|
|
11
97
|
|
|
12
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Filter chips allow multiple selections simultaneously. Used to filter a list of
|
|
100
|
+
* results by one or more categories — e.g. an app marketplace or payment list.
|
|
101
|
+
*/
|
|
102
|
+
export const Filter: Story = {
|
|
103
|
+
render: function Render({ onChange, ...args }) {
|
|
104
|
+
const [selected, setSelected] = useState<readonly ChipValue[]>(['accounting', 'payments']);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Chips
|
|
108
|
+
{...args}
|
|
109
|
+
selected={selected}
|
|
110
|
+
multiple
|
|
111
|
+
aria-label="Category filter"
|
|
112
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
113
|
+
setSelected((prev) =>
|
|
114
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue),
|
|
115
|
+
);
|
|
116
|
+
onChange({ selectedValue, isEnabled });
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
args: {
|
|
122
|
+
chips: categoryChips,
|
|
123
|
+
},
|
|
124
|
+
parameters: {
|
|
125
|
+
docs: {
|
|
126
|
+
source: {
|
|
127
|
+
code: `function FilterChips() {
|
|
128
|
+
const [selected, setSelected] = useState(['accounting', 'payments']);
|
|
13
129
|
|
|
14
|
-
const FilterTemplate: Story = (args: ChipsProps) => {
|
|
15
|
-
const [selected, setSelected] = useState<readonly ChipValue[]>(
|
|
16
|
-
args.selected == null || Array.isArray(args.selected) ? args.selected : [args.selected],
|
|
17
|
-
);
|
|
18
130
|
return (
|
|
19
131
|
<Chips
|
|
20
|
-
{
|
|
21
|
-
|
|
132
|
+
chips={[
|
|
133
|
+
{ value: 'all', label: 'All' },
|
|
134
|
+
{ value: 'accounting', label: 'Accounting' },
|
|
135
|
+
{ value: 'payroll', label: 'Payroll' },
|
|
136
|
+
{ value: 'reporting', label: 'Reporting' },
|
|
137
|
+
{ value: 'payments', label: 'Payments' },
|
|
138
|
+
]}
|
|
22
139
|
multiple
|
|
140
|
+
selected={selected}
|
|
141
|
+
aria-label="Category filter"
|
|
23
142
|
onChange={({ selectedValue, isEnabled }) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const updatedSelected = selected.filter((value) => selectedValue !== value);
|
|
28
|
-
setSelected(updatedSelected);
|
|
29
|
-
}
|
|
143
|
+
setSelected((prev) =>
|
|
144
|
+
isEnabled ? [...prev, selectedValue] : prev.filter((v) => v !== selectedValue)
|
|
145
|
+
);
|
|
30
146
|
}}
|
|
31
147
|
/>
|
|
32
148
|
);
|
|
149
|
+
}`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
33
153
|
};
|
|
34
154
|
|
|
35
|
-
|
|
36
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Choice chips behave like a radio group — only one chip can be active at a time.
|
|
157
|
+
* Useful for selecting a single value from a small set, such as a transfer amount.
|
|
158
|
+
*/
|
|
159
|
+
export const Choice: Story = {
|
|
160
|
+
render: function Render({ onChange, ...args }) {
|
|
161
|
+
const [selected, setSelected] = useState<ChipValue>(300);
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Chips
|
|
165
|
+
{...args}
|
|
166
|
+
selected={selected}
|
|
167
|
+
aria-label="Transfer amount"
|
|
168
|
+
onChange={({ selectedValue, isEnabled }) => {
|
|
169
|
+
setSelected(selectedValue);
|
|
170
|
+
onChange({ selectedValue, isEnabled });
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
args: {
|
|
176
|
+
chips: amountChips,
|
|
177
|
+
},
|
|
178
|
+
parameters: {
|
|
179
|
+
docs: {
|
|
180
|
+
source: {
|
|
181
|
+
code: `function ChoiceChips() {
|
|
182
|
+
const [selected, setSelected] = useState(300);
|
|
183
|
+
|
|
37
184
|
return (
|
|
38
185
|
<Chips
|
|
39
|
-
{
|
|
186
|
+
chips={[
|
|
187
|
+
{ value: 100, label: '100 GBP' },
|
|
188
|
+
{ value: 200, label: '200 GBP' },
|
|
189
|
+
{ value: 300, label: '300 GBP' },
|
|
190
|
+
{ value: 500, label: '500 GBP+' },
|
|
191
|
+
]}
|
|
40
192
|
selected={selected}
|
|
193
|
+
aria-label="Transfer amount"
|
|
41
194
|
onChange={({ selectedValue }) => setSelected(selectedValue)}
|
|
42
195
|
/>
|
|
43
196
|
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const FilterChips: typeof FilterTemplate = FilterTemplate.bind({});
|
|
47
|
-
FilterChips.args = {
|
|
48
|
-
chips: [
|
|
49
|
-
{
|
|
50
|
-
value: 'accounting',
|
|
51
|
-
label: 'Accounting',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
value: 'payroll',
|
|
55
|
-
label: 'Payroll',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
value: 'reporting',
|
|
59
|
-
label: 'Reporting',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
value: 'payments',
|
|
63
|
-
label: 'Payments',
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const PreSelectedFilterChips: typeof FilterTemplate = FilterTemplate.bind({});
|
|
69
|
-
PreSelectedFilterChips.args = {
|
|
70
|
-
chips: [
|
|
71
|
-
{
|
|
72
|
-
value: 'accounting',
|
|
73
|
-
label: 'Accounting',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
value: 'payroll',
|
|
77
|
-
label: 'Payroll',
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
value: 'reporting',
|
|
81
|
-
label: 'Reporting',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
value: 'payments',
|
|
85
|
-
label: 'Payments',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
selected: ['accounting', 'payments'],
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export const ChoiceChips: typeof ChoiceTemplate = ChoiceTemplate.bind({});
|
|
92
|
-
ChoiceChips.args = {
|
|
93
|
-
chips: [
|
|
94
|
-
{
|
|
95
|
-
value: 1,
|
|
96
|
-
label: '100 GBP',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
value: 2,
|
|
100
|
-
label: '200 GBP',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
value: 3,
|
|
104
|
-
label: '300 GBP',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
value: 4,
|
|
108
|
-
label: '400 GBP+',
|
|
197
|
+
}`,
|
|
198
|
+
},
|
|
109
199
|
},
|
|
110
|
-
|
|
111
|
-
selected: 3,
|
|
200
|
+
},
|
|
112
201
|
};
|