@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.
- package/CHANGELOG.md +24 -0
- package/dist/es/index.js +92 -162
- package/dist/index.js +285 -374
- package/package.json +6 -6
- package/src/components/__docs__/action-menu.argtypes.js +44 -0
- package/src/components/__docs__/action-menu.stories.js +435 -0
- package/src/components/__docs__/base-select.argtypes.js +54 -0
- package/src/components/__docs__/multi-select.stories.js +509 -0
- package/src/components/__docs__/single-select.accessibility.stories.mdx +59 -0
- package/src/components/__docs__/single-select.argtypes.js +54 -0
- package/src/components/__docs__/single-select.stories.js +464 -0
- package/src/components/__tests__/dropdown-core-virtualized.test.js +0 -15
- package/src/components/__tests__/dropdown-core.test.js +114 -208
- package/src/components/__tests__/multi-select.test.js +1 -3
- package/src/components/__tests__/single-select.test.js +15 -47
- package/src/components/action-menu.js +11 -0
- package/src/components/dropdown-core-virtualized.js +0 -5
- package/src/components/dropdown-core.js +140 -126
- package/src/components/multi-select.js +17 -33
- package/src/components/single-select.js +15 -30
- package/src/util/__tests__/dropdown-menu-styles.test.js +0 -26
- package/src/util/constants.js +0 -11
- package/src/util/dropdown-menu-styles.js +0 -5
- package/src/util/types.js +2 -5
- package/src/components/__tests__/search-text-input.test.js +0 -212
- package/src/components/action-menu.stories.js +0 -48
- package/src/components/multi-select.stories.js +0 -124
- package/src/components/search-text-input.js +0 -115
- package/src/components/single-select.stories.js +0 -247
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
// A TextField with a search icon on its left side and X icon on its right side
|
|
3
|
-
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
import SearchField from "@khanacademy/wonder-blocks-search-field";
|
|
7
|
-
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
8
|
-
|
|
9
|
-
import {defaultLabels} from "../util/constants.js";
|
|
10
|
-
|
|
11
|
-
type Labels = {|
|
|
12
|
-
clearSearch: string,
|
|
13
|
-
filter: string,
|
|
14
|
-
|};
|
|
15
|
-
|
|
16
|
-
type Props = {|
|
|
17
|
-
/**
|
|
18
|
-
* The object containing the custom labels used inside this component.
|
|
19
|
-
*/
|
|
20
|
-
labels: Labels,
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* the text input
|
|
24
|
-
*/
|
|
25
|
-
searchText: string,
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Called when the input value is changed
|
|
29
|
-
*/
|
|
30
|
-
onChange: (searchText: string) => mixed,
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Handler that is triggered when this component is clicked. For example,
|
|
34
|
-
* use this to adjust focus in parent component. This gets called when we
|
|
35
|
-
* click the dismiss icon button within the SearchTextInput.
|
|
36
|
-
*/
|
|
37
|
-
onClick?: () => mixed,
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Used to handle the focus order in its parent component. The itemRef is
|
|
41
|
-
* applied to the input directly.
|
|
42
|
-
*/
|
|
43
|
-
itemRef?: {|current: any|},
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Custom styles for the main wrapper
|
|
47
|
-
*/
|
|
48
|
-
style?: StyleType,
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Test ID used for e2e testing.
|
|
52
|
-
*/
|
|
53
|
-
testId?: string,
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Automatically focus on this search field on mount.
|
|
57
|
-
* TODO(WB-1310): Remove the autofocus prop after making
|
|
58
|
-
* the search field sticky in dropdowns.
|
|
59
|
-
*/
|
|
60
|
-
autofocus?: boolean,
|
|
61
|
-
|};
|
|
62
|
-
|
|
63
|
-
type DefaultProps = {|
|
|
64
|
-
labels: $PropertyType<Props, "labels">,
|
|
65
|
-
|};
|
|
66
|
-
|
|
67
|
-
export default class SearchTextInput extends React.Component<Props> {
|
|
68
|
-
static isClassOf(instance: React.Element<any>): boolean {
|
|
69
|
-
return (
|
|
70
|
-
instance && instance.type && instance.type.__IS_SEARCH_TEXT_INPUT__
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
static defaultProps: DefaultProps = {
|
|
75
|
-
labels: {
|
|
76
|
-
clearSearch: defaultLabels.clearSearch,
|
|
77
|
-
filter: defaultLabels.filter,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// TODO(WB-1310): Remove `componentDidMount` autofocus on the search field
|
|
82
|
-
// after making the search field sticky.
|
|
83
|
-
componentDidMount() {
|
|
84
|
-
// We need to re-focus on the text input after it mounts because of
|
|
85
|
-
// the case in which the dropdown switches between virtualized and
|
|
86
|
-
// non-virtualized. It can rerender the search field as the user is
|
|
87
|
-
// typing based on the number of search results, which results
|
|
88
|
-
// in losing focus on the field so the user can't type anymore.
|
|
89
|
-
// To work around this issue, this temporary fix auto-focuses on the
|
|
90
|
-
// search field on mount.
|
|
91
|
-
if (this.props.autofocus) {
|
|
92
|
-
this.props.itemRef?.current.focus();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
static __IS_SEARCH_TEXT_INPUT__: boolean = true;
|
|
97
|
-
|
|
98
|
-
render(): React.Node {
|
|
99
|
-
const {labels, onChange, onClick, itemRef, searchText, style, testId} =
|
|
100
|
-
this.props;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<SearchField
|
|
104
|
-
clearAriaLabel={labels.clearSearch}
|
|
105
|
-
onChange={onChange}
|
|
106
|
-
onClick={onClick}
|
|
107
|
-
placeholder={labels.filter}
|
|
108
|
-
ref={itemRef}
|
|
109
|
-
style={style}
|
|
110
|
-
testId={testId}
|
|
111
|
-
value={searchText}
|
|
112
|
-
/>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import {StyleSheet} from "aphrodite";
|
|
4
|
-
|
|
5
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
6
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
7
|
-
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
8
|
-
import {OnePaneDialog, ModalLauncher} from "@khanacademy/wonder-blocks-modal";
|
|
9
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
10
|
-
import {Body} from "@khanacademy/wonder-blocks-typography";
|
|
11
|
-
|
|
12
|
-
import type {StoryComponentType} from "@storybook/react";
|
|
13
|
-
|
|
14
|
-
import {SingleSelect, OptionItem} from "../index.js";
|
|
15
|
-
|
|
16
|
-
export default {
|
|
17
|
-
title: "Dropdown / SingleSelect",
|
|
18
|
-
component: SingleSelect,
|
|
19
|
-
args: {
|
|
20
|
-
isFilterable: true,
|
|
21
|
-
opened: true,
|
|
22
|
-
disabled: false,
|
|
23
|
-
light: false,
|
|
24
|
-
placeholder: "Choose a fruit",
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const SingleSelectTemplate = (args) => <SingleSelect {...args} />;
|
|
29
|
-
|
|
30
|
-
export const DefaultSingleSelectOpened: StoryComponentType = (args) => {
|
|
31
|
-
const [selectedValue, setSelectedValue] = React.useState("pear");
|
|
32
|
-
const [opened, setOpened] = React.useState(args.opened);
|
|
33
|
-
React.useEffect(() => {
|
|
34
|
-
// Only update opened if the args.opened prop changes (using the
|
|
35
|
-
// controls panel).
|
|
36
|
-
setOpened(args.opened);
|
|
37
|
-
}, [args.opened]);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<SingleSelectTemplate
|
|
41
|
-
{...args}
|
|
42
|
-
onChange={setSelectedValue}
|
|
43
|
-
selectedValue={selectedValue}
|
|
44
|
-
opened={opened}
|
|
45
|
-
onToggle={setOpened}
|
|
46
|
-
>
|
|
47
|
-
<OptionItem label="Banana" value="banana" />
|
|
48
|
-
<OptionItem label="Strawberry" value="strawberry" disabled />
|
|
49
|
-
<OptionItem label="Pear" value="pear" />
|
|
50
|
-
<OptionItem label="Orange" value="orange" />
|
|
51
|
-
<OptionItem label="Watermelon" value="watermelon" />
|
|
52
|
-
<OptionItem label="Apple" value="apple" />
|
|
53
|
-
<OptionItem label="Grape" value="grape" />
|
|
54
|
-
<OptionItem label="Lemon" value="lemon" />
|
|
55
|
-
<OptionItem label="Mango" value="mango" />
|
|
56
|
-
</SingleSelectTemplate>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
DefaultSingleSelectOpened.parameters = {
|
|
61
|
-
docs: {
|
|
62
|
-
storyDescription:
|
|
63
|
-
"This select starts with a starting selected item. One of the items is disabled and thus cannot be selected.",
|
|
64
|
-
},
|
|
65
|
-
// Added to ensure that the dropdown menu is rendered using PopperJS.
|
|
66
|
-
chromatic: {delay: 400},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const fruits = ["banana", "strawberry", "pear", "orange"];
|
|
70
|
-
|
|
71
|
-
const optionItems = new Array(1000)
|
|
72
|
-
.fill(null)
|
|
73
|
-
.map((_, i) => (
|
|
74
|
-
<OptionItem
|
|
75
|
-
key={i}
|
|
76
|
-
value={(i + 1).toString()}
|
|
77
|
-
label={`Fruit # ${i + 1} ${fruits[i % fruits.length]}`}
|
|
78
|
-
/>
|
|
79
|
-
));
|
|
80
|
-
|
|
81
|
-
type Props = {|
|
|
82
|
-
selectedValue?: ?string,
|
|
83
|
-
opened: boolean,
|
|
84
|
-
|};
|
|
85
|
-
|
|
86
|
-
type State = {|
|
|
87
|
-
selectedValue?: ?string,
|
|
88
|
-
opened: boolean,
|
|
89
|
-
|};
|
|
90
|
-
|
|
91
|
-
type DefaultProps = {|
|
|
92
|
-
selectedValue: $PropertyType<Props, "selectedValue">,
|
|
93
|
-
opened: $PropertyType<Props, "opened">,
|
|
94
|
-
|};
|
|
95
|
-
|
|
96
|
-
class SingleSelectWithFilter extends React.Component<Props, State> {
|
|
97
|
-
static defaultProps: DefaultProps = {
|
|
98
|
-
selectedValue: "2",
|
|
99
|
-
opened: false,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
state: State = {
|
|
103
|
-
selectedValue: this.props.selectedValue,
|
|
104
|
-
opened: this.props.opened,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
handleChange: (selected: string) => void = (selected) => {
|
|
108
|
-
this.setState({
|
|
109
|
-
selectedValue: selected,
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
handleToggleMenu: (opened: boolean) => void = (opened) => {
|
|
114
|
-
this.setState({
|
|
115
|
-
opened,
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
render(): React.Node {
|
|
120
|
-
return (
|
|
121
|
-
<View style={styles.wrapper}>
|
|
122
|
-
<SingleSelect
|
|
123
|
-
onChange={this.handleChange}
|
|
124
|
-
isFilterable={true}
|
|
125
|
-
opened={this.state.opened}
|
|
126
|
-
onToggle={this.handleToggleMenu}
|
|
127
|
-
placeholder="Select a fruit"
|
|
128
|
-
selectedValue={this.state.selectedValue}
|
|
129
|
-
dropdownStyle={styles.fullBleed}
|
|
130
|
-
style={styles.fullBleed}
|
|
131
|
-
>
|
|
132
|
-
{optionItems}
|
|
133
|
-
</SingleSelect>
|
|
134
|
-
</View>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const styles = StyleSheet.create({
|
|
140
|
-
row: {
|
|
141
|
-
flexDirection: "row",
|
|
142
|
-
},
|
|
143
|
-
fullBleed: {
|
|
144
|
-
width: "100%",
|
|
145
|
-
},
|
|
146
|
-
wrapper: {
|
|
147
|
-
height: "800px",
|
|
148
|
-
width: "600px",
|
|
149
|
-
},
|
|
150
|
-
centered: {
|
|
151
|
-
alignItems: "center",
|
|
152
|
-
justifyContent: "center",
|
|
153
|
-
height: `calc(100vh - 16px)`,
|
|
154
|
-
},
|
|
155
|
-
scrollableArea: {
|
|
156
|
-
height: "200vh",
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
export const WithFilter: StoryComponentType = () => <SingleSelectWithFilter />;
|
|
161
|
-
|
|
162
|
-
WithFilter.parameters = {
|
|
163
|
-
chromatic: {
|
|
164
|
-
// we don't need screenshots because this story only tests behavior.
|
|
165
|
-
disableSnapshot: true,
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export const WithFilterOpened: StoryComponentType = () => (
|
|
170
|
-
<SingleSelectWithFilter opened={true} />
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
export const WithFilterOpenedNoValueSelected: StoryComponentType = () => (
|
|
174
|
-
<SingleSelectWithFilter opened={true} selectedValue={null} />
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
export const DropdownInModal: StoryComponentType = () => {
|
|
178
|
-
const [value, setValue] = React.useState(null);
|
|
179
|
-
const [opened, setOpened] = React.useState(true);
|
|
180
|
-
|
|
181
|
-
const modalContent = (
|
|
182
|
-
<View style={styles.scrollableArea}>
|
|
183
|
-
<View>
|
|
184
|
-
<Body>
|
|
185
|
-
Sometimes we want to include Dropdowns inside a Modal, and
|
|
186
|
-
these controls can be accessed only by scrolling down. This
|
|
187
|
-
example help us to demonstrate that SingleSelect components
|
|
188
|
-
can correctly be displayed within the visible scrolling
|
|
189
|
-
area.
|
|
190
|
-
</Body>
|
|
191
|
-
<Strut size={Spacing.large_24} />
|
|
192
|
-
<SingleSelect
|
|
193
|
-
onChange={(selected) => setValue(selected)}
|
|
194
|
-
isFilterable={true}
|
|
195
|
-
opened={opened}
|
|
196
|
-
onToggle={(opened) => setOpened(opened)}
|
|
197
|
-
placeholder="Select a fruit"
|
|
198
|
-
selectedValue={value}
|
|
199
|
-
>
|
|
200
|
-
{optionItems}
|
|
201
|
-
</SingleSelect>
|
|
202
|
-
</View>
|
|
203
|
-
</View>
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const modal = (
|
|
207
|
-
<OnePaneDialog title="Dropdown in a Modal" content={modalContent} />
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<View style={styles.centered}>
|
|
212
|
-
<ModalLauncher modal={modal}>
|
|
213
|
-
{({openModal}) => (
|
|
214
|
-
<Button onClick={openModal}>Click here!</Button>
|
|
215
|
-
)}
|
|
216
|
-
</ModalLauncher>
|
|
217
|
-
</View>
|
|
218
|
-
);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
DropdownInModal.storyName = "Dropdown in a modal";
|
|
222
|
-
|
|
223
|
-
DropdownInModal.parameters = {
|
|
224
|
-
chromatic: {
|
|
225
|
-
// We don't need screenshots because this story can be tested after
|
|
226
|
-
// the modal is opened.
|
|
227
|
-
disableSnapshot: true,
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export const DisabledSingleSelect: StoryComponentType = () => (
|
|
232
|
-
<SingleSelect
|
|
233
|
-
disabled={true}
|
|
234
|
-
placeholder="Choose a juice"
|
|
235
|
-
onChange={() => {}}
|
|
236
|
-
>
|
|
237
|
-
<OptionItem label="Banana juice" value="banana" />
|
|
238
|
-
<OptionItem label="Strawberry juice" value="strawberry" />
|
|
239
|
-
</SingleSelect>
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
DisabledSingleSelect.parameters = {
|
|
243
|
-
docs: {
|
|
244
|
-
storyDescription:
|
|
245
|
-
"`SingleSelect` can be disabled by passing `disabled={true}`. This can be useful when you want to disable a control temporarily.",
|
|
246
|
-
},
|
|
247
|
-
};
|