@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +3403 -0
  3. package/dist/index.js +3966 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +12 -0
  6. package/package.json +44 -0
  7. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +4054 -0
  8. package/src/__tests__/generated-snapshot.test.js +1612 -0
  9. package/src/__tests__/index.test.js +23 -0
  10. package/src/components/__mocks__/dropdown-core-virtualized.js +40 -0
  11. package/src/components/__tests__/__snapshots__/action-item.test.js.snap +63 -0
  12. package/src/components/__tests__/action-item.test.js +43 -0
  13. package/src/components/__tests__/action-menu.test.js +544 -0
  14. package/src/components/__tests__/dropdown-core-virtualized.test.js +119 -0
  15. package/src/components/__tests__/dropdown-core.test.js +659 -0
  16. package/src/components/__tests__/multi-select.test.js +982 -0
  17. package/src/components/__tests__/search-text-input.test.js +144 -0
  18. package/src/components/__tests__/single-select.test.js +588 -0
  19. package/src/components/action-item.js +270 -0
  20. package/src/components/action-menu-opener-core.js +203 -0
  21. package/src/components/action-menu.js +300 -0
  22. package/src/components/action-menu.md +338 -0
  23. package/src/components/check.js +59 -0
  24. package/src/components/checkbox.js +111 -0
  25. package/src/components/dropdown-core-virtualized-item.js +62 -0
  26. package/src/components/dropdown-core-virtualized.js +246 -0
  27. package/src/components/dropdown-core.js +770 -0
  28. package/src/components/dropdown-opener.js +101 -0
  29. package/src/components/multi-select.js +597 -0
  30. package/src/components/multi-select.md +718 -0
  31. package/src/components/multi-select.stories.js +111 -0
  32. package/src/components/option-item.js +239 -0
  33. package/src/components/search-text-input.js +227 -0
  34. package/src/components/select-opener.js +297 -0
  35. package/src/components/separator-item.js +50 -0
  36. package/src/components/single-select.js +418 -0
  37. package/src/components/single-select.md +520 -0
  38. package/src/components/single-select.stories.js +107 -0
  39. package/src/index.js +20 -0
  40. package/src/util/constants.js +50 -0
  41. 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
+ ```