@khanacademy/wonder-blocks-dropdown 2.7.4 → 2.8.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/es/index.js +167 -167
  3. package/dist/index.js +389 -360
  4. package/package.json +7 -7
  5. package/src/components/__docs__/action-menu.argtypes.js +44 -0
  6. package/src/components/__docs__/action-menu.stories.js +435 -0
  7. package/src/components/__docs__/base-select.argtypes.js +54 -0
  8. package/src/components/__docs__/multi-select.stories.js +509 -0
  9. package/src/components/__docs__/single-select.accessibility.stories.mdx +59 -0
  10. package/src/components/__docs__/single-select.argtypes.js +54 -0
  11. package/src/components/__docs__/single-select.stories.js +464 -0
  12. package/src/components/__tests__/dropdown-core-virtualized.test.js +0 -15
  13. package/src/components/__tests__/dropdown-core.test.js +113 -209
  14. package/src/components/__tests__/multi-select.test.js +49 -3
  15. package/src/components/__tests__/single-select.test.js +43 -50
  16. package/src/components/action-menu.js +11 -0
  17. package/src/components/dropdown-core-virtualized.js +0 -5
  18. package/src/components/dropdown-core.js +224 -130
  19. package/src/components/multi-select.js +18 -33
  20. package/src/components/single-select.js +16 -30
  21. package/src/util/__tests__/dropdown-menu-styles.test.js +0 -26
  22. package/src/util/__tests__/helpers.test.js +73 -0
  23. package/src/util/constants.js +0 -11
  24. package/src/util/dropdown-menu-styles.js +0 -5
  25. package/src/util/helpers.js +44 -0
  26. package/src/util/types.js +2 -5
  27. package/src/components/__tests__/search-text-input.test.js +0 -212
  28. package/src/components/action-menu.stories.js +0 -48
  29. package/src/components/multi-select.stories.js +0 -124
  30. package/src/components/search-text-input.js +0 -115
  31. package/src/components/single-select.stories.js +0 -247
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  // @flow
2
3
  // A menu that consists of action items
3
4
 
@@ -11,6 +12,7 @@ import Color, {fade} from "@khanacademy/wonder-blocks-color";
11
12
 
12
13
  import Spacing from "@khanacademy/wonder-blocks-spacing";
13
14
  import {addStyle, View} from "@khanacademy/wonder-blocks-core";
15
+ import SearchField from "@khanacademy/wonder-blocks-search-field";
14
16
  import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
15
17
  import {withActionScheduler} from "@khanacademy/wonder-blocks-timing";
16
18
 
@@ -21,10 +23,10 @@ import type {
21
23
  } from "@khanacademy/wonder-blocks-timing";
22
24
  import DropdownCoreVirtualized from "./dropdown-core-virtualized.js";
23
25
  import SeparatorItem from "./separator-item.js";
24
- import SearchTextInput from "./search-text-input.js";
25
- import {defaultLabels, keyCodes, searchInputStyle} from "../util/constants.js";
26
+ import {defaultLabels, keyCodes} from "../util/constants.js";
26
27
  import type {DropdownItem} from "../util/types.js";
27
28
  import DropdownPopper from "./dropdown-popper.js";
