@khanacademy/wonder-blocks-dropdown 2.6.9 → 2.7.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 2.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9c2580e6: Fixed the search field focus bug in dropdowns
8
+
9
+ ## 2.7.0
10
+
11
+ ### Minor Changes
12
+
13
+ - b3960766: Replaced core elements with Wonder Blocks SearchField (added dependency to SearchField)
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [b3960766]
18
+ - @khanacademy/wonder-blocks-search-field@1.0.4
19
+
20
+ ## 2.6.10
21
+
22
+ ### Patch Changes
23
+
24
+ - 0b94d616: Adds a max-height value to the dropdown menu to avoid displaying long lists that might cut off on small screen resolutions
25
+
3
26
  ## 2.6.9
4
27
 
5
28
  ### Patch Changes
package/dist/es/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import _extends from '@babel/runtime/helpers/extends';
2
2
  import * as React from 'react';
3
- import { StyleSheet, css } from 'aphrodite';
3
+ import { StyleSheet } from 'aphrodite';
4
4
  import { Link } from 'react-router-dom';
5
5
  import { __RouterContext } from 'react-router';
6
6
  import Color, { mix, fade, SemanticColor } from '@khanacademy/wonder-blocks-color';
7
7
  import Spacing from '@khanacademy/wonder-blocks-spacing';
