@khanacademy/wonder-blocks-dropdown 2.4.4 → 2.6.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.
@@ -2,12 +2,15 @@
2
2
  import * as React from "react";
3
3
  import * as ReactDOM from "react-dom";
4
4
  import {mount} from "enzyme";
5
+ import "jest-enzyme";
5
6
 
6
7
  import OptionItem from "../option-item.js";
7
8
  import SearchTextInput from "../search-text-input.js";
8
9
  import DropdownCore from "../dropdown-core.js";
9
10
  import {keyCodes} from "../../util/constants.js";
10
11
 
12
+ import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
13
+
11
14
  jest.mock("../dropdown-core-virtualized.js");
12
15
 
13
16
  const elementAtIndex = (wrapper, index) =>
@@ -87,6 +90,12 @@ describe("DropdownCore", () => {
87
90
  });
88
91
 
89
92
  afterEach(() => {
93
+ // We have to explicitly unmount before clearing mocks, otherwise jest
94
+ // timers will throw because we'll try to clear an animation frame that
95
+ // we set with a setTimeout but are clearing with clearAnimationFrame
96
+ // because we restored the clearAnimationFrame mock (and we won't
97
+ // have cleared the timeout we actually set!)
98
+ unmountAll();
90
99
  jest.restoreAllMocks();
91
100
  });
92
101
 
@@ -2,6 +2,7 @@
2
2
  //@flow
3
3
  import * as React from "react";
4
4
  import {mount} from "enzyme";
5
+ import "jest-enzyme";
5
6
 
