@khanacademy/wonder-blocks-dropdown 5.3.5 → 5.3.7

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,26 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 5.3.7
4
+
5
+ ### Patch Changes
6
+
7
+ - f7ff9a77: SingleSelect and MultiSelect: Disable keyboard interactions to open the select if the `disabled` prop is set to `true`. Prevent select from being in an open state if
8
+ `disabled` prop is `true`.
9
+
10
+ ## 5.3.6
11
+
12
+ ### Patch Changes
13
+
14
+ - cc2d8e86: - Update styling for variants of `SingleSelect` and `MultiSelect`. This addresses some styling edge cases around error/disabled states, focus/hover/active states, and light mode. The `not-allowed` cursor is also applied to the disabled state.
15
+ - Styling in the `SelectOpener` (`SingleSelect` and `MultiSelect`) is now applied using css pseudo-classes (`:focus-visible`, `:hover`, `:active`) instead of applying styles using js.
16
+ - Replaced internal use of `ClickableBehaviour` in `SelectOpener` for normalized keyboard interaction behaviour across browsers.
17
+ - 13f49f85: Allow `SingleSelect` and `MultiSelect` to be focusable when disabled. These components now have `aria-disabled` when the `disabled` prop is `true`. This makes it so screenreaders will continue to communicate that the component is disabled, while allowing focus on the disabled component. Focus styling is also added to the disabled state.
18
+ - Updated dependencies [47a758b6]
19
+ - @khanacademy/wonder-blocks-layout@2.1.0
20
+ - @khanacademy/wonder-blocks-cell@3.3.7
21
+ - @khanacademy/wonder-blocks-modal@5.1.4
22
+ - @khanacademy/wonder-blocks-search-field@2.2.15
23
+
3
24
  ## 5.3.5
4
25
 
5
26
  ### Patch Changes
@@ -77,6 +77,10 @@ declare const _default: React.ComponentType<Readonly<{
77
77
  * top. The items will be filtered by the input.
78
78
  */
79
79
  isFilterable?: boolean | undefined;
80
+ /**
81
+ * Whether the dropdown and it's interactions should be disabled.
82
+ */
83
+ disabled?: boolean | undefined;
80
84
  /**
81
85
  * Whether this menu should be left-aligned or right-aligned with the
82
86
  * opener component. Defaults to left-aligned.
@@ -49,13 +49,25 @@ type DefaultProps = {
49
49
  light: SelectOpenerProps["light"];
50
50
  isPlaceholder: SelectOpenerProps["isPlaceholder"];
51
51
  };
52
+ type SelectOpenerState = {
53
+ /**
54
+ * We only keep track of the pressed state to apply styling for when the select
55
+ * opener is pressed using Enter/Space. Other states (active, hover, focus)
56
+ * are not tracked because we use css pseudo-classes to handle those styles
57
+ * instead. Note: `:active` styling is only applied on clicks across browsers,
58
+ * and not on keyboard interaction.
59
+ */
60
+ pressed: boolean;
61
+ };
52
62
  /**
53
63
  * An opener that opens select boxes.
54
64
  */
