@khanacademy/wonder-blocks-dropdown 2.7.3 → 2.7.6

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 (29) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/es/index.js +92 -162
  3. package/dist/index.js +285 -374
  4. package/package.json +6 -6
  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 +114 -208
  14. package/src/components/__tests__/multi-select.test.js +1 -3
  15. package/src/components/__tests__/single-select.test.js +15 -47
  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 +140 -126
  19. package/src/components/multi-select.js +17 -33
  20. package/src/components/single-select.js +15 -30
  21. package/src/util/__tests__/dropdown-menu-styles.test.js +0 -26
  22. package/src/util/constants.js +0 -11
  23. package/src/util/dropdown-menu-styles.js +0 -5
  24. package/src/util/types.js +2 -5
  25. package/src/components/__tests__/search-text-input.test.js +0 -212
  26. package/src/components/action-menu.stories.js +0 -48
  27. package/src/components/multi-select.stories.js +0 -124
  28. package/src/components/search-text-input.js +0 -115
  29. package/src/components/single-select.stories.js +0 -247
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "2.7.3",
3
+ "version": "2.7.6",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,15 +16,15 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.16.3",
19
- "@khanacademy/wonder-blocks-button": "^2.11.6",
19
+ "@khanacademy/wonder-blocks-button": "^3.0.0",
20
20
  "@khanacademy/wonder-blocks-clickable": "^2.2.7",
21
21
  "@khanacademy/wonder-blocks-color": "^1.1.20",
22
22
  "@khanacademy/wonder-blocks-core": "^4.3.2",
23
- "@khanacademy/wonder-blocks-icon": "^1.2.28",
24
- "@khanacademy/wonder-blocks-icon-button": "^3.4.7",
23
+ "@khanacademy/wonder-blocks-icon": "^1.2.29",
24
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.8",
25
25
  "@khanacademy/wonder-blocks-layout": "^1.4.10",
26
- "@khanacademy/wonder-blocks-modal": "^2.3.2",
27
- "@khanacademy/wonder-blocks-search-field": "^1.0.5",
26
+ "@khanacademy/wonder-blocks-modal": "^2.3.3",
27
+ "@khanacademy/wonder-blocks-search-field": "^1.0.6",
28
28
  "@khanacademy/wonder-blocks-spacing": "^3.0.5",
29
29
  "@khanacademy/wonder-blocks-timing": "^2.1.0",
30
30
  "@khanacademy/wonder-blocks-typography": "^1.1.32"