8
- import { LabelMedium, styles as styles$a, LabelLarge } from '@khanacademy/wonder-blocks-typography';
8
+ import { LabelMedium, LabelLarge } from '@khanacademy/wonder-blocks-typography';
9
9
  import { getClickableBehavior, isClientSideUrl, ClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
10
10
  import { addStyle, View } from '@khanacademy/wonder-blocks-core';
11
11
  import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
@@ -13,7 +13,7 @@ import Icon, { icons } from '@khanacademy/wonder-blocks-icon';
13
13
  import ReactDOM from 'react-dom';
14
14
  import { VariableSizeList } from 'react-window';
15
15
  import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
16
- import IconButton from '@khanacademy/wonder-blocks-icon-button';
16
+ import SearchField from '@khanacademy/wonder-blocks-search-field';
17
17
  import { Popper } from 'react-popper';
18
18
  import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
19
19
  import { Strut } from '@khanacademy/wonder-blocks-layout';
@@ -31,14 +31,15 @@ const selectDropdownStyle = {
31
31
  marginBottom: Spacing.xSmall_8
32
32
  };
33
33
  const filterableDropdownStyle = {
34
- minHeight: 100,
35
- maxHeight: 384
34
+ minHeight: 100
36
35
  };
37
36
  const searchInputStyle = {
38
37
  margin: Spacing.xSmall_8,
39
- marginTop: Spacing.xxxSmall_4
38
+ marginTop: Spacing.xxxSmall_4,
39
+ minHeight: "auto"
40
40
  };
41
41
  const DROPDOWN_ITEM_HEIGHT = 40;
42
+ const MAX_VISIBLE_ITEMS = 9;
42
43
  const SEPARATOR_ITEM_HEIGHT = 9;
43
44
  const SEARCH_ITEM_HEIGHT = DROPDOWN_ITEM_HEIGHT + searchInputStyle.margin + searchInputStyle.marginTop;
44
45
  const defaultLabels = {
@@ -93,7 +94,7 @@ class ActionItem extends React.Component {
93
94
  hovered,
94
95
  focused
95
96
  } = state;
96
- const defaultStyle = [styles$9.shared, disabled && styles$9.disabled, !disabled && (pressed ? styles$9.active : (hovered || focused) && styles$9.focus), style];
97
+ const defaultStyle = [styles$8.shared, disabled && styles$8.disabled, !disabled && (pressed ? styles$8.active : (hovered || focused) && styles$8.focus), style];
97
98
 
98
99
  const props = _extends({
99
100
  "data-test-id": testId,
@@ -104,7 +105,7 @@ class ActionItem extends React.Component {
104
105
 
105
106
  const children = React.createElement(React.Fragment, null, React.createElement(LabelMedium, {
106
107
  lang: lang,
107
- style: [indent && styles$9.indent, styles$9.label]
108
+ style: [indent && styles$8.indent, styles$8.label]
108
109
  }, label));
109
110
 
110
111
  if (href && !disabled) {
@@ -135,7 +136,7 @@ ActionItem.defaultProps = {
135
136
  role: "menuitem"
136
137
  };
137
138
  ActionItem.__IS_ACTION_ITEM__ = true;
138
- const styles$9 = StyleSheet.create({
139
+ const styles$8 = StyleSheet.create({
139
140
  shared: {
140
141
  background: white$4,
141
142
  color: offBlack$3,
@@ -189,10 +190,10 @@ function Check(props) {
189
190
  icon: icons.check,
190
191
  size: "small",
191
192
  color: disabled ? offBlack32$3 : pressed || hovered || focused ? white$3 : offBlack$2,
192
- style: [styles$8.bounds, !selected && styles$8.hide]
193
+ style: [styles$7.bounds, !selected && styles$7.hide]
193
194
  });
194
195
  }
195
- const styles$8 = StyleSheet.create({
196
+ const styles$7 = StyleSheet.create({
196
197
  bounds: {
197
198
  minHeight: 16,
198
199
  minWidth: 16
@@ -226,17 +227,17 @@ function Checkbox(props) {
226
227
  const bgColor = disabled ? offWhite : selected && !clickInteraction ? blue$2 : white$2;
227
228
  const checkColor = disabled ? offBlack32$2 : clickInteraction ? pressed ? activeBlue : blue$2 : white$2;
228
229
  return React.createElement(View, {
229
- style: [styles$7.checkbox, (clickInteraction || selected && !disabled) && styles$7.noBorder, disabled && styles$7.disabledCheckbox, {
230
+ style: [styles$6.checkbox, (clickInteraction || selected && !disabled) && styles$6.noBorder, disabled && styles$6.disabledCheckbox, {
230
231
  backgroundColor: bgColor
231
232
  }]
232
233
  }, selected && React.createElement(Icon, {
233
234
  icon: checkboxCheck,
234
235
  size: "small",
235
236
  color: checkColor,
236
- style: [disabled && selected && styles$7.disabledCheckFormatting]
237
+ style: [disabled && selected && styles$6.disabledCheckFormatting]
237
238
  }));
238
239
  }
239
- const styles$7 = StyleSheet.create({
240
+ const styles$6 = StyleSheet.create({
240
241
  checkbox: {
241
242
  minHeight: 16,
242
243
  minWidth: 16,
@@ -314,7 +315,7 @@ class OptionItem extends React.Component {
314
315
  hovered,
315
316
  focused
316
317
  } = state;
317
- const defaultStyle = [styles$6.itemContainer, pressed ? styles$6.active : (hovered || focused) && styles$6.focus, disabled && styles$6.disabled, style];
318
+ const defaultStyle = [styles$5.itemContainer, pressed ? styles$5.active : (hovered || focused) && styles$5.focus, disabled && styles$5.disabled, style];
318
319
  return React.createElement(View, _extends({}, sharedProps, {
319
320
  testId: testId,
320
321
  style: defaultStyle,
@@ -327,7 +328,7 @@ class OptionItem extends React.Component {
327
328
  hovered: hovered,
328
329
  focused: focused
329
330
  }), React.createElement(LabelMedium, {
330
- style: styles$6.label
331
+ style: styles$5.label
331
332
  }, label));
332
333
  });
333
334
  }
@@ -346,7 +347,7 @@ const {
346
347
  offBlack: offBlack$1,
347
348
  offBlack32: offBlack32$1
348
349
  } = Color;
349
- const styles$6 = StyleSheet.create({
350
+ const styles$5 = StyleSheet.create({
350
351
  itemContainer: {
351
352
  flexDirection: "row",
352
353
  background: white$1,
@@ -392,14 +393,14 @@ class SeparatorItem extends React.Component {
392
393
 
393
394
  render() {
394
395
  return React.createElement(View, {
395
- style: [styles$5.separator, this.props.style],
396
+ style: [styles$4.separator, this.props.style],
396
397
  "aria-hidden": "true"
397
398
  });
398
399
  }
399
400
 
400
401
  }
401
402
  SeparatorItem.__IS_SEPARATOR_ITEM__ = true;
402
- const styles$5 = StyleSheet.create({
403
+ const styles$4 = StyleSheet.create({
403
404
  separator: {
404
405
  boxShadow: `0 -1px ${Color.offBlack16}`,
405
406
  height: 1,
@@ -487,110 +488,38 @@ class DropdownVirtualizedItem extends React.Component {
487
488
  }
488
489
 
489
490
  class SearchTextInput extends React.Component {
490
- constructor(...args) {
491
- super(...args);
492
- this.state = {
493
- focused: false,
494
- labels: _extends({
495
- clearSearch: defaultLabels.clearSearch,
496
- filter: defaultLabels.filter
497
- }, this.props.labels)
498
- };
499
-
500
- this.handleChange = e => {
501
- e.preventDefault();
502
- this.props.onChange(e.target.value);
503
- };
504
-
505
- this.handleDismiss = () => {
506
- const {
507
- onClick,
508
- onChange
509
- } = this.props;
510
- onChange("");
511
-
512
- if (onClick) {
513
- onClick();
514
- }
515
- };
516
-
517
- this.handleBlur = e => {
518
- this.setState({
519
- focused: false
520
- });
521
- };
522
-
523
- this.handleFocus = e => {
524
- this.setState({
525
- focused: true
526
- });
527
- };
528
- }
529
-
530
491
  static isClassOf(instance) {
531
492
  return instance && instance.type && instance.type.__IS_SEARCH_TEXT_INPUT__;
532
493
  }
533
494
 
534
- componentDidUpdate(prevProps) {
535
- if (this.props.labels !== prevProps.labels) {
536
- this.setState({
537
- labels: _extends({}, this.state.labels, this.props.labels)
538
- });
539
- }
540
- }
541
-
542
- maybeRenderDismissIconButton() {
543
- const {
544
- searchText
545
- } = this.props;
546
- const {
547
- clearSearch
548
- } = this.state.labels;
495
+ componentDidMount() {
496
+ if (this.props.autofocus) {
497
+ var _this$props$itemRef;
549
498
 
550
- if (searchText.length > 0) {
551
- return React.createElement(IconButton, {
552
- icon: icons.dismiss,
553
- kind: "tertiary",
554
- onClick: this.handleDismiss,
555
- style: styles$4.dismissIcon,
556
- "aria-label": clearSearch
557
- });
499
+ (_this$props$itemRef = this.props.itemRef) == null ? void 0 : _this$props$itemRef.current.focus();
558
500
  }
559
-
560
- return null;
561
501
  }
562
502
 
563
503
  render() {
564
504
  const {
505
+ labels,
506
+ onChange,
565
507
  onClick,
566
508
  itemRef,
567
509
  searchText,
568
510
  style,
569
511
  testId
570
512
  } = this.props;
571
- const {
572
- filter
573
- } = this.state.labels;
574
- return React.createElement(View, {
513
+ return React.createElement(SearchField, {
514
+ clearAriaLabel: labels.clearSearch,
515
+ onChange: onChange,
575
516
  onClick: onClick,
576
- style: [styles$4.inputContainer, this.state.focused && styles$4.focused, style]
577
- }, React.createElement(Icon, {
578
- icon: icons.search,
579
- size: "medium",
580
- color: Color.offBlack64,
581
- style: styles$4.searchIcon,
582
- "aria-hidden": "true"
583
- }), React.createElement("input", {
584
- type: "text",
585
- onChange: this.handleChange,
586
- onFocus: this.handleFocus,
587
- onBlur: this.handleBlur,
517
+ placeholder: labels.filter,
588
518
  ref: itemRef,
589
- placeholder: filter,
590
- value: searchText,
591
- className: css(styles$4.inputStyleReset, styles$a.LabelMedium),
592
- "data-test-id": testId
593
- }), this.maybeRenderDismissIconButton());
519
+ style: style,
520
+ testId: testId,
521
+ value: searchText
522
+ });
594
523
  }
595
524
 
596
525
  }
@@ -601,51 +530,31 @@ SearchTextInput.defaultProps = {
601
530
  }
602
531
  };
603
532
  SearchTextInput.__IS_SEARCH_TEXT_INPUT__ = true;
604
- const styles$4 = StyleSheet.create({
605
- inputContainer: {
606
- flexDirection: "row",
607
- border: `1px solid ${Color.offBlack16}`,
608
- borderRadius: Spacing.xxxSmall_4,
609
- alignItems: "center",
610
- height: DROPDOWN_ITEM_HEIGHT,
611
- minHeight: DROPDOWN_ITEM_HEIGHT
612
- },
613
- focused: {
614
- border: `1px solid ${Color.blue}`
615
- },
616
- searchIcon: {
617
- marginLeft: Spacing.xSmall_8,
618
- marginRight: Spacing.xSmall_8
619
- },
620
- dismissIcon: {
621
- margin: 0,
622
- ":hover": {
623
- border: "none"
624
- }
625
- },
626
- inputStyleReset: {
627
- display: "flex",
628
- flex: 1,
629
- background: "inherit",
630
- border: "none",
631
- outline: "none",
632
- "::placeholder": {
633
- color: Color.offBlack64
634
- },
635
- width: "100%",
636
- color: "inherit"
637
- }
638
- });
639
533
 
640
- const MAX_VISIBLE_ITEMS = 9;
534
+ function getDropdownMenuHeight(items, initialHeight = 0) {
535
+ return items.slice(0, MAX_VISIBLE_ITEMS).reduce((sum, item) => {
536
+ if (SeparatorItem.isClassOf(item.component)) {
537
+ return sum + SEPARATOR_ITEM_HEIGHT;
538
+ } else if (SearchTextInput.isClassOf(item.component)) {
539
+ return sum + SEARCH_ITEM_HEIGHT;
540
+ } else {
541
+ return sum + DROPDOWN_ITEM_HEIGHT;
542
+ }
543
+ }, initialHeight);
544
+ }
545
+ function generateDropdownMenuStyles(minWidth, maxHeight) {
546
+ const styles = StyleSheet.create({
547
+ dropdownMenu: {
548
+ minWidth,
549
+ maxHeight
550
+ }
551
+ });
552
+ return styles.dropdownMenu;
553
+ }
641
554
 
642
555
  class DropdownCoreVirtualized extends React.Component {
643
- constructor(...args) {
644
- super(...args);
645
- this.state = {
646
- height: this.getHeight(),
647
- width: this.props.width
648
- };
556
+ constructor(props) {
557
+ super(props);
649
558
 
650
559
  this.getItemSize = index => {
651
560
  const item = this.props.data[index];
@@ -658,6 +567,11 @@ class DropdownCoreVirtualized extends React.Component {
658
567
  return DROPDOWN_ITEM_HEIGHT;
659
568
  }
660
569
  };
570
+
571
+ this.state = {
572
+ height: getDropdownMenuHeight(props.data),
573
+ width: props.width
574
+ };
661
575
  }
662
576
 
663
577
  componentDidMount() {
@@ -697,24 +611,12 @@ class DropdownCoreVirtualized extends React.Component {
697
611
  }
698
612
 
699
613
  setHeight() {
700
- const height = this.getHeight();
614
+ const height = getDropdownMenuHeight(this.props.data);
701
615
  this.setState({
702
616
  height
703
617
  });
704
618
  }
705
619
 
706
- getHeight() {
707
- return this.props.data.slice(0, MAX_VISIBLE_ITEMS).reduce((sum, item) => {
708
- if (SeparatorItem.isClassOf(item.component)) {
709
- return sum + SEPARATOR_ITEM_HEIGHT;
710
- } else if (SearchTextInput.isClassOf(item.component)) {
711
- return sum + SEARCH_ITEM_HEIGHT;
712
- } else {
713
- return sum + DROPDOWN_ITEM_HEIGHT;
714
- }
715
- }, 0);
716
- }
717
-
718
620
  renderInitialItems() {
719
621
  const {
720
622
  data
@@ -1209,7 +1111,8 @@ class DropdownCore extends React.Component {
1209
1111
  this.handleClickFocus(0);
1210
1112
  this.focusCurrentItem();
1211
1113
  },
1212
- style: searchInputStyle
1114
+ style: searchInputStyle,
1115
+ autofocus: this.focusedIndex === 0
1213
1116
  }));
1214
1117
  }
1215
1118
 
@@ -1242,7 +1145,8 @@ class DropdownCore extends React.Component {
1242
1145
  },
1243
1146
  populatedProps: {
1244
1147
  style: searchInputStyle,
1245
- itemRef: this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null
1148
+ itemRef: this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null,
1149
+ autofocus: this.focusedIndex === 0
1246
1150
  }
1247
1151
  });
1248
1152
  }
@@ -1273,12 +1177,12 @@ class DropdownCore extends React.Component {
1273
1177
  } = this.props;
1274
1178
  const openerStyle = openerElement && window.getComputedStyle(openerElement);
1275
1179
  const minDropdownWidth = openerStyle ? openerStyle.getPropertyValue("width") : 0;
1180
+ const initialHeight = 12;
1181
+ const maxDropdownHeight = getDropdownMenuHeight(this.props.items, initialHeight);
1276
1182
  return React.createElement(View, {
1277
1183
  onMouseUp: this.handleDropdownMouseUp,
1278
1184
  role: this.props.role,
1279
- style: [styles$3.dropdown, light && styles$3.light, isReferenceHidden && styles$3.hidden, {
1280
- minWidth: minDropdownWidth
1281
- }, dropdownStyle]
1185
+ style: [styles$3.dropdown, light && styles$3.light, isReferenceHidden && styles$3.hidden, generateDropdownMenuStyles(minDropdownWidth, maxDropdownHeight), dropdownStyle]
1282
1186
  }, listRenderer, this.maybeRenderNoResults());
1283
1187
  }
1284
1188