@patternfly/react-templates 1.1.0-prerelease.39 → 1.1.0-prerelease.40
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 +4 -0
- package/components/package.json +1 -1
- package/dist/dynamic/components/Dropdown/package.json +1 -1
- package/dist/dynamic/components/Select/package.json +1 -1
- package/dist/esm/components/Select/TypeaheadSelect.d.ts +7 -3
- package/dist/esm/components/Select/TypeaheadSelect.d.ts.map +1 -1
- package/dist/esm/components/Select/TypeaheadSelect.js +66 -58
- package/dist/esm/components/Select/TypeaheadSelect.js.map +1 -1
- package/dist/js/components/Select/TypeaheadSelect.d.ts +7 -3
- package/dist/js/components/Select/TypeaheadSelect.d.ts.map +1 -1
- package/dist/js/components/Select/TypeaheadSelect.js +66 -58
- package/dist/js/components/Select/TypeaheadSelect.js.map +1 -1
- package/dist/umd/assets/{output-DtM3geql.css → output-Cfyy993h.css} +3797 -3797
- package/dist/umd/react-templates.min.js +1 -1
- package/package.json +2 -2
- package/src/components/Select/TypeaheadSelect.tsx +69 -61
- package/src/components/Select/__tests__/TypeaheadSelect.test.tsx +36 -72
- package/src/components/Select/__tests__/__snapshots__/TypeaheadSelect.test.tsx.snap +3 -3
- package/src/components/Select/examples/SelectTemplates.md +3 -0
- package/src/components/Select/examples/TypeaheadSelectDemo.tsx +9 -7
|
@@ -15,18 +15,20 @@ import {
|
|
|
15
15
|
} from '@patternfly/react-core';
|
|
16
16
|
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
17
17
|
|
|
18
|
-
export interface TypeaheadSelectOption extends Omit<SelectOptionProps, 'content'> {
|
|
18
|
+
export interface TypeaheadSelectOption extends Omit<SelectOptionProps, 'content' | 'isSelected'> {
|
|
19
19
|
/** Content of the select option. */
|
|
20
20
|
content: string | number;
|
|
21
21
|
/** Value of the select option. */
|
|
22
22
|
value: string | number;
|
|
23
|
+
/** Indicator for option being selected */
|
|
24
|
+
isSelected?: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle' | 'onSelect'> {
|
|
26
28
|
/** @hide Forwarded ref */
|
|
27
29
|
innerRef?: React.Ref<any>;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
+
/** Options of the select */
|
|
31
|
+
selectOptions: TypeaheadSelectOption[];
|
|
30
32
|
/** Callback triggered on selection. */
|
|
31
33
|
onSelect?: (
|
|
32
34
|
_event: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<HTMLInputElement> | undefined,
|
|
@@ -36,6 +38,8 @@ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle' | 'onSe
|
|
|
36
38
|
onToggle?: (nextIsOpen: boolean) => void;
|
|
37
39
|
/** Callback triggered when the text in the input field changes. */
|
|
38
40
|
onInputChange?: (newValue: string) => void;
|
|
41
|
+
/** Function to return items matching the current filter value */
|
|
42
|
+
filterFunction?: (filterValue: string, options: TypeaheadSelectOption[]) => TypeaheadSelectOption[];
|
|
39
43
|
/** Callback triggered when the clear button is selected */
|
|
40
44
|
onClearSelection?: () => void;
|
|
41
45
|
/** Placeholder text for the select input. */
|
|
@@ -61,12 +65,16 @@ export interface TypeaheadSelectProps extends Omit<SelectProps, 'toggle' | 'onSe
|
|
|
61
65
|
const defaultNoOptionsFoundMessage = (filter: string) => `No results found for "${filter}"`;
|
|
62
66
|
const defaultCreateOptionMessage = (newValue: string) => `Create "${newValue}"`;
|
|
63
67
|
|
|
68
|
+
const defaultFilterFunction = (filterValue: string, options: TypeaheadSelectOption[]) =>
|
|
69
|
+
options.filter((o) => String(o.content).toLowerCase().includes(filterValue.toLowerCase()));
|
|
70
|
+
|
|
64
71
|
export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps> = ({
|
|
65
72
|
innerRef,
|
|
66
|
-
|
|
73
|
+
selectOptions,
|
|
67
74
|
onSelect,
|
|
68
75
|
onToggle,
|
|
69
76
|
onInputChange,
|
|
77
|
+
filterFunction = defaultFilterFunction,
|
|
70
78
|
onClearSelection,
|
|
71
79
|
placeholder = 'Select an option',
|
|
72
80
|
noOptionsAvailableMessage = 'No options are available',
|
|
@@ -80,31 +88,30 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
80
88
|
...props
|
|
81
89
|
}: TypeaheadSelectProps) => {
|
|
82
90
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
83
|
-
const [selected, setSelected] = React.useState<string>(String(initialOptions.find((o) => o.selected)?.content ?? ''));
|
|
84
|
-
const [inputValue, setInputValue] = React.useState<string>(
|
|
85
|
-
String(initialOptions.find((o) => o.selected)?.content ?? '')
|
|
86
|
-
);
|
|
87
91
|
const [filterValue, setFilterValue] = React.useState<string>('');
|
|
88
|
-
const [
|
|
92
|
+
const [isFiltering, setIsFiltering] = React.useState<boolean>(false);
|
|
89
93
|
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
90
94
|
const [activeItemId, setActiveItemId] = React.useState<string | null>(null);
|
|
91
95
|
const textInputRef = React.useRef<HTMLInputElement>();
|
|
92
96
|
|
|
93
97
|
const NO_RESULTS = 'no results';
|
|
94
98
|
|
|
95
|
-
React.
|
|
96
|
-
|
|
99
|
+
const selected = React.useMemo(
|
|
100
|
+
() => selectOptions?.find((option) => option.value === props.selected || option.isSelected),
|
|
101
|
+
[props.selected, selectOptions]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const filteredSelections = React.useMemo(() => {
|
|
105
|
+
let newSelectOptions: TypeaheadSelectOption[] = selectOptions;
|
|
97
106
|
|
|
98
107
|
// Filter menu items based on the text input value when one exists
|
|
99
|
-
if (filterValue) {
|
|
100
|
-
newSelectOptions =
|
|
101
|
-
String(option.content).toLowerCase().includes(filterValue.toLowerCase())
|
|
102
|
-
);
|
|
108
|
+
if (isFiltering && filterValue) {
|
|
109
|
+
newSelectOptions = filterFunction(filterValue, selectOptions);
|
|
103
110
|
|
|
104
111
|
if (
|
|
105
112
|
isCreatable &&
|
|
106
|
-
filterValue &&
|
|
107
|
-
!
|
|
113
|
+
filterValue.trim() &&
|
|
114
|
+
!newSelectOptions.find((o) => String(o.content).toLowerCase() === filterValue.toLowerCase())
|
|
108
115
|
) {
|
|
109
116
|
const createOption = {
|
|
110
117
|
content: typeof createOptionMessage === 'string' ? createOptionMessage : createOptionMessage(filterValue),
|
|
@@ -126,9 +133,6 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
126
133
|
}
|
|
127
134
|
];
|
|
128
135
|
}
|
|
129
|
-
|
|
130
|
-
// Open the menu when the input value changes and the new value is not empty
|
|
131
|
-
openMenu();
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
// When no options are available, display 'No options available'
|
|
@@ -142,10 +146,12 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
142
146
|
];
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
return newSelectOptions;
|
|
146
150
|
}, [
|
|
151
|
+
isFiltering,
|
|
147
152
|
filterValue,
|
|
148
|
-
|
|
153
|
+
filterFunction,
|
|
154
|
+
selectOptions,
|
|
149
155
|
noOptionsFoundMessage,
|
|
150
156
|
isCreatable,
|
|
151
157
|
isCreateOptionOnTop,
|
|
@@ -154,14 +160,12 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
154
160
|
]);
|
|
155
161
|
|
|
156
162
|
React.useEffect(() => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (inputValue === selected && selectedOption?.value !== selected) {
|
|
160
|
-
setInputValue(String(selectedOption?.content ?? ''));
|
|
163
|
+
if (isFiltering) {
|
|
164
|
+
openMenu();
|
|
161
165
|
}
|
|
162
|
-
//
|
|
166
|
+
// Don't update on openMenu changes
|
|
163
167
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
-
}, [
|
|
168
|
+
}, [isFiltering]);
|
|
165
169
|
|
|
166
170
|
const setActiveAndFocusedItem = (itemIndex: number) => {
|
|
167
171
|
setFocusedItemIndex(itemIndex);
|
|
@@ -178,6 +182,9 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
178
182
|
if (!isOpen) {
|
|
179
183
|
onToggle && onToggle(true);
|
|
180
184
|
setIsOpen(true);
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
textInputRef.current?.focus();
|
|
187
|
+
}, 100);
|
|
181
188
|
}
|
|
182
189
|
};
|
|
183
190
|
|
|
@@ -185,16 +192,14 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
185
192
|
onToggle && onToggle(false);
|
|
186
193
|
setIsOpen(false);
|
|
187
194
|
resetActiveAndFocusedItem();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
setInputValue(String(option.content));
|
|
191
|
-
}
|
|
195
|
+
setIsFiltering(false);
|
|
196
|
+
setFilterValue(String(selected?.content ?? ''));
|
|
192
197
|
};
|
|
193
198
|
|
|
194
199
|
const onInputClick = () => {
|
|
195
200
|
if (!isOpen) {
|
|
196
201
|
openMenu();
|
|
197
|
-
} else if (
|
|
202
|
+
} else if (isFiltering) {
|
|
198
203
|
closeMenu();
|
|
199
204
|
}
|
|
200
205
|
};
|
|
@@ -204,25 +209,24 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
204
209
|
option: TypeaheadSelectOption
|
|
205
210
|
) => {
|
|
206
211
|
onSelect && onSelect(_event, option.value);
|
|
207
|
-
|
|
208
|
-
setInputValue(String(option.content));
|
|
209
|
-
setFilterValue('');
|
|
210
|
-
setSelected(String(option.value));
|
|
211
|
-
|
|
212
212
|
closeMenu();
|
|
213
213
|
};
|
|
214
214
|
|
|
215
215
|
const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
|
|
216
216
|
if (value && value !== NO_RESULTS) {
|
|
217
217
|
const optionToSelect = selectOptions.find((option) => option.value === value);
|
|
218
|
-
|
|
218
|
+
if (optionToSelect) {
|
|
219
|
+
selectOption(_event, optionToSelect);
|
|
220
|
+
} else if (isCreatable) {
|
|
221
|
+
selectOption(_event, { value, content: value });
|
|
222
|
+
}
|
|
219
223
|
}
|
|
220
224
|
};
|
|
221
225
|
|
|
222
226
|
const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
|
|
223
|
-
|
|
227
|
+
setIsFiltering(true);
|
|
228
|
+
setFilterValue(value || '');
|
|
224
229
|
onInputChange && onInputChange(value);
|
|
225
|
-
setFilterValue(value);
|
|
226
230
|
|
|
227
231
|
resetActiveAndFocusedItem();
|
|
228
232
|
};
|
|
@@ -232,39 +236,39 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
232
236
|
|
|
233
237
|
openMenu();
|
|
234
238
|
|
|
235
|
-
if (
|
|
239
|
+
if (filteredSelections.every((option) => option.isDisabled)) {
|
|
236
240
|
return;
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
if (key === 'ArrowUp') {
|
|
240
244
|
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
241
245
|
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
242
|
-
indexToFocus =
|
|
246
|
+
indexToFocus = filteredSelections.length - 1;
|
|
243
247
|
} else {
|
|
244
248
|
indexToFocus = focusedItemIndex - 1;
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
// Skip disabled options
|
|
248
|
-
while (
|
|
252
|
+
while (filteredSelections[indexToFocus].isDisabled) {
|
|
249
253
|
indexToFocus--;
|
|
250
254
|
if (indexToFocus === -1) {
|
|
251
|
-
indexToFocus =
|
|
255
|
+
indexToFocus = filteredSelections.length - 1;
|
|
252
256
|
}
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
if (key === 'ArrowDown') {
|
|
257
261
|
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
258
|
-
if (focusedItemIndex === null || focusedItemIndex ===
|
|
262
|
+
if (focusedItemIndex === null || focusedItemIndex === filteredSelections.length - 1) {
|
|
259
263
|
indexToFocus = 0;
|
|
260
264
|
} else {
|
|
261
265
|
indexToFocus = focusedItemIndex + 1;
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
// Skip disabled options
|
|
265
|
-
while (
|
|
269
|
+
while (filteredSelections[indexToFocus].isDisabled) {
|
|
266
270
|
indexToFocus++;
|
|
267
|
-
if (indexToFocus ===
|
|
271
|
+
if (indexToFocus === filteredSelections.length) {
|
|
268
272
|
indexToFocus = 0;
|
|
269
273
|
}
|
|
270
274
|
}
|
|
@@ -274,7 +278,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
274
278
|
};
|
|
275
279
|
|
|
276
280
|
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
277
|
-
const focusedItem = focusedItemIndex !== null ?
|
|
281
|
+
const focusedItem = focusedItemIndex !== null ? filteredSelections[focusedItemIndex] : null;
|
|
278
282
|
|
|
279
283
|
switch (event.key) {
|
|
280
284
|
case 'Enter':
|
|
@@ -294,16 +298,21 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
294
298
|
};
|
|
295
299
|
|
|
296
300
|
const onToggleClick = () => {
|
|
297
|
-
|
|
298
|
-
|
|
301
|
+
if (!isOpen) {
|
|
302
|
+
openMenu();
|
|
303
|
+
} else {
|
|
304
|
+
closeMenu();
|
|
305
|
+
}
|
|
299
306
|
textInputRef.current?.focus();
|
|
300
307
|
};
|
|
301
308
|
|
|
302
309
|
const onClearButtonClick = () => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
310
|
+
if (selected && onSelect) {
|
|
311
|
+
onSelect(undefined, selected.value);
|
|
312
|
+
}
|
|
306
313
|
setFilterValue('');
|
|
314
|
+
onInputChange && onInputChange('');
|
|
315
|
+
setIsFiltering(false);
|
|
307
316
|
resetActiveAndFocusedItem();
|
|
308
317
|
textInputRef.current?.focus();
|
|
309
318
|
onClearSelection && onClearSelection();
|
|
@@ -327,7 +336,7 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
327
336
|
>
|
|
328
337
|
<TextInputGroup isPlain>
|
|
329
338
|
<TextInputGroupMain
|
|
330
|
-
value={
|
|
339
|
+
value={isFiltering ? filterValue : (selected?.content ?? '')}
|
|
331
340
|
onClick={onInputClick}
|
|
332
341
|
onChange={onTextInputChange}
|
|
333
342
|
onKeyDown={onInputKeyDown}
|
|
@@ -339,8 +348,9 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
339
348
|
isExpanded={isOpen}
|
|
340
349
|
aria-controls="select-typeahead-listbox"
|
|
341
350
|
/>
|
|
342
|
-
|
|
343
|
-
|
|
351
|
+
<TextInputGroupUtilities
|
|
352
|
+
{...(!(isFiltering && filterValue) && !selected ? { style: { display: 'none' } } : {})}
|
|
353
|
+
>
|
|
344
354
|
<Button variant="plain" onClick={onClearButtonClick} aria-label="Clear input value">
|
|
345
355
|
<TimesIcon aria-hidden />
|
|
346
356
|
</Button>
|
|
@@ -354,16 +364,14 @@ export const TypeaheadSelectBase: React.FunctionComponent<TypeaheadSelectProps>
|
|
|
354
364
|
isOpen={isOpen}
|
|
355
365
|
selected={selected}
|
|
356
366
|
onSelect={_onSelect}
|
|
357
|
-
onOpenChange={(isOpen) =>
|
|
358
|
-
!isOpen && closeMenu();
|
|
359
|
-
}}
|
|
367
|
+
onOpenChange={(isOpen) => !isOpen && closeMenu()}
|
|
360
368
|
toggle={toggle}
|
|
361
369
|
shouldFocusFirstItemOnOpen={false}
|
|
362
370
|
ref={innerRef}
|
|
363
371
|
{...props}
|
|
364
372
|
>
|
|
365
373
|
<SelectList>
|
|
366
|
-
{
|
|
374
|
+
{filteredSelections.map((option, index) => {
|
|
367
375
|
const { content, value, ...props } = option;
|
|
368
376
|
|
|
369
377
|
return (
|
|
@@ -5,7 +5,7 @@ import { TypeaheadSelect } from '../TypeaheadSelect';
|
|
|
5
5
|
import styles from '@patternfly/react-styles/css/components/Menu/menu';
|
|
6
6
|
|
|
7
7
|
test('renders typeahead select with options', async () => {
|
|
8
|
-
const
|
|
8
|
+
const selectOptions = [
|
|
9
9
|
{ content: 'Option 1', value: 'option1' },
|
|
10
10
|
{ content: 'Option 2', value: 'option2' },
|
|
11
11
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -13,7 +13,7 @@ test('renders typeahead select with options', async () => {
|
|
|
13
13
|
|
|
14
14
|
const user = userEvent.setup();
|
|
15
15
|
|
|
16
|
-
render(<TypeaheadSelect
|
|
16
|
+
render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
17
17
|
|
|
18
18
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
19
19
|
|
|
@@ -28,8 +28,8 @@ test('renders typeahead select with options', async () => {
|
|
|
28
28
|
expect(option3).toBeInTheDocument();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test('
|
|
32
|
-
const
|
|
31
|
+
test('shows the selected item as selected', async () => {
|
|
32
|
+
const selectOptions = [
|
|
33
33
|
{ content: 'Option 1', value: 'option1' },
|
|
34
34
|
{ content: 'Option 2', value: 'option2' },
|
|
35
35
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -37,7 +37,7 @@ test('selects options when clicked', async () => {
|
|
|
37
37
|
|
|
38
38
|
const user = userEvent.setup();
|
|
39
39
|
|
|
40
|
-
render(<TypeaheadSelect
|
|
40
|
+
render(<TypeaheadSelect selectOptions={selectOptions} selected="option1" />);
|
|
41
41
|
|
|
42
42
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
43
43
|
|
|
@@ -45,15 +45,11 @@ test('selects options when clicked', async () => {
|
|
|
45
45
|
|
|
46
46
|
const option1 = screen.getByRole('option', { name: 'Option 1' });
|
|
47
47
|
|
|
48
|
-
expect(option1).not.toHaveClass(styles.modifiers.selected);
|
|
49
|
-
|
|
50
|
-
await user.click(option1);
|
|
51
|
-
|
|
52
48
|
expect(option1).toHaveClass(styles.modifiers.selected);
|
|
53
49
|
});
|
|
54
50
|
|
|
55
51
|
test('calls the onSelect callback with the selected value when an option is selected', async () => {
|
|
56
|
-
const
|
|
52
|
+
const selectOptions = [
|
|
57
53
|
{ content: 'Option 1', value: 'option1' },
|
|
58
54
|
{ content: 'Option 2', value: 'option2' },
|
|
59
55
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -62,7 +58,7 @@ test('calls the onSelect callback with the selected value when an option is sele
|
|
|
62
58
|
const user = userEvent.setup();
|
|
63
59
|
const onSelectMock = jest.fn();
|
|
64
60
|
|
|
65
|
-
render(<TypeaheadSelect
|
|
61
|
+
render(<TypeaheadSelect selectOptions={selectOptions} onSelect={onSelectMock} />);
|
|
66
62
|
|
|
67
63
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
68
64
|
|
|
@@ -78,23 +74,23 @@ test('calls the onSelect callback with the selected value when an option is sele
|
|
|
78
74
|
expect(onSelectMock).toHaveBeenCalledWith(expect.anything(), 'option1');
|
|
79
75
|
});
|
|
80
76
|
|
|
81
|
-
test('
|
|
82
|
-
const
|
|
77
|
+
test('calls the onClearSelection callback when clear button is clicked', async () => {
|
|
78
|
+
const selectOptions = [
|
|
83
79
|
{ content: 'Option 1', value: 'option1' },
|
|
84
80
|
{ content: 'Option 2', value: 'option2' },
|
|
85
81
|
{ content: 'Option 3', value: 'option3' }
|
|
86
82
|
];
|
|
83
|
+
const onClearSelectionMock = jest.fn();
|
|
87
84
|
|
|
88
85
|
const user = userEvent.setup();
|
|
89
86
|
|
|
90
|
-
render(<TypeaheadSelect
|
|
87
|
+
render(<TypeaheadSelect selectOptions={selectOptions} selected="option1" onClearSelection={onClearSelectionMock} />);
|
|
91
88
|
|
|
92
89
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
93
90
|
|
|
94
91
|
await user.click(toggle);
|
|
95
92
|
|
|
96
93
|
const option1 = screen.getByRole('option', { name: 'Option 1' });
|
|
97
|
-
await user.click(option1);
|
|
98
94
|
expect(option1).toHaveClass(styles.modifiers.selected);
|
|
99
95
|
|
|
100
96
|
const input = screen.getByRole('combobox');
|
|
@@ -102,14 +98,12 @@ test('deselects options when clear button is clicked', async () => {
|
|
|
102
98
|
|
|
103
99
|
const clearButton = screen.getByRole('button', { name: 'Clear input value' });
|
|
104
100
|
await user.click(clearButton);
|
|
105
|
-
expect(input).toHaveValue('');
|
|
106
101
|
|
|
107
|
-
|
|
108
|
-
expect(screen.getByRole('option', { name: 'Option 1' })).not.toHaveClass(styles.modifiers.selected);
|
|
102
|
+
expect(onClearSelectionMock).toHaveBeenCalledTimes(1);
|
|
109
103
|
});
|
|
110
104
|
|
|
111
105
|
test('toggles the select menu when the toggle button is clicked', async () => {
|
|
112
|
-
const
|
|
106
|
+
const selectOptions = [
|
|
113
107
|
{ content: 'Option 1', value: 'option1' },
|
|
114
108
|
{ content: 'Option 2', value: 'option2' },
|
|
115
109
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -117,7 +111,7 @@ test('toggles the select menu when the toggle button is clicked', async () => {
|
|
|
117
111
|
|
|
118
112
|
const user = userEvent.setup();
|
|
119
113
|
|
|
120
|
-
render(<TypeaheadSelect
|
|
114
|
+
render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
121
115
|
|
|
122
116
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
123
117
|
|
|
@@ -134,7 +128,7 @@ test('toggles the select menu when the toggle button is clicked', async () => {
|
|
|
134
128
|
});
|
|
135
129
|
|
|
136
130
|
test('calls the onToggle callback when the toggle is clicked', async () => {
|
|
137
|
-
const
|
|
131
|
+
const selectOptions = [
|
|
138
132
|
{ content: 'Option 1', value: 'option1' },
|
|
139
133
|
{ content: 'Option 2', value: 'option2' },
|
|
140
134
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -143,7 +137,7 @@ test('calls the onToggle callback when the toggle is clicked', async () => {
|
|
|
143
137
|
const user = userEvent.setup();
|
|
144
138
|
const onToggleMock = jest.fn();
|
|
145
139
|
|
|
146
|
-
render(<TypeaheadSelect
|
|
140
|
+
render(<TypeaheadSelect selectOptions={selectOptions} onToggle={onToggleMock} />);
|
|
147
141
|
|
|
148
142
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
149
143
|
|
|
@@ -161,39 +155,39 @@ test('calls the onToggle callback when the toggle is clicked', async () => {
|
|
|
161
155
|
});
|
|
162
156
|
|
|
163
157
|
test('Passes toggleWidth', () => {
|
|
164
|
-
const
|
|
158
|
+
const selectOptions = [
|
|
165
159
|
{ content: 'Option 1', value: 'option1' },
|
|
166
160
|
{ content: 'Option 2', value: 'option2' },
|
|
167
161
|
{ content: 'Option 3', value: 'option3' }
|
|
168
162
|
];
|
|
169
163
|
|
|
170
|
-
render(<TypeaheadSelect
|
|
164
|
+
render(<TypeaheadSelect selectOptions={selectOptions} toggleWidth="500px" />);
|
|
171
165
|
|
|
172
166
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
173
167
|
expect(toggle.parentElement).toHaveAttribute('style', 'width: 500px;');
|
|
174
168
|
});
|
|
175
169
|
|
|
176
170
|
test('Passes additional toggleProps', () => {
|
|
177
|
-
const
|
|
171
|
+
const selectOptions = [
|
|
178
172
|
{ content: 'Option 1', value: 'option1' },
|
|
179
173
|
{ content: 'Option 2', value: 'option2' },
|
|
180
174
|
{ content: 'Option 3', value: 'option3' }
|
|
181
175
|
];
|
|
182
176
|
|
|
183
|
-
render(<TypeaheadSelect
|
|
177
|
+
render(<TypeaheadSelect selectOptions={selectOptions} toggleProps={{ id: 'toggle' }} />);
|
|
184
178
|
|
|
185
179
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
186
180
|
expect(toggle.parentElement).toHaveAttribute('id', 'toggle');
|
|
187
181
|
});
|
|
188
182
|
|
|
189
183
|
test('passes custom placeholder text', async () => {
|
|
190
|
-
const
|
|
184
|
+
const selectOptions = [
|
|
191
185
|
{ content: 'Option 1', value: 'option1' },
|
|
192
186
|
{ content: 'Option 2', value: 'option2' },
|
|
193
187
|
{ content: 'Option 3', value: 'option3' }
|
|
194
188
|
];
|
|
195
189
|
|
|
196
|
-
render(<TypeaheadSelect
|
|
190
|
+
render(<TypeaheadSelect selectOptions={selectOptions} placeholder="custom" />);
|
|
197
191
|
|
|
198
192
|
const input = screen.getByRole('combobox');
|
|
199
193
|
|
|
@@ -201,14 +195,14 @@ test('passes custom placeholder text', async () => {
|
|
|
201
195
|
});
|
|
202
196
|
|
|
203
197
|
test('displays noOptionsFoundMessage when filter returns no results', async () => {
|
|
204
|
-
const
|
|
198
|
+
const selectOptions = [
|
|
205
199
|
{ content: 'Option 1', value: 'option1' },
|
|
206
200
|
{ content: 'Option 2', value: 'option2' },
|
|
207
201
|
{ content: 'Option 3', value: 'option3' }
|
|
208
202
|
];
|
|
209
203
|
const user = userEvent.setup();
|
|
210
204
|
|
|
211
|
-
render(<TypeaheadSelect
|
|
205
|
+
render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
212
206
|
|
|
213
207
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
214
208
|
const input = screen.getByRole('combobox');
|
|
@@ -235,14 +229,14 @@ test('displays noOptionsFoundMessage when filter returns no results', async () =
|
|
|
235
229
|
});
|
|
236
230
|
|
|
237
231
|
test('displays custom noOptionsFoundMessage', async () => {
|
|
238
|
-
const
|
|
232
|
+
const selectOptions = [
|
|
239
233
|
{ content: 'Option 1', value: 'option1' },
|
|
240
234
|
{ content: 'Option 2', value: 'option2' },
|
|
241
235
|
{ content: 'Option 3', value: 'option3' }
|
|
242
236
|
];
|
|
243
237
|
const user = userEvent.setup();
|
|
244
238
|
|
|
245
|
-
render(<TypeaheadSelect
|
|
239
|
+
render(<TypeaheadSelect selectOptions={selectOptions} noOptionsFoundMessage="custom" />);
|
|
246
240
|
|
|
247
241
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
248
242
|
const input = screen.getByRole('combobox');
|
|
@@ -269,7 +263,7 @@ test('displays custom noOptionsFoundMessage', async () => {
|
|
|
269
263
|
});
|
|
270
264
|
|
|
271
265
|
test('disables the select when isDisabled prop is true', async () => {
|
|
272
|
-
const
|
|
266
|
+
const selectOptions = [
|
|
273
267
|
{ content: 'Option 1', value: 'option1' },
|
|
274
268
|
{ content: 'Option 2', value: 'option2' },
|
|
275
269
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -277,7 +271,7 @@ test('disables the select when isDisabled prop is true', async () => {
|
|
|
277
271
|
|
|
278
272
|
const user = userEvent.setup();
|
|
279
273
|
|
|
280
|
-
render(<TypeaheadSelect
|
|
274
|
+
render(<TypeaheadSelect selectOptions={selectOptions} isDisabled={true} />);
|
|
281
275
|
|
|
282
276
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
283
277
|
|
|
@@ -289,11 +283,11 @@ test('disables the select when isDisabled prop is true', async () => {
|
|
|
289
283
|
});
|
|
290
284
|
|
|
291
285
|
test('passes other SelectOption props to the SelectOption component', async () => {
|
|
292
|
-
const
|
|
286
|
+
const selectOptions = [{ content: 'Option 1', value: 'option1', isDisabled: true }];
|
|
293
287
|
|
|
294
288
|
const user = userEvent.setup();
|
|
295
289
|
|
|
296
|
-
render(<TypeaheadSelect
|
|
290
|
+
render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
297
291
|
|
|
298
292
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
299
293
|
|
|
@@ -303,38 +297,8 @@ test('passes other SelectOption props to the SelectOption component', async () =
|
|
|
303
297
|
expect(option1).toBeDisabled();
|
|
304
298
|
});
|
|
305
299
|
|
|
306
|
-
test('displays the option in input when option is selected', async () => {
|
|
307
|
-
const initialOptions = [
|
|
308
|
-
{ content: 'Option 1', value: 'option1' },
|
|
309
|
-
{ content: 'Option 2', value: 'option2' },
|
|
310
|
-
{ content: 'Option 3', value: 'option3' }
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
const user = userEvent.setup();
|
|
314
|
-
|
|
315
|
-
render(<TypeaheadSelect initialOptions={initialOptions} />);
|
|
316
|
-
|
|
317
|
-
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
318
|
-
const input = screen.getByRole('combobox');
|
|
319
|
-
|
|
320
|
-
expect(input).toHaveAttribute('placeholder', 'Select an option');
|
|
321
|
-
|
|
322
|
-
await user.click(toggle);
|
|
323
|
-
|
|
324
|
-
const option1 = screen.getByRole('option', { name: 'Option 1' });
|
|
325
|
-
|
|
326
|
-
await user.click(option1);
|
|
327
|
-
|
|
328
|
-
expect(input).toHaveValue('Option 1');
|
|
329
|
-
|
|
330
|
-
const clearButton = screen.getByRole('button', { name: 'Clear input value' });
|
|
331
|
-
await user.click(clearButton);
|
|
332
|
-
|
|
333
|
-
expect(input).toHaveValue('');
|
|
334
|
-
});
|
|
335
|
-
|
|
336
300
|
test('typing in input filters options', async () => {
|
|
337
|
-
const
|
|
301
|
+
const selectOptions = [
|
|
338
302
|
{ content: 'Option 1', value: 'option1' },
|
|
339
303
|
{ content: 'Option 2', value: 'option2' },
|
|
340
304
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -342,7 +306,7 @@ test('typing in input filters options', async () => {
|
|
|
342
306
|
|
|
343
307
|
const user = userEvent.setup();
|
|
344
308
|
|
|
345
|
-
render(<TypeaheadSelect
|
|
309
|
+
render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
346
310
|
|
|
347
311
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
348
312
|
const input = screen.getByRole('combobox');
|
|
@@ -368,7 +332,7 @@ test('typing in input filters options', async () => {
|
|
|
368
332
|
});
|
|
369
333
|
|
|
370
334
|
test('typing in input triggers onInputChange callback', async () => {
|
|
371
|
-
const
|
|
335
|
+
const selectOptions = [
|
|
372
336
|
{ content: 'Option 1', value: 'option1' },
|
|
373
337
|
{ content: 'Option 2', value: 'option2' },
|
|
374
338
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -377,7 +341,7 @@ test('typing in input triggers onInputChange callback', async () => {
|
|
|
377
341
|
const user = userEvent.setup();
|
|
378
342
|
const onInputChangeMock = jest.fn();
|
|
379
343
|
|
|
380
|
-
render(<TypeaheadSelect
|
|
344
|
+
render(<TypeaheadSelect selectOptions={selectOptions} onInputChange={onInputChangeMock} />);
|
|
381
345
|
|
|
382
346
|
const input = screen.getByRole('combobox');
|
|
383
347
|
|
|
@@ -391,7 +355,7 @@ test('typing in input triggers onInputChange callback', async () => {
|
|
|
391
355
|
});
|
|
392
356
|
|
|
393
357
|
test('Matches snapshot', async () => {
|
|
394
|
-
const
|
|
358
|
+
const selectOptions = [
|
|
395
359
|
{ content: 'Option 1', value: 'option1' },
|
|
396
360
|
{ content: 'Option 2', value: 'option2' },
|
|
397
361
|
{ content: 'Option 3', value: 'option3' }
|
|
@@ -399,7 +363,7 @@ test('Matches snapshot', async () => {
|
|
|
399
363
|
|
|
400
364
|
const user = userEvent.setup();
|
|
401
365
|
|
|
402
|
-
const { asFragment } = render(<TypeaheadSelect
|
|
366
|
+
const { asFragment } = render(<TypeaheadSelect selectOptions={selectOptions} />);
|
|
403
367
|
|
|
404
368
|
const toggle = screen.getByRole('button', { name: 'Typeahead menu toggle' });
|
|
405
369
|
await user.click(toggle);
|
|
@@ -35,7 +35,7 @@ exports[`Matches snapshot 1`] = `
|
|
|
35
35
|
aria-disabled="false"
|
|
36
36
|
aria-label="Clear input value"
|
|
37
37
|
class="pf-v5-c-button pf-m-plain"
|
|
38
|
-
data-ouia-component-id="OUIA-Generated-Button-plain-
|
|
38
|
+
data-ouia-component-id="OUIA-Generated-Button-plain-16"
|
|
39
39
|
data-ouia-component-type="PF5/Button"
|
|
40
40
|
data-ouia-safe="true"
|
|
41
41
|
type="button"
|
|
@@ -60,7 +60,7 @@ exports[`Matches snapshot 1`] = `
|
|
|
60
60
|
aria-expanded="true"
|
|
61
61
|
aria-label="Typeahead menu toggle"
|
|
62
62
|
class="pf-v5-c-menu-toggle__button"
|
|
63
|
-
data-ouia-component-id="OUIA-Generated-MenuToggle-typeahead-
|
|
63
|
+
data-ouia-component-id="OUIA-Generated-MenuToggle-typeahead-16"
|
|
64
64
|
data-ouia-component-type="PF5/MenuToggle"
|
|
65
65
|
data-ouia-safe="true"
|
|
66
66
|
tabindex="-1"
|
|
@@ -91,7 +91,7 @@ exports[`Matches snapshot 1`] = `
|
|
|
91
91
|
</div>
|
|
92
92
|
<div
|
|
93
93
|
class="pf-v5-c-menu"
|
|
94
|
-
data-ouia-component-id="OUIA-Generated-Select-
|
|
94
|
+
data-ouia-component-id="OUIA-Generated-Select-36"
|
|
95
95
|
data-ouia-component-type="PF5/Select"
|
|
96
96
|
data-ouia-safe="true"
|
|
97
97
|
data-popper-escaped="true"
|