6
7
  import {ClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
7
8
  import IconButton from "@khanacademy/wonder-blocks-icon-button";
@@ -14,6 +15,8 @@ import MultiSelect from "../multi-select.js";
14
15
  import {keyCodes} from "../../util/constants.js";
15
16
  import SearchTextInput from "../search-text-input.js";
16
17
 
18
+ import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
19
+
17
20
  import type {Labels} from "../multi-select.js";
18
21
 
19
22
  jest.useFakeTimers();
@@ -68,6 +71,12 @@ describe("MultiSelect", () => {
68
71
  });
69
72
 
70
73
  afterEach(() => {
74
+ // We have to explicitly unmount before clearing mocks, otherwise jest
75
+ // timers will throw because we'll try to clear an animation frame that
76
+ // we set with a setTimeout but are clearing with clearAnimationFrame
77
+ // because we restored the clearAnimationFrame mock (and we won't
78
+ // have cleared the timeout we actually set!)
79
+ unmountAll();
71
80
  jest.restoreAllMocks();
72
81
  });
73
82
 
@@ -1,6 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {shallow} from "enzyme";
4
+ import "jest-enzyme";
4
5
 
5
6
  import SearchTextInput from "../search-text-input.js";
6
7
 
@@ -1,8 +1,6 @@
1
1
  //@flow
2
2
  import * as React from "react";
3
3
  import {render, screen} from "@testing-library/react";
4
- // eslint-disable-next-line import/no-unassigned-import
5
- import "@testing-library/jest-dom/extend-expect";
6
4
 
7
5
  import OptionItem from "../option-item.js";
8
6
  import SingleSelect from "../single-select.js";
@@ -25,8 +23,7 @@ describe("SingleSelect", () => {
25
23
  afterEach(() => {
26
24
  window.scrollTo.mockClear();
27
25
  onChange.mockReset();
28
- // $FlowIgnore[prop-missing]: Flow doesn't understand that we're mocking console.error
29
- console.error.mockReset(); // eslint-disable-line no-console
26
+ jest.spyOn(console, "error").mockReset();
30
27
  });
31
28
 
32
29
  afterEach(() => {
@@ -52,7 +49,7 @@ describe("SingleSelect", () => {
52
49
  userEvent.click(opener);
53
50
 
54
51
  // Assert
55
- expect(screen.queryByRole("listbox")).toBeInTheDocument();
52
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
56
53
  });
57
54
 
58
55
  it("should focus the first item in the dropdown", () => {
@@ -121,9 +118,7 @@ describe("SingleSelect", () => {
121
118
  userEvent.keyboard(key);
122
119
 
123
120
  // Assert
124
- expect(
125
- screen.queryByRole("listbox"),
126
- ).toBeInTheDocument();
121
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
127
122
  });
128
123
 
129
124
  it("should focus the first item in the dropdown", () => {
@@ -152,7 +147,7 @@ describe("SingleSelect", () => {
152
147
 
153
148
  // Assert
154
149
  expect(onChange).not.toHaveBeenCalled();
155
- expect(screen.queryByRole("listbox")).toBeInTheDocument();
150
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
156
151
  });
157
152
 
158
153
  it("should select an item when pressing {space}", () => {
@@ -278,7 +273,7 @@ describe("SingleSelect", () => {
278
273
  userEvent.click(screen.getByText("Choose"));
279
274
 
280
275
  // Assert
281
- expect(screen.queryByRole("listbox")).toBeInTheDocument();
276
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
282
277
  });
283
278
 
284
279
  it("closes the menu when the anchor is clicked", () => {
@@ -321,7 +316,7 @@ describe("SingleSelect", () => {
321
316
  userEvent.click(opener);
322
317
 
323
318
  // Assert
324
- expect(screen.queryByRole("listbox")).toBeInTheDocument();
319
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
325
320
  });
326
321
 
327
322
  it("calls the custom onClick handler", () => {
@@ -3,7 +3,7 @@
3
3
  import * as React from "react";
4
4
  import {StyleSheet} from "aphrodite";
5
5
  import {Link} from "react-router-dom";
6
- import * as PropTypes from "prop-types";
6
+ import {__RouterContext} from "react-router";
7
7
 
8
8
  import Color, {mix, fade} from "@khanacademy/wonder-blocks-color";
9
9
  import Spacing from "@khanacademy/wonder-blocks-spacing";
@@ -38,6 +38,12 @@ type ActionProps = {|
38
38
  */
39
39
  href?: string,
40
40
 
41
+ /**
42
+ * Optional attribute to indicate to the Screen Reader which language the
43
+ * item text is in.
44
+ */
45
+ lang?: string,
46
+
41
47
  /**
42
48
  * A target destination window for a link to open in.
43
49
  */
@@ -103,10 +109,6 @@ type DefaultProps = {|
103
109
  role: $PropertyType<ActionProps, "role">,
104
110
  |};
105
111
 
106
- type ContextTypes = {|
107
- router: $FlowFixMe,
108
- |};
109
-
110
112
  const StyledAnchor = addStyle("a");
111
113
  const StyledButton = addStyle("button");
112
114
  const StyledLink = addStyle(Link);
@@ -120,7 +122,6 @@ export default class ActionItem extends React.Component<ActionProps> {
120
122
  static isClassOf(instance: React.Element<any>): boolean {
121
123
  return instance && instance.type && instance.type.__IS_ACTION_ITEM__;
122
124
  }
123
- static contextTypes: ContextTypes = {router: PropTypes.any};
124
125
  static defaultProps: DefaultProps = {
125
126
  disabled: false,
126
127
  indent: false,
@@ -128,7 +129,7 @@ export default class ActionItem extends React.Component<ActionProps> {
128
129
  };
129
130
  static __IS_ACTION_ITEM__: boolean = true;
130
131
 
131
- render(): React.Node {
132
+ renderClickableBehavior(router: any): React.Node {
132
133
  const {
133
134
  skipClientNav,
134
135
  disabled,
@@ -136,12 +137,12 @@ export default class ActionItem extends React.Component<ActionProps> {
136
137
  target,
137
138
  indent,
138
139
  label,
140
+ lang,
139
141
  onClick,
140
142
  role,
141
143
  style,
142
144
  testId,
143
145
  } = this.props;
144
- const {router} = this.context;
145
146
 
146
147
  const ClickableBehavior = getClickableBehavior(
147
148
  href,
@@ -182,6 +183,7 @@ export default class ActionItem extends React.Component<ActionProps> {
182
183
  const children = (
183
184
  <React.Fragment>
184
185
  <LabelMedium
186
+ lang={lang}
185
187
  style={[indent && styles.indent, styles.label]}
186
188
  >
187
189
  {label}
@@ -220,6 +222,14 @@ export default class ActionItem extends React.Component<ActionProps> {
220
222
  </ClickableBehavior>
221
223
  );
222
224
  }
225
+
226
+ render(): React.Node {
227
+ return (
228
+ <__RouterContext.Consumer>
229
+ {(router) => this.renderClickableBehavior(router)}
230
+ </__RouterContext.Consumer>
231
+ );
232
+ }
223
233
  }
224
234
 
225
235
  const styles = StyleSheet.create({
@@ -1,7 +1,6 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {StyleSheet} from "aphrodite";
4
- import * as PropTypes from "prop-types";
5
4
 
6
5
  import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
7
6
  import Color, {SemanticColor, mix} from "@khanacademy/wonder-blocks-color";
@@ -39,10 +38,6 @@ type Props = {|
39
38
  opened: boolean,
40
39
  |};
41
40
 
42
- type ContextTypes = {|
43
- router: $FlowFixMe,
44
- |};
45
-
46
41
  const StyledButton = addStyle<"button">("button");
47
42
 
48
43
  /**
@@ -52,8 +47,6 @@ const StyledButton = addStyle<"button">("button");
52
47
  * - the down caret icon is smaller that the one that would be used by ButtonCore
53
48
  */
54
49
  export default class ActionMenuOpenerCore extends React.Component<Props> {
55
- static contextTypes: ContextTypes = {router: PropTypes.any};
56
-
57
50
  render(): React.Node {
58
51
  const {
59
52
  children,
@@ -0,0 +1,48 @@
1
+ // @flow
2
+ import * as React from "react";
3
+
4
+ import type {StoryComponentType} from "@storybook/react";
5
+ import ActionMenu from "./action-menu.js";
6
+ import ActionItem from "./action-item.js";
7
+
8
+ export default {
9
+ title: "Dropdown / ActionMenu",
10
+ component: ActionMenu,
11
+ };
12
+
13
+ export const ActionMenuWithLang: StoryComponentType = () => (
14
+ <ActionMenu menuText="Locales">
15
+ {locales.map((locale) => (
16
+ <ActionItem
17
+ key={locale.locale}
18
+ label={locale.localName}
19
+ lang={locale.locale}
20
+ testId={"language_picker_" + locale.locale}
21
+ />
22
+ ))}
23
+ </ActionMenu>
24
+ );
25
+
26
+ ActionMenuWithLang.storyName = "Using the ActionMenu with the lang attribute";
27
+
28
+ ActionMenuWithLang.parameters = {
29
+ docs: {
30
+ storyDescription:
31
+ "You can use the `lang` attribute to specify the language of the action item(s). This is useful if you want to avoid issues with Screen Readers trying to read the proper language for the rendered text.",
32
+ },
33
+ chromatic: {
34
+ disableSnapshot: true,
35
+ },
36
+ };
37
+
38
+ const locales = [
39
+ {id: "az", locale: "az", localName: "Azərbaycanca"},
40
+ {id: "id", locale: "id", localName: "Bahasa Indonesia"},
41
+ {id: "cs", locale: "cs", localName: "čeština"},
42
+ {id: "da", locale: "da", localName: "dansk"},
43
+ {id: "de", locale: "de", localName: "Deutsch"},
44
+ {id: "en", locale: "en", localName: "English"},
45
+ {id: "es", locale: "es", localName: "español"},
46
+ {id: "fr", locale: "fr", localName: "français"},
47
+ {id: "it", locale: "it", localName: "italiano"},
48
+ ];
@@ -14,8 +14,7 @@ import type {IconAsset} from "@khanacademy/wonder-blocks-icon";
14
14
  // If the intended icon is a check without a checkbox, you should be using
15
15
  // icons.check from the Wonder Blocks Icon package.
16
16
  const checkboxCheck: IconAsset = {
17
- small:
18
- "M11.263 4.324a1 1 0 1 1 1.474 1.352l-5.5 6a1 1 0 0 1-1.505-.036l-2.5-3a1 1 0 1 1 1.536-1.28L6.536 9.48l4.727-5.157z",
17
+ small: "M11.263 4.324a1 1 0 1 1 1.474 1.352l-5.5 6a1 1 0 0 1-1.505-.036l-2.5-3a1 1 0 1 1 1.536-1.28L6.536 9.48l4.727-5.157z",
19
18
  };
20
19
  const {blue, white, offBlack16, offBlack32, offBlack50, offWhite} = Color;
21
20
 
@@ -522,9 +522,8 @@ class DropdownCore extends React.Component<Props, State> {
522
522
  // appropriate item in handleKeyDown
523
523
  this.itemsClicked = true;
524
524
  this.focusedIndex = index;
525
- this.focusedOriginalIndex = this.state.itemRefs[
526
- this.focusedIndex
527
- ].originalIndex;
525
+ this.focusedOriginalIndex =
526
+ this.state.itemRefs[this.focusedIndex].originalIndex;
528
527
  };
529
528
 
530
529
  handleDropdownMouseUp: (event: SyntheticMouseEvent<>) => void = (event) => {
@@ -79,20 +79,18 @@ class MultiSelectWithCustomStyles extends React.Component<Props, State> {
79
79
  }
80
80
  }
81
81
 
82
- export const customStyles: StoryComponentType = () => (
82
+ export const CustomStyles: StoryComponentType = () => (
83
83
  <MultiSelectWithCustomStyles />
84
84
  );
85
85
 
86
- customStyles.story = {
87
- parameters: {
88
- chromatic: {
89
- // we don't need screenshots because this story only tests behavior.
90
- disable: true,
91
- },
86
+ CustomStyles.parameters = {
87
+ chromatic: {
88
+ // we don't need screenshots because this story only tests behavior.
89
+ disableSnapshot: true,
92
90
  },
93
91
  };
94
92
 
95
- export const customStylesOpened: StoryComponentType = () => (
93
+ export const CustomStylesOpened: StoryComponentType = () => (
96
94
  <MultiSelectWithCustomStyles opened={true} />
97
95
  );
98
96
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import {StyleSheet} from "aphrodite";
5
- import * as PropTypes from "prop-types";
6
5
 
7
6
  import Color, {mix, fade} from "@khanacademy/wonder-blocks-color";
8
7
  import Spacing from "@khanacademy/wonder-blocks-spacing";
@@ -79,10 +78,6 @@ type OptionProps = {|
79
78
  style?: StyleType,
80
79
  |};
81
80
 
82
- type ContextTypes = {|
83
- router: $FlowFixMe,
84
- |};
85
-
86
81
  type DefaultProps = {|
87
82
  disabled: $PropertyType<OptionProps, "disabled">,
88
83
  onToggle: $PropertyType<OptionProps, "onToggle">,
@@ -99,7 +94,6 @@ export default class OptionItem extends React.Component<OptionProps> {
99
94
  static isClassOf(instance: React.Element<any>): boolean {
100
95
  return instance && instance.type && instance.type.__IS_OPTION_ITEM__;
101
96
  }
102
- static contextTypes: ContextTypes = {router: PropTypes.any};
103
97
  static defaultProps: DefaultProps = {
104
98
  disabled: false,
105
99
  onToggle: () => void 0,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import {StyleSheet} from "aphrodite";
5
- import * as PropTypes from "prop-types";
5
+ import {__RouterContext} from "react-router";
6
6
 
7
7
  import type {AriaProps} from "@khanacademy/wonder-blocks-core";
8
8
 
@@ -16,15 +16,8 @@ import {DROPDOWN_ITEM_HEIGHT} from "../util/constants.js";
16
16
 
17
17
  const StyledButton = addStyle("button");
18
18
 
19
- const {
20
- blue,
21
- white,
22
- white50,
23
- offBlack,
24
- offBlack16,
25
- offBlack32,
26
- offBlack64,
27
- } = Color;
19
+ const {blue, white, white50, offBlack, offBlack16, offBlack32, offBlack64} =
20
+ Color;
28
21
 
29
22
  type SelectOpenerProps = {|
30
23
  ...AriaProps,
@@ -77,10 +70,6 @@ type SelectOpenerProps = {|
77
70
  open: boolean,
78
71
  |};
79
72
 
80
- type ContextTypes = {|
81
- router: $FlowFixMe,
82
- |};
83
-
84
73
  type DefaultProps = {|
85
74
  disabled: $PropertyType<SelectOpenerProps, "disabled">,
86
75
  light: $PropertyType<SelectOpenerProps, "light">,
@@ -91,7 +80,6 @@ type DefaultProps = {|
91
80
  * An opener that opens select boxes.
92
81
  */
93
82
  export default class SelectOpener extends React.Component<SelectOpenerProps> {
94
- static contextTypes: ContextTypes = {router: PropTypes.any};
95
83
  static defaultProps: DefaultProps = {
96
84
  disabled: false,
97
85
  light: false,
@@ -103,7 +91,7 @@ export default class SelectOpener extends React.Component<SelectOpenerProps> {
103
91
  this.props.onOpenChanged(!open);
104
92
  };
105
93
 
106
- render(): React.Node {
94
+ renderClickableBehavior(router: any): React.Node {
107
95
  const {
108
96
  children,
109
97
  disabled,
@@ -117,7 +105,7 @@ export default class SelectOpener extends React.Component<SelectOpenerProps> {
117
105
  ...sharedProps
118
106
  } = this.props;
119
107
 
120
- const ClickableBehavior = getClickableBehavior(this.context.router);
108
+ const ClickableBehavior = getClickableBehavior(router);
121
109
 
122
110
  return (
123
111
  <ClickableBehavior disabled={disabled} onClick={this.handleClick}>
@@ -173,6 +161,14 @@ export default class SelectOpener extends React.Component<SelectOpenerProps> {
173
161
  </ClickableBehavior>
174
162
  );
175
163
  }
164
+
165
+ render(): React.Node {
166
+ return (
167
+ <__RouterContext.Consumer>
168
+ {(router) => this.renderClickableBehavior(router)}
169
+ </__RouterContext.Consumer>
170
+ );
171
+ }
176
172
  }
177
173
 
178
174
  const buttonRadius = 4;
@@ -106,12 +106,10 @@ const styles = StyleSheet.create({
106
106
 
107
107
  export const WithFilter: StoryComponentType = () => <SingleSelectWithFilter />;
108
108
 
109
- WithFilter.story = {
110
- parameters: {
111
- chromatic: {
112
- // we don't need screenshots because this story only tests behavior.
113
- disable: true,
114
- },
109
+ WithFilter.parameters = {
110
+ chromatic: {
111
+ // we don't need screenshots because this story only tests behavior.
112
+ disableSnapshot: true,
115
113
  },
116
114
  };
117
115
 
@@ -163,13 +161,12 @@ export const DropdownInModal: StoryComponentType = () => {
163
161
  );
164
162
  };
165
163
 
166
- DropdownInModal.story = {
167
- name: "Dropdown in a modal",
168
- parameters: {
169
- chromatic: {
170
- // We don't need screenshots because this story can be tested after
171
- // the modal is opened.
172
- disable: true,
173
- },
164
+ DropdownInModal.storyName = "Dropdown in a modal";
165
+
166
+ DropdownInModal.parameters = {
167
+ chromatic: {
168
+ // We don't need screenshots because this story can be tested after
169
+ // the modal is opened.
170
+ disableSnapshot: true,
174
171
  },
175
172
  };
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2018 Khan Academy
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,63 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`ActionItem should render with disabled styles 1`] = `
4
- <button
5
- className=""
6
- disabled={true}
7
- onBlur={[Function]}
8
- onClick={[Function]}
9
- onDragStart={[Function]}
10
- onFocus={[Function]}
11
- onKeyDown={[Function]}
12
- onKeyUp={[Function]}
13
- onMouseDown={[Function]}
14
- onMouseEnter={[Function]}
15
- onMouseLeave={[Function]}
16
- onMouseUp={[Function]}
17
- onTouchCancel={[Function]}
18
- onTouchEnd={[Function]}
19
- onTouchStart={[Function]}
20
- role="menuitem"
21
- style={
22
- Object {
23
- "::MozFocusInner": Object {
24
- "border": 0,
25
- },
26
- "alignItems": "center",
27
- "background": "#ffffff",
28
- "border": "none",
29
- "color": "rgba(33,36,44,0.32)",
30
- "cursor": "default",
31
- "display": "flex",
32
- "flexDirection": "row",
33
- "height": 40,
34
- "margin": 0,
35
- "minHeight": 40,
36
- "outline": "none",
37
- "paddingLeft": 16,
38
- "paddingRight": 16,
39
- "textDecoration": "none",
40
- "touchAction": "manipulation",
41
- }
42
- }
43
- tabIndex={-1}
44
- type="button"
45
- >
46
- <span
47
- className=""
48
- style={
49
- Object {
50
- "MozOsxFontSmoothing": "grayscale",
51
- "WebkitFontSmoothing": "antialiased",
52
- "display": "block",
53
- "fontFamily": "Lato, \\"Noto Sans\\", sans-serif",
54
- "fontSize": 16,
55
- "fontWeight": 400,
56
- "lineHeight": "20px",
57
- "userSelect": "none",
58
- "whiteSpace": "nowrap",
59
- }
60
- }
61
- />
62
- </button>
63
- `;