@@ -0,0 +1,44 @@
1
+ // @flow
2
+ export default {
3
+ alignment: {
4
+ table: {
5
+ category: "Layout",
6
+ },
7
+ },
8
+ disabled: {
9
+ table: {
10
+ category: "States",
11
+ },
12
+ },
13
+ opened: {
14
+ control: "boolean",
15
+ table: {
16
+ category: "States",
17
+ },
18
+ },
19
+ onToggle: {
20
+ table: {
21
+ category: "Events",
22
+ },
23
+ },
24
+ onChange: {
25
+ table: {
26
+ category: "Events",
27
+ },
28
+ },
29
+ dropdownStyle: {
30
+ table: {
31
+ category: "Styling",
32
+ },
33
+ },
34
+ style: {
35
+ table: {
36
+ category: "Styling",
37
+ },
38
+ },
39
+ className: {
40
+ table: {
41
+ category: "Styling",
42
+ },
43
+ },
44
+ };
@@ -0,0 +1,435 @@
1
+ /* eslint-disable no-console */
2
+ // @flow
3
+ import * as React from "react";
4
+ import {StyleSheet} from "aphrodite";
5
+
6
+ import type {StoryComponentType} from "@storybook/react";
7
+
8
+ import Color from "@khanacademy/wonder-blocks-color";
9
+ import {View} from "@khanacademy/wonder-blocks-core";
10
+ import {
11
+ ActionItem,
12
+ ActionMenu,
13
+ OptionItem,
14
+ SeparatorItem,
15
+ } from "@khanacademy/wonder-blocks-dropdown";
16
+ import {Checkbox} from "@khanacademy/wonder-blocks-form";
17
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
18
+ import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
19
+
20
+ import actionMenuArgtypes from "./action-menu.argtypes.js";
21
+ import ComponentInfo from "../../../../../.storybook/components/component-info.js";
22
+ import {name, version} from "../../../package.json";
23
+
24
+ import type {Item} from "../../util/types.js";
25
+
26
+ type ActionMenuProps = React.ElementProps<typeof ActionMenu>;
27
+
28
+ const defaultArgs: ActionMenuProps = {
29
+ alignment: "left",
30
+ disabled: false,
31
+ menuText: "Betsy Appleseed",
32
+ onChange: (selectedItems) => {},
33
+ selectedValues: [],
34
+ testId: "",
35
+ dropdownStyle: {},
36
+ style: {},
37
+ className: "",
38
+ };
39
+
40
+ export default {
41
+ title: "Dropdown / ActionMenu",
42
+ component: ActionMenu,
43
+ subcomponents: {ActionItem},
44
+ argTypes: actionMenuArgtypes,
45
+ args: defaultArgs,
46
+ decorators: [
47
+ (Story: StoryComponentType): React.Element<typeof View> => (
48
+ <View style={styles.example}>
49
+ <Story />
50
+ </View>
51
+ ),
52
+ ],
53
+ parameters: {
54
+ componentSubtitle: ((
55
+ <ComponentInfo name={name} version={version} />
56
+ ): any),
57
+ docs: {
58
+ description: {
59
+ component: null,
60
+ },
61
+ source: {
62
+ // See https://github.com/storybookjs/storybook/issues/12596
63
+ excludeDecorators: true,
64
+ },
65
+ },
66
+ },
67
+ };
68
+
69
+ const styles = StyleSheet.create({
70
+ example: {
71
+ background: Color.offWhite,
72
+ padding: Spacing.medium_16,
73
+ },
74
+ rowRight: {
75
+ flexDirection: "row",
76
+ justifyContent: "flex-end",
77
+ },
78
+ row: {
79
+ flexDirection: "row",
80
+ alignItems: "center",
81
+ justifyContent: "space-between",
82
+ },
83
+ dropdown: {
84
+ maxHeight: 200,
85
+ },
86
+ /**
87
+ * Custom opener styles
88
+ */
89
+ customOpener: {
90
+ borderLeft: `5px solid ${Color.blue}`,
91
+ borderRadius: Spacing.xxxSmall_4,
92
+ background: Color.lightBlue,
93
+ color: Color.white,
94
+ padding: Spacing.medium_16,
95
+ },
96
+ focused: {
97
+ color: Color.offWhite,
98
+ },
99
+ hovered: {
100
+ textDecoration: "underline",
101
+ color: Color.offWhite,
102
+ cursor: "pointer",
103
+ },
104
+ pressed: {
105
+ color: Color.blue,
106
+ },
107
+ });
108
+
109
+ const actionItems: Array<Item> = [
110
+ <ActionItem
111
+ label="Profile"
112
+ href="http://khanacademy.org/profile"
113
+ target="_blank"
114
+ testId="profile"
115
+ />,
116
+ <ActionItem
117
+ label="Teacher dashboard"
118
+ href="http://khanacademy.org/coach/dashboard"
119
+ testId="dashboard"
120
+ />,
121
+ <ActionItem
122
+ label="Settings (onClick)"
123
+ onClick={() => console.log("user clicked on settings")}
124
+ testId="settings"
125
+ />,
126
+ <ActionItem
127
+ label="Help"
128
+ disabled={true}
129
+ onClick={() => console.log("this item is disabled...")}
130
+ testId="help"
131
+ />,
132
+ <ActionItem
133
+ label="Feedback"
134
+ disabled={true}
135
+ href="/feedback"
136
+ testId="feedback"
137
+ />,
138
+ <SeparatorItem />,
139
+ <ActionItem
140
+ label="Log out"
141
+ href="http://khanacademy.org/logout"
142
+ testId="logout"
143
+ />,
144
+ ];
145
+
146
+ const Template = (args) => (
147
+ <ActionMenu {...args}>
148
+ {actionItems.map((actionItem, index) => actionItem)}
149
+ </ActionMenu>
150
+ );
151
+
152
+ export const Default: StoryComponentType = Template.bind({});
153
+
154
+ Default.parameters = {
155
+ chromatic: {
156
+ // Disabling because this doesn't test visuals, its only showing the
157
+ // dropdown opener closed.
158
+ disableSnapshot: true,
159
+ },
160
+ };
161
+
162
+ /**
163
+ * Right-aligned action menu.
164
+ */
165
+ export const RightAligned: StoryComponentType = (args) => (
166
+ <ActionMenu {...args} alignment="right">
167
+ {actionItems.map((actionItem, index) => actionItem)}
168
+ </ActionMenu>
169
+ );
170
+
171
+ RightAligned.decorators = [
172
+ (Story): React.Element<typeof View> => (
173
+ <View style={styles.rowRight}>{Story()}</View>
174
+ ),
175
+ ];
176
+
177
+ RightAligned.parameters = {
178
+ docs: {
179
+ description: {
180
+ story:
181
+ "This menu shows different type of possible items in this type of menu:\n" +
182
+ "1. leads to a different page (the profile).\n" +
183
+ "2. leads to the teacher dashboard.\n" +
184
+ "3. has an onClick callback, which could be used for conversion logging.\n" +
185
+ "4. is a disabled item.\n" +
186
+ "5. is a separator.\n" +
187
+ "6. leads to the logout link.\n\n" +
188
+ "This menu is also right-aligned.",
189
+ },
190
+ },
191
+ chromatic: {
192
+ // Disabling because this doesn't test visuals, its only showing the
193
+ // dropdown opener closed.
194
+ disableSnapshot: true,
195
+ },
196
+ };
197
+
198
+ /**
199
+ * Menu with truncated text.
200
+ */
201
+ export const TruncatedOpener: StoryComponentType = Template.bind({});
202
+
203
+ TruncatedOpener.args = {
204
+ style: {width: 100},
205
+ };
206
+
207
+ TruncatedOpener.parameters = {
208
+ docs: {
209
+ description: {
210
+ story: "The text in the menu opener should be truncated with ellipsis at the end and the down caret should be the same size as it is for the other examples.",
211
+ },
212
+ },
213
+ };
214
+
215
+ /**
216
+ * With option items
217
+ */
218
+ export const WithOptionItems: StoryComponentType = () => {
219
+ const [selectedValues, setSelectedValues] = React.useState([]);
220
+ const [showHiddenOption, setShowHiddenOption] = React.useState(false);
221
+
222
+ const handleChange = (selectedItems) => {
223
+ setSelectedValues(selectedItems);
224
+ setShowHiddenOption(selectedItems.includes("in-class"));
225
+ };
226
+
227
+ return (
228
+ <ActionMenu
229
+ menuText="Assignments"
230
+ onChange={handleChange}
231
+ selectedValues={selectedValues}
232
+ >
233
+ <ActionItem
234
+ label="Create..."
235
+ onClick={() => console.log("create action")}
236
+ />
237
+ <ActionItem
238
+ label="Edit..."
239
+ disabled={true}
240
+ onClick={() => console.log("edit action")}
241
+ />
242
+ <ActionItem
243
+ label="Delete"
244
+ disabled={true}
245
+ onClick={() => console.log("delete action")}
246
+ />
247
+ {showHiddenOption && (
248
+ <ActionItem
249
+ label="Hidden menu for class"
250
+ disabled={!showHiddenOption}
251
+ onClick={() => console.log("hidden menu is clicked!")}
252
+ />
253
+ )}
254
+ <SeparatorItem />
255
+ <OptionItem
256
+ label="Show homework assignments"
257
+ value="homework"
258
+ onClick={() => console.log(`Show homework assignments toggled`)}
259
+ />
260
+ <OptionItem
261
+ label="Show in-class assignments"
262
+ value="in-class"
263
+ onClick={() => console.log(`Show in-class assignments toggled`)}
264
+ />
265
+ </ActionMenu>
266
+ );
267
+ };
268
+
269
+ WithOptionItems.parameters = {
270
+ docs: {
271
+ description: {
272
+ story: "The following menu demonstrates a hybrid menu with both action items and items that can toggle to change the state of the application. The user of this menu must keep track of the state of the selected items.",
273
+ },
274
+ },
275
+ chromatic: {
276
+ // Disabling because this doesn't test visuals, its only showing the
277
+ // dropdown opener closed.
278
+ disableSnapshot: true,
279
+ },
280
+ };
281
+
282
+ /**
283
+ * Empty menu
284
+ */
285
+ export const EmptyMenu: StoryComponentType = () => (
286
+ <ActionMenu menuText="Empty" />
287
+ );
288
+
289
+ EmptyMenu.parameters = {
290
+ docs: {
291
+ description: {
292
+ story: "Empty menus are disabled automatically.",
293
+ },
294
+ },
295
+ };
296
+
297
+ /**
298
+ * Custom dropdownStyle
299
+ */
300
+ export const CustomDropdownStyle: StoryComponentType = Template.bind({});
301
+
302
+ CustomDropdownStyle.args = {
303
+ dropdownStyle: styles.dropdown,
304
+ };
305
+
306
+ CustomDropdownStyle.storyName = "Custom dropdownStyle";
307
+
308
+ CustomDropdownStyle.parameters = {
309
+ docs: {
310
+ description: {
311
+ story: "This example shows how we can add custom styles to the dropdown menu.",
312
+ },
313
+ },
314
+ chromatic: {
315
+ // Disabling because this doesn't test visuals.
316
+ disableSnapshot: true,
317
+ },
318
+ };
319
+
320
+ /**
321
+ * Controlled ActionMenu
322
+ */
323
+ export const Controlled: StoryComponentType = () => {
324
+ const [opened, setOpened] = React.useState(false);
325
+
326
+ return (
327
+ <View style={styles.row}>
328
+ <Checkbox
329
+ label="Click to toggle"
330
+ onChange={setOpened}
331
+ checked={opened}
332
+ />
333
+ <ActionMenu
334
+ menuText="Betsy Appleseed"
335
+ opened={opened}
336
+ onToggle={setOpened}
337
+ >
338
+ {actionItems.map((actionItem, index) => actionItem)}
339
+ </ActionMenu>
340
+ </View>
341
+ );
342
+ };
343
+
344
+ Controlled.parameters = {
345
+ docs: {
346
+ description: {
347
+ story:
348
+ "Sometimes you'll want to trigger a dropdown programmatically. This can be done by setting a value to the opened prop (true or false). In this situation the ActionMenu is a controlled component. The parent is responsible for managing the opening/closing of the dropdown when using this prop.\n" +
349
+ "This means that you'll also have to update opened to the value triggered by the onToggle prop.",
350
+ },
351
+ },
352
+ chromatic: {
353
+ // Disabling because this doesn't test visuals.
354
+ disableSnapshot: true,
355
+ },
356
+ };
357
+
358
+ /**
359
+ * With custom opener
360
+ */
361
+
362
+ export const CustomOpener: StoryComponentType = Template.bind({});
363
+
364
+ CustomOpener.args = {
365
+ opener: ({focused, hovered, pressed, text}) => (
366
+ <LabelLarge
367
+ onClick={() => {
368
+ console.log("custom click!!!!!");
369
+ }}
370
+ testId="teacher-menu-custom-opener"
371
+ style={[
372
+ styles.customOpener,
373
+ focused && styles.focused,
374
+ hovered && styles.hovered,
375
+ pressed && styles.pressed,
376
+ ]}
377
+ >
378
+ {text}
379
+ </LabelLarge>
380
+ ),
381
+ };
382
+
383
+ CustomOpener.storyName = "With custom opener";
384
+
385
+ CustomOpener.parameters = {
386
+ docs: {
387
+ description: {
388
+ story:
389
+ "In case you need to use a custom opener, you can use the opener property to achieve this. In this example, the opener prop accepts a function with the following arguments:\n" +
390
+ "- `eventState`: lets you customize the style for different states, such as pressed, hovered and focused.\n" +
391
+ "- `text`: Passes the menu label defined in the parent component. This value is passed using the placeholder prop set in the ActionMenu component.\n\n" +
392
+ "**Note:** If you need to use a custom ID for testing the opener, make sure to pass the testId prop inside the opener component/element.",
393
+ },
394
+ },
395
+ };
396
+
397
+ /**
398
+ * Action menu items with lang attribute.
399
+ */
400
+ export const ActionMenuWithLang: StoryComponentType = () => (
401
+ <ActionMenu menuText="Locales">
402
+ {locales.map((locale) => (
403
+ <ActionItem
404
+ key={locale.locale}
405
+ label={locale.localName}
406
+ lang={locale.locale}
407
+ testId={"language_picker_" + locale.locale}
408
+ />
409
+ ))}
410
+ </ActionMenu>
411
+ );
412
+
413
+ ActionMenuWithLang.storyName = "Using the lang attribute";
414
+
415
+ ActionMenuWithLang.parameters = {
416
+ docs: {
417
+ storyDescription:
418
+ "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.",
419
+ },
420
+ chromatic: {
421
+ disableSnapshot: true,
422
+ },
423
+ };
424
+
425
+ const locales = [
426
+ {id: "az", locale: "az", localName: "Azərbaycanca"},
427
+ {id: "id", locale: "id", localName: "Bahasa Indonesia"},
428
+ {id: "cs", locale: "cs", localName: "čeština"},
429
+ {id: "da", locale: "da", localName: "dansk"},
430
+ {id: "de", locale: "de", localName: "Deutsch"},
431
+ {id: "en", locale: "en", localName: "English"},
432
+ {id: "es", locale: "es", localName: "español"},
433
+ {id: "fr", locale: "fr", localName: "français"},
434
+ {id: "it", locale: "it", localName: "italiano"},
435
+ ];
@@ -0,0 +1,54 @@
1
+ // @flow
2
+ export default {
3
+ alignment: {
4
+ table: {
5
+ category: "Layout",
6
+ },
7
+ },
8
+ disabled: {
9
+ table: {
10
+ category: "States",
11
+ },
12
+ },
13
+ isFilterable: {
14
+ table: {
15
+ category: "States",
16
+ },
17
+ },
18
+ light: {
19
+ table: {
20
+ category: "States",
21
+ },
22
+ },
23
+ opened: {
24
+ control: "boolean",
25
+ table: {
26
+ category: "States",
27
+ },
28
+ },
29
+ onToggle: {
30
+ table: {
31
+ category: "Events",
32
+ },
33
+ },
34
+ onChange: {
35
+ table: {
36
+ category: "Events",
37
+ },
38
+ },
39
+ dropdownStyle: {
40
+ table: {
41
+ category: "Styling",
42
+ },
43
+ },
44
+ style: {
45
+ table: {
46
+ category: "Styling",
47
+ },
48
+ },
49
+ className: {
50
+ table: {
51
+ category: "Styling",
52
+ },
53
+ },
54
+ };