@khanacademy/wonder-blocks-dropdown 2.3.19
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/LICENSE +21 -0
- package/dist/es/index.js +3403 -0
- package/dist/index.js +3966 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +12 -0
- package/package.json +44 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +4054 -0
- package/src/__tests__/generated-snapshot.test.js +1612 -0
- package/src/__tests__/index.test.js +23 -0
- package/src/components/__mocks__/dropdown-core-virtualized.js +40 -0
- package/src/components/__tests__/__snapshots__/action-item.test.js.snap +63 -0
- package/src/components/__tests__/action-item.test.js +43 -0
- package/src/components/__tests__/action-menu.test.js +544 -0
- package/src/components/__tests__/dropdown-core-virtualized.test.js +119 -0
- package/src/components/__tests__/dropdown-core.test.js +659 -0
- package/src/components/__tests__/multi-select.test.js +982 -0
- package/src/components/__tests__/search-text-input.test.js +144 -0
- package/src/components/__tests__/single-select.test.js +588 -0
- package/src/components/action-item.js +270 -0
- package/src/components/action-menu-opener-core.js +203 -0
- package/src/components/action-menu.js +300 -0
- package/src/components/action-menu.md +338 -0
- package/src/components/check.js +59 -0
- package/src/components/checkbox.js +111 -0
- package/src/components/dropdown-core-virtualized-item.js +62 -0
- package/src/components/dropdown-core-virtualized.js +246 -0
- package/src/components/dropdown-core.js +770 -0
- package/src/components/dropdown-opener.js +101 -0
- package/src/components/multi-select.js +597 -0
- package/src/components/multi-select.md +718 -0
- package/src/components/multi-select.stories.js +111 -0
- package/src/components/option-item.js +239 -0
- package/src/components/search-text-input.js +227 -0
- package/src/components/select-opener.js +297 -0
- package/src/components/separator-item.js +50 -0
- package/src/components/single-select.js +418 -0
- package/src/components/single-select.md +520 -0
- package/src/components/single-select.stories.js +107 -0
- package/src/index.js +20 -0
- package/src/util/constants.js +50 -0
- package/src/util/types.js +32 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
### Basic multi select
|
|
2
|
+
|
|
3
|
+
This multi select starts with nothing selected and has no selection shortcuts.
|
|
4
|
+
It also has a set minWidth, and one of the items is disabled.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
8
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
9
|
+
import {StyleSheet} from "aphrodite";
|
|
10
|
+
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
row: {
|
|
13
|
+
flexDirection: "row",
|
|
14
|
+
},
|
|
15
|
+
setWidth: {
|
|
16
|
+
minWidth: 170,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
class ExampleNoneSelected extends React.Component {
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.state = {
|
|
24
|
+
selectedValues: [],
|
|
25
|
+
};
|
|
26
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
27
|
+
this.handleChange = this.handleChange.bind(this);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
handleChange(update) {
|
|
31
|
+
console.log("changes happened!");
|
|
32
|
+
this.setState({
|
|
33
|
+
selectedValues: update,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
return <MultiSelect
|
|
39
|
+
onChange={this.handleChange}
|
|
40
|
+
selectedValues={this.state.selectedValues}
|
|
41
|
+
style={styles.setWidth}
|
|
42
|
+
testId="palette"
|
|
43
|
+
labels={{
|
|
44
|
+
noneSelected: "Color palette",
|
|
45
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} colors`,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<OptionItem label="Red" value="1" testId="red"
|
|
49
|
+
onClick={() => console.log("Roses are red")}
|
|
50
|
+
/>
|
|
51
|
+
<OptionItem label="Yellow" value="2" disabled testId="yellow"/>
|
|
52
|
+
<OptionItem label="Green" value="3" testId="green" />
|
|
53
|
+
<OptionItem label="Blue" value="4" testId="blue" />
|
|
54
|
+
{false && <OptionItem label="Pink" value="5" testId="pink" />}
|
|
55
|
+
</MultiSelect>;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
<View style={styles.row}>
|
|
60
|
+
<ExampleNoneSelected />
|
|
61
|
+
</View>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Multi-select with custom dropdown styling
|
|
65
|
+
|
|
66
|
+
Sometimes, we may want to customize the dropdown style (for example, to limit
|
|
67
|
+
the height of the list), For this purpose, we have the `dropdownStyle` prop.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
71
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
72
|
+
import {StyleSheet} from "aphrodite";
|
|
73
|
+
|
|
74
|
+
const styles = StyleSheet.create({
|
|
75
|
+
row: {
|
|
76
|
+
flexDirection: "row",
|
|
77
|
+
},
|
|
78
|
+
setWidth: {
|
|
79
|
+
minWidth: 170,
|
|
80
|
+
width: "100%",
|
|
81
|
+
},
|
|
82
|
+
customDropdown: {
|
|
83
|
+
maxHeight: 200,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
class ExampleScrolling extends React.Component {
|
|
88
|
+
constructor() {
|
|
89
|
+
super();
|
|
90
|
+
this.state = {
|
|
91
|
+
selectedValues: [],
|
|
92
|
+
};
|
|
93
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
94
|
+
this.handleChange = this.handleChange.bind(this);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
handleChange(update) {
|
|
98
|
+
console.log("changes happened!");
|
|
99
|
+
this.setState({
|
|
100
|
+
selectedValues: update,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
render() {
|
|
105
|
+
return <MultiSelect
|
|
106
|
+
onChange={this.handleChange}
|
|
107
|
+
selectedValues={this.state.selectedValues}
|
|
108
|
+
style={styles.setWidth}
|
|
109
|
+
dropdownStyle={styles.customDropdown}
|
|
110
|
+
labels={{
|
|
111
|
+
noneSelected: "Solar system",
|
|
112
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} planets`,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<OptionItem label="Mercury" value="1" />
|
|
116
|
+
<OptionItem label="Venus" value="2" />
|
|
117
|
+
<OptionItem label="Earth" value="3" disabled />
|
|
118
|
+
<OptionItem label="Mars" value="4" />
|
|
119
|
+
<OptionItem label="Jupiter" value="5" />
|
|
120
|
+
<OptionItem label="Saturn" value="6" />
|
|
121
|
+
<OptionItem label="Neptune" value="7" />
|
|
122
|
+
<OptionItem label="Uranus" value="8" />
|
|
123
|
+
</MultiSelect>;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
<View style={styles.row}>
|
|
128
|
+
<ExampleScrolling />
|
|
129
|
+
</View>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Multi select with select all / select none shortcuts
|
|
133
|
+
|
|
134
|
+
This example starts with one item selected and has selection shortcuts for
|
|
135
|
+
select all and select none. This one does not have a predefined placeholder.
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
139
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
140
|
+
import {StyleSheet} from "aphrodite";
|
|
141
|
+
|
|
142
|
+
const styles = StyleSheet.create({
|
|
143
|
+
row: {
|
|
144
|
+
flexDirection: "row",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
class ExampleWithShortcuts extends React.Component {
|
|
149
|
+
constructor() {
|
|
150
|
+
super();
|
|
151
|
+
this.state = {
|
|
152
|
+
selectedValues: ["wonderblocks 4ever"],
|
|
153
|
+
};
|
|
154
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
155
|
+
this.handleChange = this.handleChange.bind(this);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
handleChange(update) {
|
|
159
|
+
console.log("changes happened!");
|
|
160
|
+
this.setState({
|
|
161
|
+
selectedValues: update,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
render() {
|
|
166
|
+
return <MultiSelect
|
|
167
|
+
shortcuts={true}
|
|
168
|
+
onChange={this.handleChange}
|
|
169
|
+
selectedValues={this.state.selectedValues}
|
|
170
|
+
labels={{
|
|
171
|
+
selectNoneLabel: "Select none",
|
|
172
|
+
selectAllLabel: (numOptions) => `Select all interns (${numOptions})`,
|
|
173
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} interns`,
|
|
174
|
+
allSelected: "All interns selected"
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<OptionItem label="Anesu" value="very mobile" />
|
|
178
|
+
<OptionItem label="Ioana" value="lives in roma" />
|
|
179
|
+
<OptionItem label="Jennie" value="master of dominion" />
|
|
180
|
+
<OptionItem label="Kelsey" value="pipelines and kotlin" />
|
|
181
|
+
<OptionItem label="Mary" value="flow-distress" />
|
|
182
|
+
<OptionItem label="Nisha" value="on the growth boat boat" />
|
|
183
|
+
<OptionItem label="Sophie" value="wonderblocks 4ever" />
|
|
184
|
+
<OptionItem label="Stephanie" value="ramen izakaya fan" />
|
|
185
|
+
<OptionItem label="Yeva" value="boba enthusiast" />
|
|
186
|
+
</MultiSelect>;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
<View style={styles.row}>
|
|
191
|
+
<ExampleWithShortcuts />
|
|
192
|
+
</View>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Multi select in a modal
|
|
196
|
+
|
|
197
|
+
This multi select is in a modal.
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import {StyleSheet} from "aphrodite";
|
|
201
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
202
|
+
import {View, Text} from "@khanacademy/wonder-blocks-core";
|
|
203
|
+
import {OnePaneDialog, ModalLauncher} from "@khanacademy/wonder-blocks-modal";
|
|
204
|
+
import Button from "@khanacademy/wonder-blocks-button";
|
|
205
|
+
|
|
206
|
+
const styles = StyleSheet.create({
|
|
207
|
+
wrapper: {
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
},
|
|
210
|
+
scrolledWrapper: {
|
|
211
|
+
height: 200,
|
|
212
|
+
overflow: "auto",
|
|
213
|
+
border: "1px solid grey",
|
|
214
|
+
borderRadius: 4,
|
|
215
|
+
margin: 10,
|
|
216
|
+
padding: 20,
|
|
217
|
+
},
|
|
218
|
+
setWidth: {
|
|
219
|
+
minWidth: 170,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
class SimpleMultiSelect extends React.Component {
|
|
224
|
+
constructor() {
|
|
225
|
+
super();
|
|
226
|
+
this.state = {
|
|
227
|
+
selectedValues: [],
|
|
228
|
+
};
|
|
229
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
230
|
+
this.handleChange = this.handleChange.bind(this);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
handleChange(update) {
|
|
234
|
+
console.log("changes happened!");
|
|
235
|
+
this.setState({
|
|
236
|
+
selectedValues: update,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
render() {
|
|
241
|
+
return <MultiSelect
|
|
242
|
+
onChange={this.handleChange}
|
|
243
|
+
selectedValues={this.state.selectedValues}
|
|
244
|
+
labels={{
|
|
245
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} great houses`,
|
|
246
|
+
}}
|
|
247
|
+
style={styles.setWidth}
|
|
248
|
+
>
|
|
249
|
+
<OptionItem label="Stark" value="1" />
|
|
250
|
+
<OptionItem label="Arryn" value="2" />
|
|
251
|
+
<OptionItem label="Baratheon" value="3" />
|
|
252
|
+
<OptionItem label="Tully" value="4" />
|
|
253
|
+
<OptionItem label="Greyjoy" value="5" />
|
|
254
|
+
<OptionItem label="Lannister" value="6" />
|
|
255
|
+
<OptionItem label="Tyrell" value="7" />
|
|
256
|
+
<OptionItem label="Martell" value="8" />
|
|
257
|
+
<OptionItem label="Targaryen" value="9" />
|
|
258
|
+
</MultiSelect>;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const modalContent = (
|
|
263
|
+
<View style={{height: "200vh"}}>
|
|
264
|
+
<View style={styles.scrolledWrapper}>
|
|
265
|
+
<View style={{minHeight: "100vh"}}>
|
|
266
|
+
<SimpleMultiSelect />
|
|
267
|
+
</View>
|
|
268
|
+
</View>
|
|
269
|
+
</View>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const modal = (
|
|
273
|
+
<OnePaneDialog
|
|
274
|
+
title="Westerosi modal"
|
|
275
|
+
footer=""
|
|
276
|
+
content={modalContent}
|
|
277
|
+
/>
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
<View style={styles.wrapper}>
|
|
281
|
+
<ModalLauncher modal={modal}>
|
|
282
|
+
{({openModal}) => <Button onClick={openModal}>Open modal!</Button>}
|
|
283
|
+
</ModalLauncher>
|
|
284
|
+
</View>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Empty menus are disabled automatically
|
|
288
|
+
|
|
289
|
+
```js
|
|
290
|
+
import {StyleSheet} from "aphrodite";
|
|
291
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
292
|
+
import {MultiSelect} from "@khanacademy/wonder-blocks-dropdown";
|
|
293
|
+
|
|
294
|
+
const styles = StyleSheet.create({
|
|
295
|
+
row: {
|
|
296
|
+
flexDirection: "row",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
<View style={styles.row}>
|
|
301
|
+
<MultiSelect labels={{noneSelected: "empty"}} />
|
|
302
|
+
</View>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Accessibility
|
|
306
|
+
|
|
307
|
+
If you need to associate this component with another element (e.g. `<label>`),
|
|
308
|
+
make sure to pass the `aria-labelledby` and/or `id` props to the `MultiSelect` component.
|
|
309
|
+
This way, the `opener` will receive this value and it will associate both
|
|
310
|
+
elements.
|
|
311
|
+
|
|
312
|
+
Also, if you need screen readers to understand any relevant information on every
|
|
313
|
+
option item, you can use `aria-label` on each item. e.g. You can use it to let
|
|
314
|
+
screen readers know the current selected/unselected status of the item when it
|
|
315
|
+
receives focus.
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
319
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
320
|
+
import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
|
|
321
|
+
|
|
322
|
+
<View>
|
|
323
|
+
<LabelLarge
|
|
324
|
+
tag="label"
|
|
325
|
+
id="label-for-multi-select"
|
|
326
|
+
htmlFor="unique-multi-select"
|
|
327
|
+
>
|
|
328
|
+
Associated label element
|
|
329
|
+
</LabelLarge>
|
|
330
|
+
<MultiSelect
|
|
331
|
+
aria-labelledby="label-for-multi-select"
|
|
332
|
+
id="unique-multi-select"
|
|
333
|
+
labels={{
|
|
334
|
+
noneSelected: "Accessible MultiSelect",
|
|
335
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} planets`,
|
|
336
|
+
}}
|
|
337
|
+
selectedValues={["one"]}
|
|
338
|
+
>
|
|
339
|
+
<OptionItem label="First element" aria-label="First element, selected" value="one" />
|
|
340
|
+
<OptionItem label="Second element" aria-label="Second element, unselelected" value="two" />
|
|
341
|
+
</MultiSelect>
|
|
342
|
+
</View>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Implicit all enabled Multi select
|
|
346
|
+
|
|
347
|
+
When nothing is selected, show the menu text as "All selected".
|
|
348
|
+
Note that the actual selection logic doesn't change. (Only the menu text)
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
import {StyleSheet} from "aphrodite";
|
|
352
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
353
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
354
|
+
|
|
355
|
+
const styles = StyleSheet.create({
|
|
356
|
+
row: {
|
|
357
|
+
flexDirection: "row",
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
class ImplicitAllEnabledExample extends React.Component {
|
|
362
|
+
constructor() {
|
|
363
|
+
super();
|
|
364
|
+
this.state = {
|
|
365
|
+
selectedValues: [],
|
|
366
|
+
};
|
|
367
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
368
|
+
this.handleChange = this.handleChange.bind(this);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
handleChange(update) {
|
|
372
|
+
console.log("changes happened!");
|
|
373
|
+
this.setState({
|
|
374
|
+
selectedValues: update,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
render() {
|
|
379
|
+
return <MultiSelect
|
|
380
|
+
implicitAllEnabled={true}
|
|
381
|
+
labels={{
|
|
382
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} fruits`,
|
|
383
|
+
allSelected: "All fruits selected",
|
|
384
|
+
}}
|
|
385
|
+
onChange={this.handleChange}
|
|
386
|
+
selectedValues={this.state.selectedValues}
|
|
387
|
+
>
|
|
388
|
+
<OptionItem label="Nectarine" value="nectarine" />
|
|
389
|
+
<OptionItem label="Plum" value="plum" />
|
|
390
|
+
<OptionItem label="Cantaloupe" value="cantaloupe" />
|
|
391
|
+
<OptionItem label="Pineapples" value="pineapples" />
|
|
392
|
+
</MultiSelect>;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
<View style={styles.row}>
|
|
397
|
+
<ImplicitAllEnabledExample />
|
|
398
|
+
</View>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Example: Opening a MultiSelect programmatically
|
|
402
|
+
|
|
403
|
+
Sometimes you'll want to trigger a dropdown programmatically. This can be done by
|
|
404
|
+
setting a value to the `opened` prop (`true` or `false`). In this situation the `MultiSelect` is a
|
|
405
|
+
controlled component. The parent is responsible for managing the opening/closing
|
|
406
|
+
of the dropdown when using this prop.
|
|
407
|
+
|
|
408
|
+
This means that you'll also have to update `opened` to the value triggered by
|
|
409
|
+
the `onToggle` prop.
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
import {OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
413
|
+
import Button from "@khanacademy/wonder-blocks-button";
|
|
414
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
415
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
416
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
417
|
+
import {StyleSheet} from "aphrodite";
|
|
418
|
+
|
|
419
|
+
const styles = StyleSheet.create({
|
|
420
|
+
row: {
|
|
421
|
+
flexDirection: "row",
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
class ControlledMultiSelectExample extends React.Component {
|
|
426
|
+
constructor() {
|
|
427
|
+
super();
|
|
428
|
+
this.state = {
|
|
429
|
+
opened: false,
|
|
430
|
+
selectedValues: [],
|
|
431
|
+
};
|
|
432
|
+
this.handleChange = this.handleChange.bind(this);
|
|
433
|
+
this.handleToggleMenu = this.handleToggleMenu.bind(this);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
handleChange(update) {
|
|
437
|
+
this.setState({
|
|
438
|
+
selectedValues: update,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
handleToggleMenu(opened) {
|
|
443
|
+
this.setState({
|
|
444
|
+
opened,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
render() {
|
|
449
|
+
return (
|
|
450
|
+
<View style={styles.row}>
|
|
451
|
+
<MultiSelect
|
|
452
|
+
labels={{
|
|
453
|
+
noneSelected: "Select one",
|
|
454
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} fruits`,
|
|
455
|
+
allSelected: "All fruits selected",
|
|
456
|
+
}}
|
|
457
|
+
onChange={this.handleChange}
|
|
458
|
+
opened={this.state.opened}
|
|
459
|
+
onToggle={this.handleToggleMenu}
|
|
460
|
+
selectedValues={this.state.selectedValues}
|
|
461
|
+
>
|
|
462
|
+
<OptionItem label="Nectarine" value="nectarine" />
|
|
463
|
+
<OptionItem label="Plum" value="plum" />
|
|
464
|
+
<OptionItem label="Cantaloupe" value="cantaloupe" />
|
|
465
|
+
<OptionItem label="Pineapples" value="pineapples" />
|
|
466
|
+
</MultiSelect>
|
|
467
|
+
<Strut size={Spacing.medium_16} />
|
|
468
|
+
<Button onClick={() => this.handleToggleMenu(true)}>
|
|
469
|
+
Open SingleSelect programatically
|
|
470
|
+
</Button>
|
|
471
|
+
</View>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
<ControlledMultiSelectExample />
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Multi select with search filter
|
|
480
|
+
|
|
481
|
+
When there are many options, you could use a search filter in the MultiSelect.
|
|
482
|
+
The search filter will be performed toward the labels of the option items.
|
|
483
|
+
|
|
484
|
+
*NOTE:* The component automatically uses
|
|
485
|
+
[react-window](https://github.com/bvaughn/react-window) to improve performance
|
|
486
|
+
when rendering these elements and is capable of handling many hundreds of items
|
|
487
|
+
without performance problems.
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
import {MultiSelect, ActionItem, SeparatorItem, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
491
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
492
|
+
import {StyleSheet} from "aphrodite";
|
|
493
|
+
|
|
494
|
+
const styles = StyleSheet.create({
|
|
495
|
+
row: {
|
|
496
|
+
flexDirection: "row",
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const optionItems = new Array(1000).fill(null).map((_, i) => (<OptionItem
|
|
501
|
+
key={i}
|
|
502
|
+
value={(i + 1).toString()}
|
|
503
|
+
label={`School ${i + 1} in Wizarding World Some more really long labels?`}
|
|
504
|
+
/>));
|
|
505
|
+
|
|
506
|
+
class ExampleWithShortcuts extends React.Component {
|
|
507
|
+
constructor() {
|
|
508
|
+
super();
|
|
509
|
+
this.state = {
|
|
510
|
+
selectedValues: [],
|
|
511
|
+
};
|
|
512
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
513
|
+
this.handleChange = this.handleChange.bind(this);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
handleChange(selectedValues) {
|
|
517
|
+
this.setState({selectedValues});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
render() {
|
|
521
|
+
return <MultiSelect
|
|
522
|
+
shortcuts={true}
|
|
523
|
+
isFilterable={true}
|
|
524
|
+
onChange={this.handleChange}
|
|
525
|
+
selectedValues={this.state.selectedValues}
|
|
526
|
+
labels={{
|
|
527
|
+
noneSelected: "Select a school",
|
|
528
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} schools`,
|
|
529
|
+
allSelected: "All schools selected",
|
|
530
|
+
selectAllLabel: (numOptions) => `Select all (${numOptions})`,
|
|
531
|
+
}}
|
|
532
|
+
>
|
|
533
|
+
{optionItems}
|
|
534
|
+
</MultiSelect>;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
<View style={styles.row}>
|
|
539
|
+
<ExampleWithShortcuts />
|
|
540
|
+
</View>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Example: MultiSelect with custom opener
|
|
544
|
+
|
|
545
|
+
In case you need to use a custom opener with the MultiSelect, you can use the
|
|
546
|
+
`opener` property to achieve this. In this example, the `opener` prop accepts a
|
|
547
|
+
function with the following arguments:
|
|
548
|
+
|
|
549
|
+
- `eventState`: lets you customize the style for different states, such as
|
|
550
|
+
`pressed`, `hovered` and `focused`.
|
|
551
|
+
- `text`: Passes the menu label defined in the parent component. By default,
|
|
552
|
+
`text` will be initialized with the value of the `labels.noneSelected` prop
|
|
553
|
+
set in the `MultiSelect` component.
|
|
554
|
+
|
|
555
|
+
**Note:** If you need to use a custom ID for testing the opener, make sure to
|
|
556
|
+
pass the `testId` prop inside the opener component/element.
|
|
557
|
+
|
|
558
|
+
```js
|
|
559
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
560
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
561
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
562
|
+
import {HeadingLarge} from "@khanacademy/wonder-blocks-typography";
|
|
563
|
+
import {StyleSheet} from "aphrodite";
|
|
564
|
+
|
|
565
|
+
const styles = StyleSheet.create({
|
|
566
|
+
focused: {
|
|
567
|
+
color: Color.purple,
|
|
568
|
+
},
|
|
569
|
+
hovered: {
|
|
570
|
+
textDecoration: "underline",
|
|
571
|
+
color: Color.purple,
|
|
572
|
+
cursor: "pointer",
|
|
573
|
+
},
|
|
574
|
+
pressed: {
|
|
575
|
+
color: Color.blue,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
class CustomOpenerExample extends React.Component {
|
|
580
|
+
constructor() {
|
|
581
|
+
super();
|
|
582
|
+
this.state = {
|
|
583
|
+
opened: false,
|
|
584
|
+
selectedValues: [],
|
|
585
|
+
};
|
|
586
|
+
this.handleChange = this.handleChange.bind(this);
|
|
587
|
+
this.handleToggleMenu = this.handleToggleMenu.bind(this);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
handleChange(update) {
|
|
591
|
+
this.setState({
|
|
592
|
+
selectedValues: update,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
handleToggleMenu(opened) {
|
|
597
|
+
this.setState({
|
|
598
|
+
opened,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
render() {
|
|
603
|
+
return (
|
|
604
|
+
<MultiSelect
|
|
605
|
+
labels={{
|
|
606
|
+
noneSelected: "Choose a fruit",
|
|
607
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} fruits`,
|
|
608
|
+
allSelected: "All fruits selected",
|
|
609
|
+
}}
|
|
610
|
+
onChange={this.handleChange}
|
|
611
|
+
opened={this.state.opened}
|
|
612
|
+
onToggle={this.handleToggleMenu}
|
|
613
|
+
selectedValues={this.state.selectedValues}
|
|
614
|
+
testId="multi-select-custom-opener"
|
|
615
|
+
opener={({focused, hovered, pressed, text}) => (
|
|
616
|
+
<HeadingLarge
|
|
617
|
+
onClick={()=>{console.log('custom click!!!!!')}}
|
|
618
|
+
testId="multi-select-custom-opener"
|
|
619
|
+
style={[
|
|
620
|
+
focused && styles.focused,
|
|
621
|
+
hovered && styles.hovered,
|
|
622
|
+
pressed && styles.pressed
|
|
623
|
+
]}
|
|
624
|
+
>
|
|
625
|
+
{text}
|
|
626
|
+
</HeadingLarge>
|
|
627
|
+
)}
|
|
628
|
+
>
|
|
629
|
+
<OptionItem label="Nectarine" value="nectarine" />
|
|
630
|
+
<OptionItem label="Plum" value="plum" />
|
|
631
|
+
<OptionItem label="Cantaloupe" value="cantaloupe" />
|
|
632
|
+
<OptionItem label="Pineapples" value="pineapples" />
|
|
633
|
+
</MultiSelect>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
<CustomOpenerExample />
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Multi select with custom labels
|
|
642
|
+
This example illustrates how you can pass custom labels to the `MultiSelect`
|
|
643
|
+
component.
|
|
644
|
+
|
|
645
|
+
```js static
|
|
646
|
+
labels={|
|
|
647
|
+
clearSearch: string,
|
|
648
|
+
filter: string,
|
|
649
|
+
noResults: string,
|
|
650
|
+
selectAllLabel: (numOptions: number) => string,
|
|
651
|
+
selectNoneLabel: string,
|
|
652
|
+
noneSelected: string,
|
|
653
|
+
someSelected: (numSelectedValues: number) => string,
|
|
654
|
+
allSelected: string,
|
|
655
|
+
|}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
*NOTE:* These labels can be optionally passed using `$Shape`:
|
|
659
|
+
|
|
660
|
+
```js static
|
|
661
|
+
import type {Labels} from "@khanacademy/wonder-blocks-dropdown";
|
|
662
|
+
|
|
663
|
+
// here you can use any or all the keys contained in `Labels`
|
|
664
|
+
const labels: $Shape<Labels> = {
|
|
665
|
+
noneSelected: "0 schools",
|
|
666
|
+
allSelected: "All schools",
|
|
667
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} schools`,
|
|
668
|
+
};
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
```js
|
|
672
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
673
|
+
|
|
674
|
+
const optionItems = new Array(10).fill(null).map((_, i) => (<OptionItem
|
|
675
|
+
key={i}
|
|
676
|
+
value={(i + 1).toString()}
|
|
677
|
+
label={`School ${i + 1} in Wizarding World`}
|
|
678
|
+
/>));
|
|
679
|
+
|
|
680
|
+
class ExampleWithTranslatedValues extends React.Component {
|
|
681
|
+
constructor() {
|
|
682
|
+
super();
|
|
683
|
+
this.state = {
|
|
684
|
+
selectedValues: [],
|
|
685
|
+
};
|
|
686
|
+
// Styleguidist doesn't support arrow functions in class field properties
|
|
687
|
+
this.handleChange = this.handleChange.bind(this);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
handleChange(selectedValues) {
|
|
691
|
+
this.setState({selectedValues});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
render() {
|
|
695
|
+
return <MultiSelect
|
|
696
|
+
shortcuts={true}
|
|
697
|
+
isFilterable={true}
|
|
698
|
+
onChange={this.handleChange}
|
|
699
|
+
selectedValues={this.state.selectedValues}
|
|
700
|
+
labels={{
|
|
701
|
+
clearSearch: "Limpiar busqueda",
|
|
702
|
+
filter: "Filtrar",
|
|
703
|
+
noResults: "Sin resultados",
|
|
704
|
+
selectAllLabel: (numOptions) => `Seleccionar todas (${numOptions})`,
|
|
705
|
+
selectNoneLabel: "No seleccionar ninguno",
|
|
706
|
+
noneSelected: "0 escuelas seleccionadas",
|
|
707
|
+
allSelected: "Todas las escuelas",
|
|
708
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} escuelas seleccionadas`,
|
|
709
|
+
}}
|
|
710
|
+
>
|
|
711
|
+
{optionItems}
|
|
712
|
+
</MultiSelect>;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
<ExampleWithTranslatedValues />
|
|
718
|
+
```
|