29
+ import {debounce, getStringForKey} from "../util/helpers.js";
28
30
  import {
29
31
  generateDropdownMenuStyles,
30
32
  getDropdownMenuHeight,
@@ -45,6 +47,19 @@ const VIRTUALIZE_THRESHOLD = 125;
45
47
  const StyledSpan = addStyle("span");
46
48
 
47
49
  type Labels = {|
50
+ /**
51
+ * Label for describing the dismiss icon on the search filter.
52
+ */
53
+ clearSearch: string,
54
+
55
+ /**
56
+ * Label for the search placeholder.
57
+ */
58
+ filter: string,
59
+
60
+ /**
61
+ * Label for when the filter returns no results.
62
+ */
48
63
  noResults: string,
49
64
  /**
50
65
  * The number total of items available.
@@ -81,6 +96,11 @@ type DefaultProps = {|
81
96
  * use when the item is used on a dark background.
82
97
  */
83
98
  light: boolean,
99
+
100
+ /**
101
+ * Used to determine if we can automatically select an item using the keyboard.
102
+ */
103
+ selectionType: "single" | "multi",
84
104
  |};
85
105
 
86
106
  type DropdownAriaRole = "listbox" | "menu";
@@ -95,15 +115,15 @@ type Props = {|
95
115
 
96
116
  /**
97
117
  * An optional handler to set the searchText of the parent. When this and
98
- * the searchText exist, SearchTextInput will be displayed at the top of
99
- * the dropdown body.
118
+ * the searchText exist, SearchField will be displayed at the top of the
119
+ * dropdown body.
100
120
  */
101
121
  onSearchTextChanged?: ?(searchText: string) => mixed,
102
122
 
103
123
  /**
104
124
  * An optional string that the user entered to search the items. When this
105
- * and the onSearchTextChanged exist, SearchTextInput will be displayed at
106
- * the top of the dropdown body.
125
+ * and the onSearchTextChanged exist, SearchField will be displayed at the
126
+ * top of the dropdown body.
107
127
  */
108
128
  searchText?: ?string,
109
129
 
@@ -149,6 +169,12 @@ type Props = {|
149
169
  */
150
170
  role: DropdownAriaRole,
151
171
 
172
+ /**
173
+ * When this is true, the dropdown body shows a search text input at the
174
+ * top. The items will be filtered by the input.
175
+ */
176
+ isFilterable?: boolean,
177
+
152
178
  ...WithActionSchedulerProps,
153
179
  |};
154
180
 
@@ -159,6 +185,11 @@ type State = {|
159
185
  */
160
186
  itemRefs: Array<{|ref: {|current: any|}, originalIndex: number|}>,
161
187
 
188
+ /**
189
+ * The object containing the custom labels used inside this component.
190
+ */
191
+ labels: Labels,
192
+
162
193
  /**
163
194
  * Because getDerivedStateFromProps doesn't store previous props (in the
164
195
  * spirit of performance), we store the previous items just to be able to
@@ -172,11 +203,6 @@ type State = {|
172
203
  * resetting focusedIndex and focusedOriginalIndex when an update happens.
173
204
  */
174
205
  sameItemsFocusable: boolean,
175
-
176
- /**
177
- * The object containing the custom labels used inside this component.
178
- */
179
- labels: Labels,
180
206
  |};
181
207
 
182
208
  /**
@@ -200,6 +226,10 @@ class DropdownCore extends React.Component<Props, State> {
200
226
  current: null | React.ElementRef<typeof List>,
201
227
  |};
202
228
 
229
+ handleKeyDownDebounced: (key: string) => void;
230
+
231
+ textSuggestion: string;
232
+
203
233
  // Figure out if the same items are focusable. If an item has been added or
204
234
  // removed, this method will return false.
205
235
  static sameItemsFocusable(
@@ -220,10 +250,13 @@ class DropdownCore extends React.Component<Props, State> {
220
250
  static defaultProps: DefaultProps = {
221
251
  alignment: "left",
222
252
  labels: {
253
+ clearSearch: defaultLabels.clearSearch,
254
+ filter: defaultLabels.filter,
223
255
  noResults: defaultLabels.noResults,
224
256
  someSelected: defaultLabels.someSelected,
225
257
  },
226
258
  light: false,
259
+ selectionType: "single",
227
260
  };
228
261
 
229
262
  // This is here to avoid calling React.createRef on each rerender. Instead,
@@ -274,6 +307,15 @@ class DropdownCore extends React.Component<Props, State> {
274
307
  };
275
308
 
276
309
  this.virtualizedListRef = React.createRef();
310
+
311
+ // We debounce the keydown handler to get the ASCII chars because it's
312
+ // called on every keydown
313
+ this.handleKeyDownDebounced = debounce(
314
+ this.handleKeyDownDebounceResult,
315
+ // Leaving enough time for the user to type a valid query (e.g. jul)
316
+ 500,
317
+ );
318
+ this.textSuggestion = "";
277
319
  }
278
320
 
279
321
  componentDidMount() {
@@ -333,31 +375,21 @@ class DropdownCore extends React.Component<Props, State> {
333
375
  this.removeEventListeners();
334
376
  }
335
377
 
336
- hasSearchBox(): boolean {
337
- return (
338
- !!this.props.onSearchTextChanged &&
339
- typeof this.props.searchText === "string"
340
- );
341
- }
378
+ searchFieldRef: {|current: null | HTMLInputElement|} = React.createRef();
342
379
 
343
- // Resets our initial focus index to what was passed in
344
- // via the props
345
- resetFocusedIndex() {
380
+ // Resets our initial focus index to what was passed in via the props
381
+ resetFocusedIndex(): void {
346
382
  const {initialFocusedIndex} = this.props;
347
383
 
348
- // If we are given an initial focus index, select it. Otherwise
349
- // default to the first item
350
- if (initialFocusedIndex) {
351
- // If we have a search box visible, then our focus
352
- // index is going to be offset by 1, since the orginal
353
- // index doesn't account for the search box's
354
- // existence.
355
- if (this.hasSearchBox()) {
356
- this.focusedIndex = initialFocusedIndex + 1;
357
- } else {
358
- this.focusedIndex = initialFocusedIndex;
359
- }
384
+ // If we are given an initial focus index, select it. Otherwise default
385
+ // to the first item
386
+ if (typeof initialFocusedIndex !== "undefined") {
387
+ this.focusedIndex = initialFocusedIndex;
360
388
  } else {
389
+ if (this.hasSearchField() && !this.isSearchFieldFocused()) {
390
+ return this.focusSearchField();
391
+ }
392
+
361
393
  this.focusedIndex = 0;
362
394
  }
363
395
  }
@@ -408,20 +440,26 @@ class DropdownCore extends React.Component<Props, State> {
408
440
  }
409
441
  };
410
442
 
411
- scheduleToFocusCurrentItem() {
443
+ scheduleToFocusCurrentItem(onFocus?: (node: void | HTMLElement) => void) {
412
444
  if (this.shouldVirtualizeList()) {
413
445
  // wait for windowed items to be recalculated
414
- this.props.schedule.animationFrame(() => this.focusCurrentItem());
446
+ this.props.schedule.animationFrame(() => {
447
+ this.focusCurrentItem(onFocus);
448
+ });
415
449
  } else {
416
450
  // immediately focus the current item if we're not virtualizing
417
- this.focusCurrentItem();
451
+ this.focusCurrentItem(onFocus);
418
452
  }
419
453
  }
420
454
 
421
- focusCurrentItem() {
422
- const fousedItemRef = this.state.itemRefs[this.focusedIndex];
455
+ /**
456
+ * Focus on the current item.
457
+ * @param [onFocus] - Callback to be called when the item is focused.
458
+ */
459
+ focusCurrentItem(onFocus?: (node: HTMLElement) => void) {
460
+ const focusedItemRef = this.state.itemRefs[this.focusedIndex];
423
461
 
424
- if (fousedItemRef) {
462
+ if (focusedItemRef) {
425
463
  // force react-window to scroll to ensure the focused item is visible
426
464
  if (this.virtualizedListRef.current) {
427
465
  // Our focused index does not include disabled items, but the
@@ -429,24 +467,50 @@ class DropdownCore extends React.Component<Props, State> {
429
467
  // in the count. So we need to use "originalIndex", which
430
468
  // does account for disabled items.
431
469
  this.virtualizedListRef.current.scrollToItem(
432
- fousedItemRef.originalIndex,
470
+ focusedItemRef.originalIndex,
433
471
  );
434
472
  }
435
473
 
436
474
  const node = ((ReactDOM.findDOMNode(
437
- fousedItemRef.ref.current,
475
+ focusedItemRef.ref.current,
438
476
  ): any): HTMLElement);
439
477
  if (node) {
440
478
  node.focus();
441
479
  // Keep track of the original index of the newly focused item.
442
480
  // To be used if the set of focusable items in the menu changes
443
- this.focusedOriginalIndex = fousedItemRef.originalIndex;
481
+ this.focusedOriginalIndex = focusedItemRef.originalIndex;
482
+
483
+ if (onFocus) {
484
+ // Call the callback with the node that was focused.
485
+ onFocus(node);
486
+ }
444
487
  }
445
488
  }
446
489
  }
447
490
 
448
- focusPreviousItem() {
491
+ focusSearchField() {
492
+ if (this.searchFieldRef.current) {
493
+ this.searchFieldRef.current.focus();
494
+ }
495
+ }
496
+
497
+ hasSearchField(): boolean {
498
+ return !!this.props.isFilterable;
499
+ }
500
+
501
+ isSearchFieldFocused(): boolean {
502
+ return (
503
+ this.hasSearchField() &&
504
+ document.activeElement === this.searchFieldRef.current
505
+ );
506
+ }
507
+
508
+ focusPreviousItem(): void {
449
509
  if (this.focusedIndex === 0) {
510
+ // Move the focus to the search field if it is the first item.
511
+ if (this.hasSearchField() && !this.isSearchFieldFocused()) {
512
+ return this.focusSearchField();
513
+ }
450
514
  this.focusedIndex = this.state.itemRefs.length - 1;
451
515
  } else {
452
516
  this.focusedIndex -= 1;
@@ -455,8 +519,12 @@ class DropdownCore extends React.Component<Props, State> {
455
519
  this.scheduleToFocusCurrentItem();
456
520
  }
457
521
 
458
- focusNextItem() {
522
+ focusNextItem(): void {
459
523
  if (this.focusedIndex === this.state.itemRefs.length - 1) {
524
+ // Move the focus to the search field if it is the last item.
525
+ if (this.hasSearchField() && !this.isSearchFieldFocused()) {
526
+ return this.focusSearchField();
527
+ }
460
528
  this.focusedIndex = 0;
461
529
  } else {
462
530
  this.focusedIndex += 1;
@@ -478,6 +546,15 @@ class DropdownCore extends React.Component<Props, State> {
478
546
  handleKeyDown: (event: SyntheticKeyboardEvent<>) => void = (event) => {
479
547
  const {onOpenChanged, open, searchText} = this.props;
480
548
  const keyCode = event.which || event.keyCode;
549
+
550
+ // Listen for the keydown events if we are using ASCII characters.
551
+ if (getStringForKey(event.key)) {
552
+ event.stopPropagation();
553
+ this.textSuggestion += event.key;
554
+ // Trigger the filter logic only after the debounce is resolved.
555
+ this.handleKeyDownDebounced(this.textSuggestion);
556
+ }
557
+
481
558
  // If menu isn't open and user presses down, open the menu
482
559
  if (!open) {
483
560
  if (keyCode === keyCodes.down) {
@@ -491,24 +568,20 @@ class DropdownCore extends React.Component<Props, State> {
491
568
  // Handle all other key behavior
492
569
  switch (keyCode) {
493
570
  case keyCodes.tab:
494
- // When we show SearchTextInput and that is focused and the
571
+ // When we show SearchField and that is focused and the
495
572
  // searchText is entered at least one character, dismiss button
496
- // is displayed. When user presses tab, we should move focus
497
- // to the dismiss button.
498
- if (
499
- this.hasSearchBox() &&
500
- this.focusedIndex === 0 &&
501
- searchText
502
- ) {
573
+ // is displayed. When user presses tab, we should move focus to
574
+ // the dismiss button.
575
+ if (this.isSearchFieldFocused() && searchText) {
503
576
  return;
504
577
  }
505
578
  this.restoreTabOrder();
506
579
  onOpenChanged(false);
507
580
  return;
508
581
  case keyCodes.space:
509
- // When we display SearchTextInput and the focus is on it,
510
- // we should let the user type space.
511
- if (this.hasSearchBox() && this.focusedIndex === 0) {
582
+ // When we display SearchField and the focus is on it, we should
583
+ // let the user type space.
584
+ if (this.isSearchFieldFocused()) {
512
585
  return;
513
586
  }
514
587
  // Prevent space from scrolling down the page
@@ -531,9 +604,9 @@ class DropdownCore extends React.Component<Props, State> {
531
604
  const keyCode = event.which || event.keyCode;
532
605
  switch (keyCode) {
533
606
  case keyCodes.space:
534
- // When we display SearchTextInput and the focus is on it,
535
- // we should let the user type space.
536
- if (this.hasSearchBox() && this.focusedIndex === 0) {
607
+ // When we display SearchField and the focus is on it, we should
608
+ // let the user type space.
609
+ if (this.isSearchFieldFocused()) {
537
610
  return;
538
611
  }
539
612
  // Prevent space from scrolling down the page
@@ -551,6 +624,45 @@ class DropdownCore extends React.Component<Props, State> {
551
624
  }
552
625
  };
553
626
 
627
+ handleKeyDownDebounceResult: (key: string) => void = (key) => {
628
+ const foundIndex = this.props.items
629
+ .filter((item) => item.focusable)
630
+ .findIndex(({component}) => {
631
+ if (SeparatorItem.isClassOf(component)) {
632
+ return false;
633
+ }
634
+
635
+ // Flow doesn't know that the component is an OptionItem
636
+ // $FlowIgnore[incompatible-use]
637
+ const label = component.props?.label.toLowerCase();
638
+
639
+ return label.startsWith(key.toLowerCase());
640
+ });
641
+
642
+ if (foundIndex >= 0) {
643
+ const isClosed = !this.props.open;
644
+ if (isClosed) {
645
+ // Open the menu to be able to focus on the item that matches
646
+ // the text suggested.
647
+ this.props.onOpenChanged(true);
648
+ }
649
+ // Update the focus reference.
650
+ this.focusedIndex = foundIndex;
651
+
652
+ this.scheduleToFocusCurrentItem((node) => {
653
+ // Force click only if the dropdown is closed and we are using
654
+ // the SingleSelect component.
655
+ if (this.props.selectionType === "single" && isClosed && node) {
656
+ node.click();
657
+ this.props.onOpenChanged(false);
658
+ }
659
+ });
660
+ }
661
+
662
+ // Otherwise, reset current text
663
+ this.textSuggestion = "";
664
+ };
665
+
554
666
  handleClickFocus: (index: number) => void = (index) => {
555
667
  // Turn itemsClicked on so pressing up or down would focus the
556
668
  // appropriate item in handleKeyDown
@@ -588,17 +700,11 @@ class DropdownCore extends React.Component<Props, State> {
588
700
  maybeRenderNoResults(): React.Node {
589
701
  const {
590
702
  items,
591
- onSearchTextChanged,
592
- searchText,
593
703
  labels: {noResults},
594
704
  } = this.props;
595
- const showSearchTextInput =
596
- !!onSearchTextChanged && typeof searchText === "string";
597
-
598
- const includeSearchCount = showSearchTextInput ? 1 : 0;
599
705
 
600
706
  // Verify if there are items to be rendered or not
601
- const numResults = items.length - includeSearchCount;
707
+ const numResults = items.length;
602
708
 
603
709
  if (numResults === 0) {
604
710
  return (
@@ -664,30 +770,6 @@ class DropdownCore extends React.Component<Props, State> {
664
770
  ? this.state.itemRefs[focusIndex].ref
665
771
  : null;
666
772
 
667
- // Render the SearchField component.
668
- if (SearchTextInput.isClassOf(component)) {
669
- return React.cloneElement(component, {
670
- ...populatedProps,
671
-
672
- key: "search-text-input",
673
- // pass the current ref down to the input element
674
- itemRef: currentRef,
675
- // override to avoid losing focus when pressing a key
676
- onClick: () => {
677
- this.handleClickFocus(0);
678
- this.focusCurrentItem();
679
- },
680
- // apply custom styles
681
- style: searchInputStyle,
682
- // TODO(WB-1310): Remove the autofocus prop after making
683
- // the search field sticky.
684
- // Currently autofocusing on the search field to work
685
- // around it losing focus on mount when switching between
686
- // virtualized and non-virtualized dropdown filter results.
687
- autofocus: this.focusedIndex === 0,
688
- });
689
- }
690
-
691
773
  // Render OptionItem and/or ActionItem elements.
692
774
  return React.cloneElement(component, {
693
775
  ...populatedProps,
@@ -721,30 +803,6 @@ class DropdownCore extends React.Component<Props, State> {
721
803
 
722
804
  const focusIndex = focusCounter - 1;
723
805
 
724
- if (SearchTextInput.isClassOf(item.component)) {
725
- return {
726
- ...item,
727
- // override to avoid losing focus when pressing a key
728
- onClick: () => {
729
- this.handleClickFocus(0);
730
- this.focusCurrentItem();
731
- },
732
- populatedProps: {
733
- style: searchInputStyle,
734
- // pass the current ref down to the input element
735
- itemRef: this.state.itemRefs[focusIndex]
736
- ? this.state.itemRefs[focusIndex].ref
737
- : null,
738
- // TODO(WB-1310): Remove the autofocus prop after making
739
- // the search field sticky.
740
- // Currently autofocusing on the search field to work
741
- // around it losing focus on mount when switching between
742
- // virtualized and non-virtualized dropdown filter results.
743
- autofocus: this.focusedIndex === 0,
744
- },
745
- };
746
- }
747
-
748
806
  return {
749
807
  ...item,
750
808
  role: itemRole,
@@ -774,6 +832,32 @@ class DropdownCore extends React.Component<Props, State> {
774
832
  );
775
833
  }
776
834
 
835
+ handleSearchTextChanged: (searchText: string) => void = (
836
+ searchText: string,
837
+ ) => {
838
+ const {onSearchTextChanged} = this.props;
839
+
840
+ if (onSearchTextChanged) {
841
+ onSearchTextChanged(searchText);
842
+ }
843
+ };
844
+
845
+ renderSearchField(): React.Node {
846
+ const {searchText} = this.props;
847
+ const {labels} = this.state;
848
+
849
+ return (
850
+ <SearchField
851
+ clearAriaLabel={labels.clearSearch}
852
+ onChange={this.handleSearchTextChanged}
853
+ placeholder={labels.filter}
854
+ ref={this.searchFieldRef}
855
+ style={styles.searchInputStyle}
856
+ value={searchText || ""}
857
+ />
858
+ );
859
+ }
860
+
777
861
  renderDropdownMenu(
778
862
  listRenderer: React.Node,
779
863
  isReferenceHidden: ?boolean,
@@ -788,33 +872,34 @@ class DropdownCore extends React.Component<Props, State> {
788
872
  ? openerStyle.getPropertyValue("width")
789
873
  : 0;
790
874
 
791
- // Vertical padding of the dropdown menu + borders
792
- const initialHeight = 12;
793
-
794
- const maxDropdownHeight = getDropdownMenuHeight(
795
- this.props.items,
796
- initialHeight,
797
- );
875
+ const maxDropdownHeight = getDropdownMenuHeight(this.props.items);
798
876
 
799
877
  return (
800
878
  <View
801
879
  // Stop propagation to prevent the mouseup listener on the
802
880
  // document from closing the menu.
803
881
  onMouseUp={this.handleDropdownMouseUp}
804
- role={this.props.role}
805
882
  style={[
806
883
  styles.dropdown,
807
884
  light && styles.light,
808
885
  isReferenceHidden && styles.hidden,
809
- generateDropdownMenuStyles(
810
- minDropdownWidth,
811
- maxDropdownHeight,
812
- ),
813
-
814
886
  dropdownStyle,
815
887
  ]}
888
+ testId="dropdown-core-container"
816
889
  >
817
- {listRenderer}
890
+ {this.props.isFilterable && this.renderSearchField()}
891
+ <View
892
+ role={this.props.role}
893
+ style={[
894
+ styles.listboxOrMenu,
895
+ generateDropdownMenuStyles(
896
+ minDropdownWidth,
897
+ maxDropdownHeight,
898
+ ),
899
+ ]}
900
+ >
901
+ {listRenderer}
902
+ </View>
818
903
  {this.maybeRenderNoResults()}
819
904
  </View>
820
905
  );
@@ -850,9 +935,7 @@ class DropdownCore extends React.Component<Props, State> {
850
935
  renderLiveRegion(): React.Node {
851
936
  const {items, open} = this.props;
852
937
  const {labels} = this.state;
853
- const totalItems = this.hasSearchBox()
854
- ? items.length - 1
855
- : items.length;
938
+ const totalItems = items.length;
856
939
 
857
940
  return (
858
941
  <StyledSpan
@@ -897,7 +980,6 @@ const styles = StyleSheet.create({
897
980
  paddingBottom: Spacing.xxxSmall_4,
898
981
  border: `solid 1px ${Color.offBlack16}`,
899
982
  boxShadow: `0px 8px 8px 0px ${fade(Color.offBlack, 0.1)}`,
900
- overflowY: "auto",
901
983
  },
902
984
 
903
985
  light: {
@@ -905,6 +987,10 @@ const styles = StyleSheet.create({
905
987
  border: "none",
906
988
  },
907
989
 
990
+ listboxOrMenu: {
991
+ overflowY: "auto",
992
+ },
993
+
908
994
  hidden: {
909
995
  pointerEvents: "none",
910
996
  visibility: "hidden",
@@ -916,6 +1002,14 @@ const styles = StyleSheet.create({
916
1002
  marginTop: Spacing.xxSmall_6,
917
1003
  },
918
1004
 
1005
+ searchInputStyle: {
1006
+ margin: Spacing.xSmall_8,
1007
+ marginTop: Spacing.xxxSmall_4,
1008
+ // Set `minHeight` to "auto" to stop the search field from having
1009
+ // a height of 0 and being cut off.
1010
+ minHeight: "auto",
1011
+ },
1012
+
919
1013
  srOnly: {
920
1014
  border: 0,
921
1015
  clip: "rect(0,0,0,0)",