@khanacademy/wonder-blocks-dropdown 2.3.19
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/LICENSE +21 -0
- package/dist/es/index.js +3403 -0
- package/dist/index.js +3966 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +12 -0
- package/package.json +44 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +4054 -0
- package/src/__tests__/generated-snapshot.test.js +1612 -0
- package/src/__tests__/index.test.js +23 -0
- package/src/components/__mocks__/dropdown-core-virtualized.js +40 -0
- package/src/components/__tests__/__snapshots__/action-item.test.js.snap +63 -0
- package/src/components/__tests__/action-item.test.js +43 -0
- package/src/components/__tests__/action-menu.test.js +544 -0
- package/src/components/__tests__/dropdown-core-virtualized.test.js +119 -0
- package/src/components/__tests__/dropdown-core.test.js +659 -0
- package/src/components/__tests__/multi-select.test.js +982 -0
- package/src/components/__tests__/search-text-input.test.js +144 -0
- package/src/components/__tests__/single-select.test.js +588 -0
- package/src/components/action-item.js +270 -0
- package/src/components/action-menu-opener-core.js +203 -0
- package/src/components/action-menu.js +300 -0
- package/src/components/action-menu.md +338 -0
- package/src/components/check.js +59 -0
- package/src/components/checkbox.js +111 -0
- package/src/components/dropdown-core-virtualized-item.js +62 -0
- package/src/components/dropdown-core-virtualized.js +246 -0
- package/src/components/dropdown-core.js +770 -0
- package/src/components/dropdown-opener.js +101 -0
- package/src/components/multi-select.js +597 -0
- package/src/components/multi-select.md +718 -0
- package/src/components/multi-select.stories.js +111 -0
- package/src/components/option-item.js +239 -0
- package/src/components/search-text-input.js +227 -0
- package/src/components/select-opener.js +297 -0
- package/src/components/separator-item.js +50 -0
- package/src/components/single-select.js +418 -0
- package/src/components/single-select.md +520 -0
- package/src/components/single-select.stories.js +107 -0
- package/src/index.js +20 -0
- package/src/util/constants.js +50 -0
- package/src/util/types.js +32 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {MultiSelect, OptionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
7
|
+
|
|
8
|
+
import type {Labels} from "@khanacademy/wonder-blocks-dropdown";
|
|
9
|
+
import type {StoryComponentType} from "@storybook/react";
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
title: "Dropdown / MultiSelect",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Custom MultiSelect labels
|
|
16
|
+
const dropdownLabels: $Shape<Labels> = {
|
|
17
|
+
noneSelected: "Solar system",
|
|
18
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} planets`,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type Props = {|
|
|
22
|
+
opened: boolean,
|
|
23
|
+
|};
|
|
24
|
+
|
|
25
|
+
type State = {|
|
|
26
|
+
opened: boolean,
|
|
27
|
+
selectedValues: Array<string>,
|
|
28
|
+
|};
|
|
29
|
+
|
|
30
|
+
type DefaultProps = {|
|
|
31
|
+
opened: $PropertyType<Props, "opened">,
|
|
32
|
+
|};
|
|
33
|
+
|
|
34
|
+
class MultiSelectWithCustomStyles extends React.Component<Props, State> {
|
|
35
|
+
static defaultProps: DefaultProps = {
|
|
36
|
+
opened: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
state: State = {
|
|
40
|
+
selectedValues: [],
|
|
41
|
+
opened: this.props.opened,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
handleChange: (update: Array<string>) => void = (update) => {
|
|
45
|
+
this.setState({
|
|
46
|
+
selectedValues: update,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
handleToggleMenu: (opened: boolean) => void = (opened) => {
|
|
51
|
+
this.setState({
|
|
52
|
+
opened,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
render(): React.Node {
|
|
57
|
+
return (
|
|
58
|
+
<View style={styles.wrapper}>
|
|
59
|
+
<MultiSelect
|
|
60
|
+
onChange={this.handleChange}
|
|
61
|
+
selectedValues={this.state.selectedValues}
|
|
62
|
+
style={styles.setWidth}
|
|
63
|
+
dropdownStyle={styles.customDropdown}
|
|
64
|
+
labels={dropdownLabels}
|
|
65
|
+
opened={this.state.opened}
|
|
66
|
+
onToggle={this.handleToggleMenu}
|
|
67
|
+
>
|
|
68
|
+
<OptionItem label="Mercury" value="1" />
|
|
69
|
+
<OptionItem label="Venus" value="2" />
|
|
70
|
+
<OptionItem label="Earth" value="3" disabled />
|
|
71
|
+
<OptionItem label="Mars" value="4" />
|
|
72
|
+
<OptionItem label="Jupiter" value="5" />
|
|
73
|
+
<OptionItem label="Saturn" value="6" />
|
|
74
|
+
<OptionItem label="Neptune" value="7" />
|
|
75
|
+
<OptionItem label="Uranus" value="8" />
|
|
76
|
+
</MultiSelect>
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const customStyles: StoryComponentType = () => (
|
|
83
|
+
<MultiSelectWithCustomStyles />
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
customStyles.story = {
|
|
87
|
+
parameters: {
|
|
88
|
+
chromatic: {
|
|
89
|
+
// we don't need screenshots because this story only tests behavior.
|
|
90
|
+
disable: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const customStylesOpened: StoryComponentType = () => (
|
|
96
|
+
<MultiSelectWithCustomStyles opened={true} />
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const styles = StyleSheet.create({
|
|
100
|
+
setWidth: {
|
|
101
|
+
minWidth: 170,
|
|
102
|
+
width: "100%",
|
|
103
|
+
},
|
|
104
|
+
customDropdown: {
|
|
105
|
+
maxHeight: 200,
|
|
106
|
+
},
|
|
107
|
+
wrapper: {
|
|
108
|
+
height: "800px",
|
|
109
|
+
width: "1200px",
|
|
110
|
+
},
|
|
111
|
+
});
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {StyleSheet} from "aphrodite";
|
|
5
|
+
import * as PropTypes from "prop-types";
|
|
6
|
+
|
|
7
|
+
import Color, {mix, fade} from "@khanacademy/wonder-blocks-color";
|
|
8
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
|
+
import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
|
|
10
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
11
|
+
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
12
|
+
|
|
13
|
+
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
14
|
+
|
|
15
|
+
import {DROPDOWN_ITEM_HEIGHT} from "../util/constants.js";
|
|
16
|
+
import Check from "./check.js";
|
|
17
|
+
import Checkbox from "./checkbox.js";
|
|
18
|
+
|
|
19
|
+
type OptionProps = {|
|
|
20
|
+
...AriaProps,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Display text of the option item.
|
|
24
|
+
*/
|
|
25
|
+
label: string,
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Value of the item, used as a key of sorts for the parent to manage its
|
|
29
|
+
* items, because label/display text may be identical for some selects. This
|
|
30
|
+
* is the value passed back when the item is selected.
|
|
31
|
+
*/
|
|
32
|
+
value: string,
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether this option item is disabled.
|
|
36
|
+
*/
|
|
37
|
+
disabled: boolean,
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Optional user-supplied callback when this item is called.
|
|
41
|
+
*/
|
|
42
|
+
onClick?: () => mixed,
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Callback for when this item is pressed to change its selection state.
|
|
46
|
+
* Passes value of the item. Auto-populated by menu or select.
|
|
47
|
+
* @ignore
|
|
48
|
+
*/
|
|
49
|
+
onToggle: (value: string) => mixed,
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether this item is selected. Auto-populated by menu or select.
|
|
53
|
+
* @ignore
|
|
54
|
+
*/
|
|
55
|
+
selected: boolean,
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Aria role to use, defaults to "option".
|
|
59
|
+
*/
|
|
60
|
+
role: "menuitem" | "option",
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Test ID used for e2e testing.
|
|
64
|
+
*/
|
|
65
|
+
testId?: string,
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the item should show a check or checkbox to indicate selection
|
|
69
|
+
* state. Auto-populated by menu or select.
|
|
70
|
+
* @ignore
|
|
71
|
+
*/
|
|
72
|
+
variant?: "check" | "checkbox",
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* In case we use react-window, this needs to be added in order to inject
|
|
76
|
+
* styles to calculate the position
|
|
77
|
+
* @ignore
|
|
78
|
+
*/
|
|
79
|
+
style?: StyleType,
|
|
80
|
+
|};
|
|
81
|
+
|
|
82
|
+
type ContextTypes = {|
|
|
83
|
+
router: $FlowFixMe,
|
|
84
|
+
|};
|
|
85
|
+
|
|
86
|
+
type DefaultProps = {|
|
|
87
|
+
disabled: $PropertyType<OptionProps, "disabled">,
|
|
88
|
+
onToggle: $PropertyType<OptionProps, "onToggle">,
|
|
89
|
+
role: $PropertyType<OptionProps, "role">,
|
|
90
|
+
selected: $PropertyType<OptionProps, "selected">,
|
|
91
|
+
|};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* For option items that can be selected in a dropdown, selection denoted either
|
|
95
|
+
* with a check ✔️ or a checkbox ☑️. Use as children in SingleSelect or
|
|
96
|
+
* MultiSelect.
|
|
97
|
+
*/
|
|
98
|
+
export default class OptionItem extends React.Component<OptionProps> {
|
|
99
|
+
static isClassOf(instance: React.Element<any>): boolean {
|
|
100
|
+
return instance && instance.type && instance.type.__IS_OPTION_ITEM__;
|
|
101
|
+
}
|
|
102
|
+
static contextTypes: ContextTypes = {router: PropTypes.any};
|
|
103
|
+
static defaultProps: DefaultProps = {
|
|
104
|
+
disabled: false,
|
|
105
|
+
onToggle: () => void 0,
|
|
106
|
+
role: "option",
|
|
107
|
+
selected: false,
|
|
108
|
+
};
|
|
109
|
+
static __IS_OPTION_ITEM__: boolean = true;
|
|
110
|
+
|
|
111
|
+
getCheckComponent(): typeof Check | typeof Checkbox {
|
|
112
|
+
if (this.props.variant === "check") {
|
|
113
|
+
return Check;
|
|
114
|
+
} else {
|
|
115
|
+
return Checkbox;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
handleClick: () => void = () => {
|
|
120
|
+
const {onClick, onToggle, value} = this.props;
|
|
121
|
+
onToggle(value);
|
|
122
|
+
if (onClick) {
|
|
123
|
+
onClick();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
render(): React.Node {
|
|
128
|
+
const {
|
|
129
|
+
disabled,
|
|
130
|
+
label,
|
|
131
|
+
role,
|
|
132
|
+
selected,
|
|
133
|
+
testId,
|
|
134
|
+
style,
|
|
135
|
+
// eslint-disable-next-line no-unused-vars
|
|
136
|
+
value,
|
|
137
|
+
/* eslint-disable no-unused-vars */
|
|
138
|
+
onClick,
|
|
139
|
+
onToggle,
|
|
140
|
+
variant,
|
|
141
|
+
/* eslint-enable no-unused-vars */
|
|
142
|
+
...sharedProps
|
|
143
|
+
} = this.props;
|
|
144
|
+
|
|
145
|
+
const ClickableBehavior = getClickableBehavior();
|
|
146
|
+
const CheckComponent = this.getCheckComponent();
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<ClickableBehavior
|
|
150
|
+
disabled={disabled}
|
|
151
|
+
onClick={this.handleClick}
|
|
152
|
+
role={role}
|
|
153
|
+
>
|
|
154
|
+
{(state, childrenProps) => {
|
|
155
|
+
const {pressed, hovered, focused} = state;
|
|
156
|
+
|
|
157
|
+
const defaultStyle = [
|
|
158
|
+
styles.itemContainer,
|
|
159
|
+
pressed
|
|
160
|
+
? styles.active
|
|
161
|
+
: (hovered || focused) && styles.focus,
|
|
162
|
+
disabled && styles.disabled,
|
|
163
|
+
// pass optional styles from react-window (if applies)
|
|
164
|
+
style,
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<View
|
|
169
|
+
{...sharedProps}
|
|
170
|
+
testId={testId}
|
|
171
|
+
style={defaultStyle}
|
|
172
|
+
aria-selected={selected ? "true" : "false"}
|
|
173
|
+
role={role}
|
|
174
|
+
{...childrenProps}
|
|
175
|
+
>
|
|
176
|
+
<CheckComponent
|
|
177
|
+
disabled={disabled}
|
|
178
|
+
selected={selected}
|
|
179
|
+
pressed={pressed}
|
|
180
|
+
hovered={hovered}
|
|
181
|
+
focused={focused}
|
|
182
|
+
/>
|
|
183
|
+
<LabelMedium style={styles.label}>
|
|
184
|
+
{label}
|
|
185
|
+
</LabelMedium>
|
|
186
|
+
</View>
|
|
187
|
+
);
|
|
188
|
+
}}
|
|
189
|
+
</ClickableBehavior>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const {blue, white, offBlack, offBlack32} = Color;
|
|
195
|
+
|
|
196
|
+
const styles = StyleSheet.create({
|
|
197
|
+
itemContainer: {
|
|
198
|
+
flexDirection: "row",
|
|
199
|
+
backgroundColor: white,
|
|
200
|
+
color: offBlack,
|
|
201
|
+
alignItems: "center",
|
|
202
|
+
height: DROPDOWN_ITEM_HEIGHT,
|
|
203
|
+
minHeight: DROPDOWN_ITEM_HEIGHT,
|
|
204
|
+
border: 0,
|
|
205
|
+
outline: 0,
|
|
206
|
+
paddingLeft: Spacing.xSmall_8,
|
|
207
|
+
paddingRight: Spacing.medium_16,
|
|
208
|
+
whiteSpace: "nowrap",
|
|
209
|
+
cursor: "default",
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
focus: {
|
|
213
|
+
color: white,
|
|
214
|
+
background: blue,
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
active: {
|
|
218
|
+
color: mix(fade(blue, 0.32), white),
|
|
219
|
+
background: mix(offBlack32, blue),
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
disabled: {
|
|
223
|
+
color: offBlack32,
|
|
224
|
+
background: white,
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
label: {
|
|
228
|
+
whiteSpace: "nowrap",
|
|
229
|
+
userSelect: "none",
|
|
230
|
+
marginLeft: Spacing.xSmall_8,
|
|
231
|
+
// added to truncate strings that are longer than expected
|
|
232
|
+
overflow: "hidden",
|
|
233
|
+
textOverflow: "ellipsis",
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
hide: {
|
|
237
|
+
visibility: "hidden",
|
|
238
|
+
},
|
|
239
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
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
|
+
import {StyleSheet, css} from "aphrodite";
|
|
6
|
+
|
|
7
|
+
import {styles as typographyStyles} from "@khanacademy/wonder-blocks-typography";
|
|
8
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
9
|
+
import IconButton from "@khanacademy/wonder-blocks-icon-button";
|
|
10
|
+
import Icon, {icons} from "@khanacademy/wonder-blocks-icon";
|
|
11
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
12
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
13
|
+
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
14
|
+
|
|
15
|
+
import {defaultLabels, DROPDOWN_ITEM_HEIGHT} from "../util/constants.js";
|
|
16
|
+
|
|
17
|
+
type Labels = {|
|
|
18
|
+
clearSearch: string,
|
|
19
|
+
filter: string,
|
|
20
|
+
|};
|
|
21
|
+
|
|
22
|
+
type Props = {|
|
|
23
|
+
/**
|
|
24
|
+
* The object containing the custom labels used inside this component.
|
|
25
|
+
*/
|
|
26
|
+
labels: Labels,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* the text input
|
|
30
|
+
*/
|
|
31
|
+
searchText: string,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Called when the input value is changed
|
|
35
|
+
*/
|
|
36
|
+
onChange: (searchText: string) => mixed,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handler that is triggered when this component is clicked. For example,
|
|
40
|
+
* use this to adjust focus in parent component. This gets called when we
|
|
41
|
+
* click the dismiss icon button within the SearchTextInput.
|
|
42
|
+
*/
|
|
43
|
+
onClick?: () => mixed,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Used to handle the focus order in its parent component. The itemRef is
|
|
47
|
+
* applied to the input directly.
|
|
48
|
+
*/
|
|
49
|
+
itemRef?: {|current: any|},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Custom styles for the main wrapper
|
|
53
|
+
*/
|
|
54
|
+
style?: StyleType,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Test ID used for e2e testing.
|
|
58
|
+
*/
|
|
59
|
+
testId?: string,
|
|
60
|
+
|};
|
|
61
|
+
|
|
62
|
+
type DefaultProps = {|
|
|
63
|
+
labels: $PropertyType<Props, "labels">,
|
|
64
|
+
|};
|
|
65
|
+
|
|
66
|
+
type State = {|
|
|
67
|
+
focused: boolean,
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The object containing the custom labels used inside this component.
|
|
71
|
+
*/
|
|
72
|
+
labels: Labels,
|
|
73
|
+
|};
|
|
74
|
+
|
|
75
|
+
export default class SearchTextInput extends React.Component<Props, State> {
|
|
76
|
+
static isClassOf(instance: React.Element<any>): boolean {
|
|
77
|
+
return (
|
|
78
|
+
instance && instance.type && instance.type.__IS_SEARCH_TEXT_INPUT__
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static defaultProps: DefaultProps = {
|
|
83
|
+
labels: {
|
|
84
|
+
clearSearch: defaultLabels.clearSearch,
|
|
85
|
+
filter: defaultLabels.filter,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
state: State = {
|
|
90
|
+
focused: false,
|
|
91
|
+
labels: {
|
|
92
|
+
clearSearch: defaultLabels.clearSearch,
|
|
93
|
+
filter: defaultLabels.filter,
|
|
94
|
+
...this.props.labels,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
componentDidUpdate(prevProps: Props) {
|
|
99
|
+
if (this.props.labels !== prevProps.labels) {
|
|
100
|
+
// eslint-disable-next-line react/no-did-update-set-state
|
|
101
|
+
this.setState({
|
|
102
|
+
labels: {...this.state.labels, ...this.props.labels},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static __IS_SEARCH_TEXT_INPUT__: boolean = true;
|
|
108
|
+
|
|
109
|
+
handleChange: (e: SyntheticInputEvent<>) => void = (e) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
this.props.onChange(e.target.value);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
handleDismiss: () => void = () => {
|
|
115
|
+
const {onClick, onChange} = this.props;
|
|
116
|
+
// Empty the search text and focus the SearchTextInput
|
|
117
|
+
onChange("");
|
|
118
|
+
if (onClick) {
|
|
119
|
+
onClick();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
handleBlur: (e: SyntheticInputEvent<>) => void = (e) => {
|
|
124
|
+
this.setState({focused: false});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
handleFocus: (e: SyntheticInputEvent<>) => void = (e) => {
|
|
128
|
+
this.setState({focused: true});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
maybeRenderDismissIconButton(): React.Node {
|
|
132
|
+
const {searchText} = this.props;
|
|
133
|
+
const {clearSearch} = this.state.labels;
|
|
134
|
+
|
|
135
|
+
if (searchText.length > 0) {
|
|
136
|
+
return (
|
|
137
|
+
<IconButton
|
|
138
|
+
icon={icons.dismiss}
|
|
139
|
+
kind="tertiary"
|
|
140
|
+
onClick={this.handleDismiss}
|
|
141
|
+
style={styles.dismissIcon}
|
|
142
|
+
aria-label={clearSearch}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
render(): React.Node {
|
|
150
|
+
const {onClick, itemRef, searchText, style, testId} = this.props;
|
|
151
|
+
const {filter} = this.state.labels;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<View
|
|
155
|
+
onClick={onClick}
|
|
156
|
+
style={[
|
|
157
|
+
styles.inputContainer,
|
|
158
|
+
this.state.focused && styles.focused,
|
|
159
|
+
style,
|
|
160
|
+
]}
|
|
161
|
+
>
|
|
162
|
+
<Icon
|
|
163
|
+
icon={icons.search}
|
|
164
|
+
size="medium"
|
|
165
|
+
color={Color.offBlack64}
|
|
166
|
+
style={styles.searchIcon}
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
/>
|
|
169
|
+
<input
|
|
170
|
+
type="text"
|
|
171
|
+
onChange={this.handleChange}
|
|
172
|
+
onFocus={this.handleFocus}
|
|
173
|
+
onBlur={this.handleBlur}
|
|
174
|
+
ref={itemRef}
|
|
175
|
+
placeholder={filter}
|
|
176
|
+
value={searchText}
|
|
177
|
+
className={css(
|
|
178
|
+
styles.inputStyleReset,
|
|
179
|
+
typographyStyles.LabelMedium,
|
|
180
|
+
)}
|
|
181
|
+
data-test-id={testId}
|
|
182
|
+
/>
|
|
183
|
+
{this.maybeRenderDismissIconButton()}
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const styles = StyleSheet.create({
|
|
190
|
+
inputContainer: {
|
|
191
|
+
flexDirection: "row",
|
|
192
|
+
border: `1px solid ${Color.offBlack16}`,
|
|
193
|
+
borderRadius: Spacing.xxxSmall_4,
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
// The height of the text input is 40 in design spec and we need to
|
|
196
|
+
// specify the height as well as minHeight to make sure the search text
|
|
197
|
+
// input takes enough height to render. (otherwise, it will get
|
|
198
|
+
// squashed)
|
|
199
|
+
height: DROPDOWN_ITEM_HEIGHT,
|
|
200
|
+
minHeight: DROPDOWN_ITEM_HEIGHT,
|
|
201
|
+
},
|
|
202
|
+
focused: {
|
|
203
|
+
border: `1px solid ${Color.blue}`,
|
|
204
|
+
},
|
|
205
|
+
searchIcon: {
|
|
206
|
+
marginLeft: Spacing.xSmall_8,
|
|
207
|
+
marginRight: Spacing.xSmall_8,
|
|
208
|
+
},
|
|
209
|
+
dismissIcon: {
|
|
210
|
+
margin: 0,
|
|
211
|
+
":hover": {
|
|
212
|
+
border: "none",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
inputStyleReset: {
|
|
216
|
+
display: "flex",
|
|
217
|
+
flex: 1,
|
|
218
|
+
background: "inherit",
|
|
219
|
+
border: "none",
|
|
220
|
+
outline: "none",
|
|
221
|
+
"::placeholder": {
|
|
222
|
+
color: Color.offBlack64,
|
|
223
|
+
},
|
|
224
|
+
width: "100%",
|
|
225
|
+
color: "inherit",
|
|
226
|
+
},
|
|
227
|
+
});
|