55
- export default class SelectOpener extends React.Component<SelectOpenerProps> {
65
+ export default class SelectOpener extends React.Component<SelectOpenerProps, SelectOpenerState> {
56
66
  static defaultProps: DefaultProps;
67
+ constructor(props: SelectOpenerProps);
57
68
  handleClick: (e: React.SyntheticEvent) => void;
58
- renderClickableBehavior(router: any): React.ReactNode;
69
+ handleKeyDown: (e: React.KeyboardEvent) => void;
70
+ handleKeyUp: (e: React.KeyboardEvent) => void;
59
71
  render(): React.ReactNode;
60
72
  }
61
73
  export {};
package/dist/es/index.js CHANGED
@@ -9,7 +9,7 @@ import { Strut } from '@khanacademy/wonder-blocks-layout';
9
9
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
10
10
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
11
11
  import * as ReactDOM from 'react-dom';
12
- import { ClickableBehavior, getClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
12
+ import { ClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
13
13
  import SearchField from '@khanacademy/wonder-blocks-search-field';
14
14
  import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
15
15
  import { VariableSizeList } from 'react-window';
@@ -17,7 +17,6 @@ import { Popper } from 'react-popper';
17
17
  import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
18
18
  import { detectOverflow } from '@popperjs/core';
19
19
  import caretDownIcon from '@phosphor-icons/core/bold/caret-down-bold.svg';
20
- import { __RouterContext } from 'react-router';
21
20
 
22
21
  function _extends() {
23
22
  _extends = Object.assign ? Object.assign.bind() : function (target) {
@@ -1275,11 +1274,12 @@ class DropdownCore extends React.Component {
1275
1274
  open,
1276
1275
  opener,
1277
1276
  style,
1278
- className
1277
+ className,
1278
+ disabled
1279
1279
  } = this.props;
1280
1280
  return React.createElement(View, {
1281
- onKeyDown: this.handleKeyDown,
1282
- onKeyUp: this.handleKeyUp,
1281
+ onKeyDown: !disabled ? this.handleKeyDown : undefined,
1282
+ onKeyUp: !disabled ? this.handleKeyUp : undefined,
1283
1283
  style: [styles$4.menuWrapper, style],
1284
1284
  className: className
1285
1285
  }, this.renderLiveRegion(), opener, open && this.renderDropdown());
@@ -1630,16 +1630,37 @@ const styles$2 = StyleSheet.create({
1630
1630
  const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "light", "open", "testId", "onOpenChanged"];
1631
1631
  const StyledButton = addStyle("button");
1632
1632
  class SelectOpener extends React.Component {
1633
- constructor(...args) {
1634
- super(...args);
1633
+ constructor(props) {
1634
+ super(props);
1635
1635
  this.handleClick = e => {
1636
1636
  const {
1637
1637
  open
1638
1638
  } = this.props;
1639
1639
  this.props.onOpenChanged(!open);
1640
1640
  };
1641
+ this.handleKeyDown = e => {
1642
+ const keyCode = e.key;
1643
+ if (keyCode === "Enter" || keyCode === " ") {
1644
+ this.setState({
1645
+ pressed: true
1646
+ });
1647
+ e.preventDefault();
1648
+ }
1649
+ };
1650
+ this.handleKeyUp = e => {
1651
+ const keyCode = e.key;
1652
+ if (keyCode === "Enter" || keyCode === " ") {
1653
+ this.setState({
1654
+ pressed: false
1655
+ });
1656
+ this.handleClick(e);
1657
+ }
1658
+ };
1659
+ this.state = {
1660
+ pressed: false
1661
+ };
1641
1662
  }
1642
- renderClickableBehavior(router) {
1663
+ render() {
1643
1664
  const _this$props = this.props,
1644
1665
  {
1645
1666
  children,
@@ -1652,40 +1673,29 @@ class SelectOpener extends React.Component {
1652
1673
  testId
1653
1674
  } = _this$props,
1654
1675
  sharedProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
1655
- const ClickableBehavior = getClickableBehavior(router);
1656
- return React.createElement(ClickableBehavior, {
1657
- disabled: disabled,
1658
- onClick: this.handleClick
1659
- }, (state, childrenProps) => {
1660
- const stateStyles = _generateStyles(light, isPlaceholder, error);
1661
- const {
1662
- hovered,
1663
- focused,
1664
- pressed
1665
- } = state;
1666
- const iconColor = light ? disabled || pressed ? "currentColor" : tokens.color.white : disabled ? tokens.color.offBlack32 : tokens.color.offBlack64;
1667
- const style = [styles$1.shared, stateStyles.default, disabled && stateStyles.disabled, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus)];
1668
- return React.createElement(StyledButton, _extends({}, sharedProps, {
1669
- "aria-expanded": open ? "true" : "false",
1670
- "aria-haspopup": "listbox",
1671
- "data-testid": testId,
1672
- disabled: disabled,
1673
- id: id,
1674
- style: style,
1675
- type: "button"
1676
- }, childrenProps), React.createElement(LabelMedium, {
1677
- style: styles$1.text
1678
- }, children || "\u00A0"), React.createElement(PhosphorIcon, {
1679
- icon: caretDownIcon,
1680
- color: iconColor,
1681
- size: "small",
1682
- style: styles$1.caret,
1683
- "aria-hidden": "true"
1684
- }));
1685
- });
1686
- }
1687
- render() {
1688
- return React.createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
1676
+ const stateStyles = _generateStyles(light, isPlaceholder, error);
1677
+ const iconColor = light ? disabled || error ? "currentColor" : tokens.color.white : disabled ? tokens.color.offBlack32 : tokens.color.offBlack64;
1678
+ const style = [styles$1.shared, stateStyles.default, disabled && stateStyles.disabled, !disabled && this.state.pressed && stateStyles.pressed];
1679
+ return React.createElement(StyledButton, _extends({}, sharedProps, {
1680
+ "aria-disabled": disabled,
1681
+ "aria-expanded": open ? "true" : "false",
1682
+ "aria-haspopup": "listbox",
1683
+ "data-testid": testId,
1684
+ id: id,
1685
+ style: style,
1686
+ type: "button",
1687
+ onClick: !disabled ? this.handleClick : undefined,
1688
+ onKeyDown: !disabled ? this.handleKeyDown : undefined,
1689
+ onKeyUp: !disabled ? this.handleKeyUp : undefined
1690
+ }), React.createElement(LabelMedium, {
1691
+ style: styles$1.text
1692
+ }, children || "\u00A0"), React.createElement(PhosphorIcon, {
1693
+ icon: caretDownIcon,
1694
+ color: iconColor,
1695
+ size: "small",
1696
+ style: styles$1.caret,
1697
+ "aria-hidden": "true"
1698
+ }));
1689
1699
  }
1690
1700
  }
1691
1701
  SelectOpener.defaultProps = {
@@ -1734,61 +1744,91 @@ const _generateStyles = (light, placeholder, error) => {
1734
1744
  }
1735
1745
  let newStyles = {};
1736
1746
  if (light) {
1747
+ const focusHoverStyling = {
1748
+ borderColor: error ? tokens.color.red : tokens.color.white,
1749
+ borderWidth: tokens.spacing.xxxxSmall_2,
1750
+ paddingLeft: adjustedPaddingLeft,
1751
+ paddingRight: adjustedPaddingRight
1752
+ };
1753
+ const activePressedStyling = {
1754
+ paddingLeft: adjustedPaddingLeft,
1755
+ paddingRight: adjustedPaddingRight,
1756
+ borderColor: error ? tokens.color.red : tokens.color.fadedBlue,
1757
+ borderWidth: tokens.border.width.thin,
1758
+ color: error ? tokens.color.offBlack64 : placeholder ? mix(tokens.color.white32, tokens.color.blue) : tokens.color.fadedBlue,
1759
+ backgroundColor: error ? tokens.color.fadedRed : tokens.color.activeBlue
1760
+ };
1737
1761
  newStyles = {
1738
1762
  default: {
1739
1763
  background: error ? tokens.color.fadedRed8 : "transparent",
1740
- color: placeholder ? tokens.color.white50 : tokens.color.white,
1764
+ color: error ? tokens.color.offBlack64 : placeholder ? tokens.color.white50 : tokens.color.white,
1741
1765
  borderColor: error ? tokens.color.red : tokens.color.white50,
1742
- borderWidth: tokens.border.width.hairline
1743
- },
1744
- focus: {
1745
- borderColor: error ? tokens.color.fadedRed8 : tokens.color.white,
1746
- borderWidth: tokens.spacing.xxxxSmall_2,
1747
- paddingLeft: adjustedPaddingLeft,
1748
- paddingRight: adjustedPaddingRight
1749
- },
1750
- active: {
1751
- paddingLeft: adjustedPaddingLeft,
1752
- paddingRight: adjustedPaddingRight,
1753
- borderColor: error ? tokens.color.red : tokens.color.fadedBlue,
1754
- borderWidth: tokens.border.width.thin,
1755
- color: placeholder ? mix(tokens.color.white32, tokens.color.blue) : tokens.color.fadedBlue,
1756
- backgroundColor: error ? tokens.color.fadedRed : tokens.color.activeBlue
1766
+ borderWidth: tokens.border.width.hairline,
1767
+ ":hover:not([aria-disabled=true])": focusHoverStyling,
1768
+ ["@media not (hover: hover)"]: {
1769
+ ":hover:not([aria-disabled=true])": {
1770
+ borderColor: error ? tokens.color.red : tokens.color.white50,
1771
+ borderWidth: tokens.border.width.hairline,
1772
+ paddingLeft: tokens.spacing.medium_16,
1773
+ paddingRight: tokens.spacing.small_12
1774
+ }
1775
+ },
1776
+ ":focus-visible:not([aria-disabled=true])": focusHoverStyling,
1777
+ ":active:not([aria-disabled=true])": activePressedStyling
1757
1778
  },
1758
1779
  disabled: {
1759
1780
  background: "transparent",
1760
1781
  borderColor: mix(tokens.color.white32, tokens.color.blue),
1761
1782
  color: mix(tokens.color.white32, tokens.color.blue),
1762
- cursor: "auto"
1763
- }
1783
+ cursor: "not-allowed",
1784
+ ":focus-visible": {
1785
+ boxShadow: `0 0 0 1px ${tokens.color.offBlack32}, 0 0 0 3px ${tokens.color.fadedBlue}`
1786
+ }
1787
+ },
1788
+ pressed: activePressedStyling
1764
1789
  };
1765
1790
  } else {
1791
+ const focusHoverStyling = {
1792
+ borderColor: error ? tokens.color.red : tokens.color.blue,
1793
+ borderWidth: tokens.border.width.thin,
1794
+ paddingLeft: adjustedPaddingLeft,
1795
+ paddingRight: adjustedPaddingRight
1796
+ };
1797
+ const activePressedStyling = {
1798
+ background: error ? tokens.color.fadedRed : tokens.color.fadedBlue,
1799
+ borderColor: error ? tokens.color.red : tokens.color.activeBlue,
1800
+ borderWidth: tokens.border.width.thin,
1801
+ paddingLeft: adjustedPaddingLeft,
1802
+ paddingRight: adjustedPaddingRight
1803
+ };
1766
1804
  newStyles = {
1767
1805
  default: {
1768
1806
  background: error ? tokens.color.fadedRed8 : tokens.color.white,
1769
1807
  borderColor: error ? tokens.color.red : tokens.color.offBlack16,
1770
1808
  borderWidth: tokens.border.width.hairline,
1771
- color: placeholder ? tokens.color.offBlack64 : tokens.color.offBlack
1772
- },
1773
- focus: {
1774
- borderColor: error ? tokens.color.red : tokens.color.blue,
1775
- borderWidth: tokens.border.width.thin,
1776
- paddingLeft: adjustedPaddingLeft,
1777
- paddingRight: adjustedPaddingRight
1778
- },
1779
- active: {
1780
- background: error ? tokens.color.fadedRed : tokens.color.fadedBlue,
1781
- borderColor: error ? tokens.color.red : tokens.color.activeBlue,
1782
- borderWidth: tokens.border.width.thin,
1783
- paddingLeft: adjustedPaddingLeft,
1784
- paddingRight: adjustedPaddingRight
1809
+ color: placeholder ? tokens.color.offBlack64 : tokens.color.offBlack,
1810
+ ":hover:not([aria-disabled=true])": focusHoverStyling,
1811
+ ["@media not (hover: hover)"]: {
1812
+ ":hover:not([aria-disabled=true])": {
1813
+ borderColor: error ? tokens.color.red : tokens.color.offBlack16,
1814
+ borderWidth: tokens.border.width.hairline,
1815
+ paddingLeft: tokens.spacing.medium_16,
1816
+ paddingRight: tokens.spacing.small_12
1817
+ }
1818
+ },
1819
+ ":focus-visible:not([aria-disabled=true])": focusHoverStyling,
1820
+ ":active:not([aria-disabled=true])": activePressedStyling
1785
1821
  },
1786
1822
  disabled: {
1787
1823
  background: tokens.color.offWhite,
1788
1824
  borderColor: tokens.color.offBlack16,
1789
1825
  color: tokens.color.offBlack64,
1790
- cursor: "auto"
1791
- }
1826
+ cursor: "not-allowed",
1827
+ ":focus-visible": {
1828
+ boxShadow: `0 0 0 1px ${tokens.color.white}, 0 0 0 3px ${tokens.color.offBlack32}`
1829
+ }
1830
+ },
1831
+ pressed: activePressedStyling
1792
1832
  };
1793
1833
  }
1794
1834
  stateStyles[styleKey] = StyleSheet.create(newStyles);
@@ -1874,7 +1914,7 @@ class SingleSelect extends React.Component {
1874
1914
  }
1875
1915
  static getDerivedStateFromProps(props, state) {
1876
1916
  return {
1877
- open: typeof props.opened === "boolean" ? props.opened : state.open
1917
+ open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
1878
1918
  };
1879
1919
  }
1880
1920
  filterChildren(children) {
@@ -1941,7 +1981,8 @@ class SingleSelect extends React.Component {
1941
1981
  light,
1942
1982
  style,
1943
1983
  "aria-invalid": ariaInvalid,
1944
- "aria-required": ariaRequired
1984
+ "aria-required": ariaRequired,
1985
+ disabled
1945
1986
  } = this.props;
1946
1987
  const {
1947
1988
  searchText
@@ -1970,7 +2011,8 @@ class SingleSelect extends React.Component {
1970
2011
  searchText: isFilterable ? searchText : "",
1971
2012
  labels: labels,
1972
2013
  "aria-invalid": ariaInvalid,
1973
- "aria-required": ariaRequired
2014
+ "aria-required": ariaRequired,
2015
+ disabled: disabled
1974
2016
  });
1975
2017
  }
1976
2018
  }
@@ -2074,7 +2116,7 @@ class MultiSelect extends React.Component {
2074
2116
  }
2075
2117
  static getDerivedStateFromProps(props, state) {
2076
2118
  return {
2077
- open: typeof props.opened === "boolean" ? props.opened : state.open
2119
+ open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
2078
2120
  };
2079
2121
  }
2080
2122
  componentDidUpdate(prevProps) {
@@ -2239,7 +2281,8 @@ class MultiSelect extends React.Component {
2239
2281
  children,
2240
2282
  isFilterable,
2241
2283
  "aria-invalid": ariaInvalid,
2242
- "aria-required": ariaRequired
2284
+ "aria-required": ariaRequired,
2285
+ disabled
2243
2286
  } = this.props;
2244
2287
  const {
2245
2288
  open,
@@ -2278,7 +2321,8 @@ class MultiSelect extends React.Component {
2278
2321
  someResults: someSelected
2279
2322
  },
2280
2323
  "aria-invalid": ariaInvalid,
2281
- "aria-required": ariaRequired
2324
+ "aria-required": ariaRequired,
2325
+ disabled: disabled
2282
2326
  });
2283
2327
  }
2284
2328
  }