@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
@@ -99,6 +99,10 @@ describe("SingleSelect", () => {
99
99
  });
100
100
 
101
101
  describe("keyboard", () => {
102
+ beforeEach(() => {
103
+ jest.useFakeTimers();
104
+ });
105
+
102
106
  describe.each([{key: "{enter}"}, {key: "{space}"}])(
103
107
  "$key",
104
108
  ({key}) => {
@@ -129,7 +133,7 @@ describe("SingleSelect", () => {
129
133
  },
130
134
  );
131
135
 
132
- it("should not select an item when pressing {enter}", () => {
136
+ it("should select an item when pressing {enter}", () => {
133
137
  // Arrange
134
138
  render(uncontrolledSingleSelect);
135
139
  userEvent.tab();
@@ -139,8 +143,8 @@ describe("SingleSelect", () => {
139
143
  userEvent.keyboard("{enter}");
140
144
 
141
145
  // Assert
142
- expect(onChange).not.toHaveBeenCalled();
143
- expect(screen.getByRole("listbox")).toBeInTheDocument();
146
+ expect(onChange).toHaveBeenCalledWith("1");
147
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
144
148
  });
145
149
 
146
150
  it("should select an item when pressing {space}", () => {
@@ -157,6 +161,27 @@ describe("SingleSelect", () => {
157
161
  expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
158
162
  });
159
163
 
164
+ it("should find and select an item using the keyboard", () => {
165
+ // Arrange
166
+ render(
167
+ <SingleSelect onChange={onChange} placeholder="Choose">
168
+ <OptionItem label="apple" value="apple" />
169
+ <OptionItem label="orange" value="orange" />
170
+ <OptionItem label="pear" value="pear" />
171
+ </SingleSelect>,
172
+ );
173
+ userEvent.tab();
174
+
175
+ // Act
176
+ // find first occurrence
177
+ userEvent.keyboard("or");
178
+ jest.advanceTimersByTime(501);
179
+
180
+ // Assert
181
+ expect(onChange).toHaveBeenCalledWith("orange");
182
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
183
+ });
184
+
160
185
  it("should dismiss the dropdown when pressing {escape}", () => {
161
186
  // Arrange
162
187
  render(uncontrolledSingleSelect);
@@ -214,7 +239,7 @@ describe("SingleSelect", () => {
214
239
  render(<ControlledComponent onToggle={onToggleMock} />);
215
240
 
216
241
  // Act
217
- userEvent.click(screen.getByTestId("parent-button"));
242
+ userEvent.click(screen.getByRole("button", {name: "Choose"}));
218
243
 
219
244
  // Assert
220
245
  expect(onToggleMock).toHaveBeenCalledWith(true);
@@ -225,7 +250,7 @@ describe("SingleSelect", () => {
225
250
  const onToggleMock = jest.fn();
226
251
  render(<ControlledComponent onToggle={onToggleMock} />);
227
252
  // open the menu from the outside
228
- userEvent.click(screen.getByTestId("parent-button"));
253
+ userEvent.click(screen.getByRole("button", {name: "Choose"}));
229
254
 
230
255
  // Act
231
256
  // click on first item
@@ -264,10 +289,11 @@ describe("SingleSelect", () => {
264
289
  render(<ControlledComponent />);
265
290
 
266
291
  // Act
292
+ const opener = screen.getByRole("button", {name: "Choose"});
267
293
  // open the menu from the outside
268
- userEvent.click(screen.getByTestId("parent-button"));
294
+ userEvent.click(opener);
269
295
  // click on the dropdown anchor to hide the menu
270
- userEvent.click(screen.getByText("Choose"));
296
+ userEvent.click(opener);
271
297
 
272
298
  // Assert
273
299
  expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
@@ -360,12 +386,7 @@ describe("SingleSelect", () => {
360
386
  testId="openTest"
361
387
  onChange={jest.fn()}
362
388
  opener={({text}) => (
363
- <button
364
- onClick={jest.fn()}
365
- data-test-id="custom-opener"
366
- >
367
- {text}
368
- </button>
389
+ <button onClick={jest.fn()}>{text}</button>
369
390
  )}
370
391
  >
371
392
  <OptionItem label="Toggle A" value="toggle_a" />
@@ -374,7 +395,7 @@ describe("SingleSelect", () => {
374
395
  );
375
396
 
376
397
  // Act
377
- const opener = screen.getByTestId("custom-opener");
398
+ const opener = screen.getByRole("button");
378
399
  // open dropdown
379
400
  userEvent.click(opener);
380
401
 
@@ -405,12 +426,7 @@ describe("SingleSelect", () => {
405
426
  selectedValue={this.state.selectedValue}
406
427
  placeholder="Custom placeholder"
407
428
  opener={({text}) => (
408
- <button
409
- onClick={jest.fn()}
410
- data-test-id="custom-opener"
411
- >
412
- {text}
413
- </button>
429
+ <button onClick={jest.fn()}>{text}</button>
414
430
  )}
415
431
  >
416
432
  <OptionItem label="Toggle A" value="toggle_a" />
@@ -424,7 +440,7 @@ describe("SingleSelect", () => {
424
440
  render(<ControlledComponent />);
425
441
 
426
442
  // Act
427
- const opener = screen.getByTestId("custom-opener");
443
+ const opener = screen.getByRole("button");
428
444
  // open dropdown
429
445
  userEvent.click(opener);
430
446
  userEvent.click(screen.getByText("Toggle B"));
@@ -437,7 +453,7 @@ describe("SingleSelect", () => {
437
453
  });
438
454
 
439
455
  describe("isFilterable", () => {
440
- it("displays SearchTextInput when isFilterable is true", () => {
456
+ it("displays SearchField when isFilterable is true", () => {
441
457
  // Arrange
442
458
  render(
443
459
  <SingleSelect
@@ -484,7 +500,7 @@ describe("SingleSelect", () => {
484
500
  expect(options[0]).toHaveTextContent("item 2");
485
501
  });
486
502
 
487
- it("Type something in SearchTextInput should update searchText in SingleSelect", () => {
503
+ it("Type something in SearchField should update searchText in SingleSelect", () => {
488
504
  // Arrange
489
505
  render(
490
506
  <SingleSelect
@@ -611,30 +627,7 @@ describe("SingleSelect", () => {
611
627
 
612
628
  // Assert
613
629
  const dropdownMenu = screen.getByRole("listbox");
614
- expect(dropdownMenu).toHaveStyle("max-height: 132px");
615
- });
616
-
617
- it("should apply the default maxHeight to a filterable listbox", () => {
618
- // Arrange
619
-
620
- // Act
621
- render(
622
- <SingleSelect
623
- onChange={onChange}
624
- opened={true}
625
- isFilterable={true}
626
- placeholder="Choose"
627
- selectedValue="2"
628
- >
629
- <OptionItem label="item 1" value="1" />
630
- <OptionItem label="item 2" value="2" />
631
- <OptionItem label="item 3" value="3" />
632
- </SingleSelect>,
633
- );
634
-
635
- // Assert
636
- const dropdownMenu = screen.getByRole("listbox");
637
- expect(dropdownMenu).toHaveStyle("max-height: 184px");
630
+ expect(dropdownMenu).toHaveStyle("max-height: 120px");
638
631
  });
639
632
 
640
633
  it("should apply the default maxHeight to a virtualized listbox", () => {
@@ -665,7 +658,7 @@ describe("SingleSelect", () => {
665
658
  // Assert
666
659
  const dropdownMenu = screen.getByRole("listbox");
667
660
  // Max allowed height
668
- expect(dropdownMenu).toHaveStyle("max-height: 384px");
661
+ expect(dropdownMenu).toHaveStyle("max-height: 360px");
669
662
  });
670
663
 
671
664
  it("should override the default maxHeight to the listbox if a custom dropdownStyle is set", () => {
@@ -688,13 +681,13 @@ describe("SingleSelect", () => {
688
681
  );
689
682
 
690
683
  // Assert
691
- const dropdownMenu = screen.getByRole("listbox");
684
+ const dropdownMenu = screen.getByTestId("dropdown-core-container");
692
685
  expect(dropdownMenu).toHaveStyle("max-height: 200px");
693
686
  });
694
687
  });
695
688
 
696
689
  describe("a11y > Live region", () => {
697
- it("should change the number of options after using the search filter", async () => {
690
+ it("should change the number of options after using the search filter", () => {
698
691
  // Arrange
699
692
  render(
700
693
  <SingleSelect
@@ -103,6 +103,17 @@ type DefaultProps = {|
103
103
 
104
104
  /**
105
105
  * A menu that consists of various types of items.
106
+ *
107
+ * ## Usage
108
+ *
109
+ * ```jsx
110
+ * import {ActionMenu, ActionItem} from "@khanacademy/wonder-blocks-dropdown";
111
+ *
112
+ * <ActionMenu menuText="Menu">
113
+ * <ActionItem href="/profile" label="Profile" />
114
+ * <ActionItem label="Settings" onClick={() => {}} />
115
+ * </ActionMenu>
116
+ * ```
106
117
  */
107
118
  export default class ActionMenu extends React.Component<Props, State> {
108
119
  openerElement: ?HTMLElement;
@@ -9,7 +9,6 @@ import type {
9
9
  WithoutActionScheduler,
10
10
  } from "@khanacademy/wonder-blocks-timing";
11
11
  import DropdownVirtualizedItem from "./dropdown-core-virtualized-item.js";
12
- import SearchTextInput from "./search-text-input.js";
13
12
  import SeparatorItem from "./separator-item.js";
14
13
 
15
14
  import type {DropdownItem} from "../util/types.js";
@@ -17,7 +16,6 @@ import type {DropdownItem} from "../util/types.js";
17
16
  import {
18
17
  DROPDOWN_ITEM_HEIGHT,
19
18
  MAX_VISIBLE_ITEMS,
20
- SEARCH_ITEM_HEIGHT,
21
19
  SEPARATOR_ITEM_HEIGHT,
22
20
  } from "../util/constants.js";
23
21
  import {getDropdownMenuHeight} from "../util/dropdown-menu-styles.js";
@@ -134,9 +132,6 @@ class DropdownCoreVirtualized extends React.Component<Props, State> {
134
132
  if (SeparatorItem.isClassOf(item.component)) {
135
133
  // this is the separator's height (1px) + vertical margin (8px)
136
134
  return SEPARATOR_ITEM_HEIGHT;
137
- } else if (SearchTextInput.isClassOf(item.component)) {
138
- // search text input height
139
- return SEARCH_ITEM_HEIGHT;
140
135
  } else {
141
136
  // default dropdown item height
142
137
  return DROPDOWN_ITEM_HEIGHT;