@patternfly/react-core 5.4.0-prerelease.8 → 5.4.0-prerelease.9
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 +12 -0
- package/CONTRIBUTING.md +0 -2
- package/components/package.json +1 -1
- package/deprecated/package.json +1 -1
- package/dist/dynamic/components/AboutModal/package.json +1 -1
- package/dist/dynamic/components/Accordion/package.json +1 -1
- package/dist/dynamic/components/ActionList/package.json +1 -1
- package/dist/dynamic/components/Alert/package.json +1 -1
- package/dist/dynamic/components/Avatar/package.json +1 -1
- package/dist/dynamic/components/BackToTop/package.json +1 -1
- package/dist/dynamic/components/Backdrop/package.json +1 -1
- package/dist/dynamic/components/BackgroundImage/package.json +1 -1
- package/dist/dynamic/components/Badge/package.json +1 -1
- package/dist/dynamic/components/Banner/package.json +1 -1
- package/dist/dynamic/components/Brand/package.json +1 -1
- package/dist/dynamic/components/Breadcrumb/package.json +1 -1
- package/dist/dynamic/components/Button/package.json +1 -1
- package/dist/dynamic/components/CalendarMonth/package.json +1 -1
- package/dist/dynamic/components/Card/package.json +1 -1
- package/dist/dynamic/components/Checkbox/package.json +1 -1
- package/dist/dynamic/components/Chip/package.json +1 -1
- package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
- package/dist/dynamic/components/CodeBlock/package.json +1 -1
- package/dist/dynamic/components/DataList/package.json +1 -1
- package/dist/dynamic/components/DatePicker/package.json +1 -1
- package/dist/dynamic/components/DescriptionList/package.json +1 -1
- package/dist/dynamic/components/Divider/package.json +1 -1
- package/dist/dynamic/components/DragDrop/package.json +1 -1
- package/dist/dynamic/components/Drawer/package.json +1 -1
- package/dist/dynamic/components/Dropdown/package.json +1 -1
- package/dist/dynamic/components/DualListSelector/package.json +1 -1
- package/dist/dynamic/components/EmptyState/package.json +1 -1
- package/dist/dynamic/components/ExpandableSection/package.json +1 -1
- package/dist/dynamic/components/FileUpload/package.json +1 -1
- package/dist/dynamic/components/Form/package.json +1 -1
- package/dist/dynamic/components/FormSelect/package.json +1 -1
- package/dist/dynamic/components/HelperText/package.json +1 -1
- package/dist/dynamic/components/Hint/package.json +1 -1
- package/dist/dynamic/components/Icon/package.json +1 -1
- package/dist/dynamic/components/InputGroup/package.json +1 -1
- package/dist/dynamic/components/JumpLinks/package.json +1 -1
- package/dist/dynamic/components/Label/package.json +1 -1
- package/dist/dynamic/components/List/package.json +1 -1
- package/dist/dynamic/components/LoginPage/package.json +1 -1
- package/dist/dynamic/components/Masthead/package.json +1 -1
- package/dist/dynamic/components/Menu/package.json +1 -1
- package/dist/dynamic/components/MenuToggle/package.json +1 -1
- package/dist/dynamic/components/Modal/package.json +1 -1
- package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
- package/dist/dynamic/components/Nav/package.json +1 -1
- package/dist/dynamic/components/NotificationBadge/package.json +1 -1
- package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
- package/dist/dynamic/components/NumberInput/package.json +1 -1
- package/dist/dynamic/components/OverflowMenu/package.json +1 -1
- package/dist/dynamic/components/Page/package.json +1 -1
- package/dist/dynamic/components/Pagination/package.json +1 -1
- package/dist/dynamic/components/Panel/package.json +1 -1
- package/dist/dynamic/components/Popover/package.json +1 -1
- package/dist/dynamic/components/Progress/package.json +1 -1
- package/dist/dynamic/components/ProgressStepper/package.json +1 -1
- package/dist/dynamic/components/Radio/package.json +1 -1
- package/dist/dynamic/components/SearchInput/package.json +1 -1
- package/dist/dynamic/components/Select/package.json +1 -1
- package/dist/dynamic/components/Sidebar/package.json +1 -1
- package/dist/dynamic/components/SimpleList/package.json +1 -1
- package/dist/dynamic/components/Skeleton/package.json +1 -1
- package/dist/dynamic/components/SkipToContent/package.json +1 -1
- package/dist/dynamic/components/Slider/package.json +1 -1
- package/dist/dynamic/components/Spinner/package.json +1 -1
- package/dist/dynamic/components/Switch/package.json +1 -1
- package/dist/dynamic/components/Tabs/package.json +1 -1
- package/dist/dynamic/components/Text/package.json +1 -1
- package/dist/dynamic/components/TextArea/package.json +1 -1
- package/dist/dynamic/components/TextInput/package.json +1 -1
- package/dist/dynamic/components/TextInputGroup/package.json +1 -1
- package/dist/dynamic/components/Tile/package.json +1 -1
- package/dist/dynamic/components/TimePicker/package.json +1 -1
- package/dist/dynamic/components/Timestamp/package.json +1 -1
- package/dist/dynamic/components/Title/package.json +1 -1
- package/dist/dynamic/components/ToggleGroup/package.json +1 -1
- package/dist/dynamic/components/Toolbar/package.json +1 -1
- package/dist/dynamic/components/Tooltip/package.json +1 -1
- package/dist/dynamic/components/TreeView/package.json +1 -1
- package/dist/dynamic/components/Truncate/package.json +1 -1
- package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
- package/dist/dynamic/components/Wizard/package.json +1 -1
- package/dist/dynamic/deprecated/components/ApplicationLauncher/package.json +1 -1
- package/dist/dynamic/deprecated/components/ContextSelector/package.json +1 -1
- package/dist/dynamic/deprecated/components/Dropdown/package.json +1 -1
- package/dist/dynamic/deprecated/components/OptionsMenu/package.json +1 -1
- package/dist/dynamic/deprecated/components/PageHeader/package.json +1 -1
- package/dist/dynamic/deprecated/components/Select/package.json +1 -1
- package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
- package/dist/dynamic/deprecated/components/package.json +1 -1
- package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
- package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
- package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
- package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
- package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
- package/dist/dynamic/helpers/constants/package.json +1 -1
- package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
- package/dist/dynamic/helpers/favorites/package.json +1 -1
- package/dist/dynamic/helpers/fileUtils/package.json +1 -1
- package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
- package/dist/dynamic/helpers/package.json +1 -1
- package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
- package/dist/dynamic/helpers/typeUtils/package.json +1 -1
- package/dist/dynamic/helpers/useInterval/package.json +1 -1
- package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
- package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
- package/dist/dynamic/helpers/util/package.json +1 -1
- package/dist/dynamic/layouts/Bullseye/package.json +1 -1
- package/dist/dynamic/layouts/Flex/package.json +1 -1
- package/dist/dynamic/layouts/Gallery/package.json +1 -1
- package/dist/dynamic/layouts/Grid/package.json +1 -1
- package/dist/dynamic/layouts/Level/package.json +1 -1
- package/dist/dynamic/layouts/Split/package.json +1 -1
- package/dist/dynamic/layouts/Stack/package.json +1 -1
- package/dist/dynamic/next/components/DualListSelector/package.json +1 -1
- package/dist/dynamic/next/components/Modal/package.json +1 -1
- package/dist/dynamic/next/components/package.json +1 -1
- package/dist/dynamic/styles/package.json +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/esm/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/esm/components/Pagination/Pagination.js +2 -1
- package/dist/esm/components/Pagination/Pagination.js.map +1 -1
- package/dist/esm/components/Pagination/PaginationOptionsMenu.d.ts +1 -0
- package/dist/esm/components/Pagination/PaginationOptionsMenu.d.ts.map +1 -1
- package/dist/esm/components/Pagination/PaginationOptionsMenu.js +2 -4
- package/dist/esm/components/Pagination/PaginationOptionsMenu.js.map +1 -1
- package/dist/esm/components/Select/Select.d.ts +2 -0
- package/dist/esm/components/Select/Select.d.ts.map +1 -1
- package/dist/esm/components/Select/Select.js +3 -3
- package/dist/esm/components/Select/Select.js.map +1 -1
- package/dist/esm/components/Tabs/Tabs.d.ts +4 -0
- package/dist/esm/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/esm/components/Tabs/Tabs.js +17 -0
- package/dist/esm/components/Tabs/Tabs.js.map +1 -1
- package/dist/esm/components/Truncate/Truncate.d.ts.map +1 -1
- package/dist/esm/components/Truncate/Truncate.js +54 -12
- package/dist/esm/components/Truncate/Truncate.js.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/js/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/js/components/Pagination/Pagination.js +2 -1
- package/dist/js/components/Pagination/Pagination.js.map +1 -1
- package/dist/js/components/Pagination/PaginationOptionsMenu.d.ts +1 -0
- package/dist/js/components/Pagination/PaginationOptionsMenu.d.ts.map +1 -1
- package/dist/js/components/Pagination/PaginationOptionsMenu.js +2 -4
- package/dist/js/components/Pagination/PaginationOptionsMenu.js.map +1 -1
- package/dist/js/components/Select/Select.d.ts +2 -0
- package/dist/js/components/Select/Select.d.ts.map +1 -1
- package/dist/js/components/Select/Select.js +3 -3
- package/dist/js/components/Select/Select.js.map +1 -1
- package/dist/js/components/Tabs/Tabs.d.ts +4 -0
- package/dist/js/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/js/components/Tabs/Tabs.js +17 -0
- package/dist/js/components/Tabs/Tabs.js.map +1 -1
- package/dist/js/components/Truncate/Truncate.d.ts.map +1 -1
- package/dist/js/components/Truncate/Truncate.js +54 -12
- package/dist/js/components/Truncate/Truncate.js.map +1 -1
- package/dist/umd/react-core.min.js +2 -2
- package/helpers/package.json +1 -1
- package/layouts/package.json +1 -1
- package/next/package.json +1 -1
- package/package.json +6 -6
- package/src/components/CodeBlock/examples/CodeBlock.md +6 -0
- package/src/components/CodeBlock/examples/CodeBlockBasic.tsx +1 -1
- package/src/components/CodeBlock/examples/CodeBlockExpandable.tsx +3 -3
- package/src/components/MenuToggle/MenuToggle.tsx +1 -0
- package/src/components/Pagination/Pagination.tsx +2 -0
- package/src/components/Pagination/PaginationOptionsMenu.tsx +13 -14
- package/src/components/Pagination/__tests__/Generated/__snapshots__/PaginationOptionsMenu.test.tsx.snap +36 -38
- package/src/components/Pagination/__tests__/__snapshots__/Pagination.test.tsx.snap +714 -754
- package/src/components/Select/Select.tsx +5 -2
- package/src/components/Select/examples/SelectMultiTypeahead.tsx +111 -66
- package/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +103 -60
- package/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +125 -79
- package/src/components/Select/examples/SelectTypeahead.tsx +113 -69
- package/src/components/Select/examples/SelectTypeaheadCreatable.tsx +115 -75
- package/src/components/Tabs/Tabs.tsx +21 -0
- package/src/components/ToggleGroup/examples/ToggleGroup.md +33 -8
- package/src/components/Truncate/Truncate.tsx +73 -7
- package/src/components/Truncate/__tests__/Truncate.test.tsx +6 -0
|
@@ -30,10 +30,12 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
30
30
|
const [selected, setSelected] = React.useState<string[]>([]);
|
|
31
31
|
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(initialSelectOptions);
|
|
32
32
|
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
33
|
-
const [
|
|
33
|
+
const [activeItemId, setActiveItemId] = React.useState<string | null>(null);
|
|
34
34
|
const [onCreation, setOnCreation] = React.useState<boolean>(false); // Boolean to refresh filter state after new option is created
|
|
35
35
|
const textInputRef = React.useRef<HTMLInputElement>();
|
|
36
36
|
|
|
37
|
+
const CREATE_NEW = 'create';
|
|
38
|
+
|
|
37
39
|
React.useEffect(() => {
|
|
38
40
|
let newSelectOptions: SelectOptionProps[] = initialSelectOptions;
|
|
39
41
|
|
|
@@ -43,9 +45,9 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
43
45
|
String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase())
|
|
44
46
|
);
|
|
45
47
|
|
|
46
|
-
//
|
|
47
|
-
if (!
|
|
48
|
-
newSelectOptions = [{
|
|
48
|
+
// If no option matches the filter exactly, display creation option
|
|
49
|
+
if (!initialSelectOptions.some((option) => option.value === inputValue)) {
|
|
50
|
+
newSelectOptions = [...newSelectOptions, { children: `Create new option "${inputValue}"`, value: CREATE_NEW }];
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// Open the menu when the input value changes and the new value is not empty
|
|
@@ -55,56 +57,125 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
setSelectOptions(newSelectOptions);
|
|
58
|
-
setFocusedItemIndex(null);
|
|
59
|
-
setActiveItem(null);
|
|
60
60
|
}, [inputValue, onCreation]);
|
|
61
61
|
|
|
62
|
+
const createItemId = (value: any) => `select-multi-create-typeahead-${value.replace(' ', '-')}`;
|
|
63
|
+
|
|
64
|
+
const setActiveAndFocusedItem = (itemIndex: number) => {
|
|
65
|
+
setFocusedItemIndex(itemIndex);
|
|
66
|
+
const focusedItem = selectOptions[itemIndex];
|
|
67
|
+
setActiveItemId(createItemId(focusedItem.value));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const resetActiveAndFocusedItem = () => {
|
|
71
|
+
setFocusedItemIndex(null);
|
|
72
|
+
setActiveItemId(null);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const closeMenu = () => {
|
|
76
|
+
setIsOpen(false);
|
|
77
|
+
resetActiveAndFocusedItem();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const onInputClick = () => {
|
|
81
|
+
if (!isOpen) {
|
|
82
|
+
setIsOpen(true);
|
|
83
|
+
} else if (!inputValue) {
|
|
84
|
+
closeMenu();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onSelect = (value: string) => {
|
|
89
|
+
if (value) {
|
|
90
|
+
if (value === CREATE_NEW) {
|
|
91
|
+
if (!initialSelectOptions.some((item) => item.value === inputValue)) {
|
|
92
|
+
initialSelectOptions = [...initialSelectOptions, { value: inputValue, children: inputValue }];
|
|
93
|
+
}
|
|
94
|
+
setSelected(
|
|
95
|
+
selected.includes(inputValue)
|
|
96
|
+
? selected.filter((selection) => selection !== inputValue)
|
|
97
|
+
: [...selected, inputValue]
|
|
98
|
+
);
|
|
99
|
+
setOnCreation(!onCreation);
|
|
100
|
+
resetActiveAndFocusedItem();
|
|
101
|
+
} else {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.log('selected', value);
|
|
104
|
+
setSelected(
|
|
105
|
+
selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
textInputRef.current?.focus();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
|
|
114
|
+
setInputValue(value);
|
|
115
|
+
resetActiveAndFocusedItem();
|
|
116
|
+
};
|
|
117
|
+
|
|
62
118
|
const handleMenuArrowKeys = (key: string) => {
|
|
63
|
-
let indexToFocus;
|
|
119
|
+
let indexToFocus = 0;
|
|
120
|
+
|
|
121
|
+
if (!isOpen) {
|
|
122
|
+
setIsOpen(true);
|
|
123
|
+
}
|
|
64
124
|
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
125
|
+
if (selectOptions.every((option) => option.isDisabled)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (key === 'ArrowUp') {
|
|
130
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
131
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
132
|
+
indexToFocus = selectOptions.length - 1;
|
|
133
|
+
} else {
|
|
134
|
+
indexToFocus = focusedItemIndex - 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Skip disabled options
|
|
138
|
+
while (selectOptions[indexToFocus].isDisabled) {
|
|
139
|
+
indexToFocus--;
|
|
140
|
+
if (indexToFocus === -1) {
|
|
69
141
|
indexToFocus = selectOptions.length - 1;
|
|
70
|
-
} else {
|
|
71
|
-
indexToFocus = focusedItemIndex - 1;
|
|
72
142
|
}
|
|
73
143
|
}
|
|
144
|
+
}
|
|
74
145
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
146
|
+
if (key === 'ArrowDown') {
|
|
147
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
148
|
+
if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
|
|
149
|
+
indexToFocus = 0;
|
|
150
|
+
} else {
|
|
151
|
+
indexToFocus = focusedItemIndex + 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Skip disabled options
|
|
155
|
+
while (selectOptions[indexToFocus].isDisabled) {
|
|
156
|
+
indexToFocus++;
|
|
157
|
+
if (indexToFocus === selectOptions.length) {
|
|
78
158
|
indexToFocus = 0;
|
|
79
|
-
} else {
|
|
80
|
-
indexToFocus = focusedItemIndex + 1;
|
|
81
159
|
}
|
|
82
160
|
}
|
|
83
|
-
|
|
84
|
-
setFocusedItemIndex(indexToFocus);
|
|
85
|
-
const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus];
|
|
86
|
-
setActiveItem(`select-multi-create-typeahead-${focusedItem.value.replace(' ', '-')}`);
|
|
87
161
|
}
|
|
162
|
+
|
|
163
|
+
setActiveAndFocusedItem(indexToFocus);
|
|
88
164
|
};
|
|
89
165
|
|
|
90
166
|
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
91
|
-
const
|
|
92
|
-
const [firstMenuItem] = enabledMenuItems;
|
|
93
|
-
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
167
|
+
const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null;
|
|
94
168
|
|
|
95
169
|
switch (event.key) {
|
|
96
|
-
// Select the first available option
|
|
97
170
|
case 'Enter':
|
|
98
|
-
if (!
|
|
99
|
-
setIsOpen((prevIsOpen) => !prevIsOpen);
|
|
100
|
-
} else if (isOpen && focusedItem.value !== 'no results') {
|
|
171
|
+
if (isOpen && focusedItem && !focusedItem.isAriaDisabled) {
|
|
101
172
|
onSelect(focusedItem.value as string);
|
|
102
173
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
174
|
+
|
|
175
|
+
if (!isOpen) {
|
|
176
|
+
setIsOpen(true);
|
|
177
|
+
}
|
|
178
|
+
|
|
108
179
|
break;
|
|
109
180
|
case 'ArrowUp':
|
|
110
181
|
case 'ArrowDown':
|
|
@@ -116,35 +187,17 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
116
187
|
|
|
117
188
|
const onToggleClick = () => {
|
|
118
189
|
setIsOpen(!isOpen);
|
|
190
|
+
textInputRef?.current?.focus();
|
|
119
191
|
};
|
|
120
192
|
|
|
121
|
-
const
|
|
122
|
-
|
|
193
|
+
const onClearButtonClick = () => {
|
|
194
|
+
setSelected([]);
|
|
195
|
+
setInputValue('');
|
|
196
|
+
resetActiveAndFocusedItem();
|
|
197
|
+
textInputRef?.current?.focus();
|
|
123
198
|
};
|
|
124
199
|
|
|
125
|
-
const
|
|
126
|
-
if (value) {
|
|
127
|
-
if (value === 'create') {
|
|
128
|
-
if (!initialSelectOptions.some((item) => item.value === inputValue)) {
|
|
129
|
-
initialSelectOptions = [...initialSelectOptions, { value: inputValue, children: inputValue }];
|
|
130
|
-
}
|
|
131
|
-
setSelected(
|
|
132
|
-
selected.includes(inputValue)
|
|
133
|
-
? selected.filter((selection) => selection !== inputValue)
|
|
134
|
-
: [...selected, inputValue]
|
|
135
|
-
);
|
|
136
|
-
setOnCreation(!onCreation);
|
|
137
|
-
} else {
|
|
138
|
-
// eslint-disable-next-line no-console
|
|
139
|
-
console.log('selected', value);
|
|
140
|
-
setSelected(
|
|
141
|
-
selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value]
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
textInputRef.current?.focus();
|
|
147
|
-
};
|
|
200
|
+
const getChildren = (value: string) => initialSelectOptions.find((option) => option.value === value)?.children;
|
|
148
201
|
|
|
149
202
|
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
|
150
203
|
<MenuToggle
|
|
@@ -158,14 +211,14 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
158
211
|
<TextInputGroup isPlain>
|
|
159
212
|
<TextInputGroupMain
|
|
160
213
|
value={inputValue}
|
|
161
|
-
onClick={
|
|
214
|
+
onClick={onInputClick}
|
|
162
215
|
onChange={onTextInputChange}
|
|
163
216
|
onKeyDown={onInputKeyDown}
|
|
164
217
|
id="multi-create-typeahead-select-input"
|
|
165
218
|
autoComplete="off"
|
|
166
219
|
innerRef={textInputRef}
|
|
167
220
|
placeholder="Select a state"
|
|
168
|
-
{...(
|
|
221
|
+
{...(activeItemId && { 'aria-activedescendant': activeItemId })}
|
|
169
222
|
role="combobox"
|
|
170
223
|
isExpanded={isOpen}
|
|
171
224
|
aria-controls="select-multi-create-typeahead-listbox"
|
|
@@ -179,25 +232,15 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
179
232
|
onSelect(selection);
|
|
180
233
|
}}
|
|
181
234
|
>
|
|
182
|
-
{selection}
|
|
235
|
+
{getChildren(selection)}
|
|
183
236
|
</Chip>
|
|
184
237
|
))}
|
|
185
238
|
</ChipGroup>
|
|
186
239
|
</TextInputGroupMain>
|
|
187
|
-
<TextInputGroupUtilities>
|
|
188
|
-
{
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
onClick={() => {
|
|
192
|
-
setInputValue('');
|
|
193
|
-
setSelected([]);
|
|
194
|
-
textInputRef?.current?.focus();
|
|
195
|
-
}}
|
|
196
|
-
aria-label="Clear input value"
|
|
197
|
-
>
|
|
198
|
-
<TimesIcon aria-hidden />
|
|
199
|
-
</Button>
|
|
200
|
-
)}
|
|
240
|
+
<TextInputGroupUtilities {...(selected.length === 0 ? { style: { display: 'none' } } : {})}>
|
|
241
|
+
<Button variant="plain" onClick={onClearButtonClick} aria-label="Clear input value">
|
|
242
|
+
<TimesIcon aria-hidden />
|
|
243
|
+
</Button>
|
|
201
244
|
</TextInputGroupUtilities>
|
|
202
245
|
</TextInputGroup>
|
|
203
246
|
</MenuToggle>
|
|
@@ -208,9 +251,12 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
208
251
|
id="multi-create-typeahead-select"
|
|
209
252
|
isOpen={isOpen}
|
|
210
253
|
selected={selected}
|
|
211
|
-
onSelect={(
|
|
212
|
-
onOpenChange={() =>
|
|
254
|
+
onSelect={(_event, selection) => onSelect(selection as string)}
|
|
255
|
+
onOpenChange={(isOpen) => {
|
|
256
|
+
!isOpen && closeMenu();
|
|
257
|
+
}}
|
|
213
258
|
toggle={toggle}
|
|
259
|
+
shouldFocusFirstItemOnOpen={false}
|
|
214
260
|
>
|
|
215
261
|
<SelectList isAriaMultiselectable id="select-multi-create-typeahead-listbox">
|
|
216
262
|
{selectOptions.map((option, index) => (
|
|
@@ -218,7 +264,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => {
|
|
|
218
264
|
key={option.value || option.children}
|
|
219
265
|
isFocused={focusedItemIndex === index}
|
|
220
266
|
className={option.className}
|
|
221
|
-
id={
|
|
267
|
+
id={createItemId(option.value)}
|
|
222
268
|
{...option}
|
|
223
269
|
ref={null}
|
|
224
270
|
/>
|
|
@@ -22,16 +22,18 @@ const initialSelectOptions: SelectOptionProps[] = [
|
|
|
22
22
|
{ value: 'North Carolina', children: 'North Carolina' }
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
export const
|
|
25
|
+
export const SelectTypeahead: React.FunctionComponent = () => {
|
|
26
26
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
27
27
|
const [selected, setSelected] = React.useState<string>('');
|
|
28
28
|
const [inputValue, setInputValue] = React.useState<string>('');
|
|
29
29
|
const [filterValue, setFilterValue] = React.useState<string>('');
|
|
30
30
|
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(initialSelectOptions);
|
|
31
31
|
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
32
|
-
const [
|
|
32
|
+
const [activeItemId, setActiveItemId] = React.useState<string | null>(null);
|
|
33
33
|
const textInputRef = React.useRef<HTMLInputElement>();
|
|
34
34
|
|
|
35
|
+
const NO_RESULTS = 'no results';
|
|
36
|
+
|
|
35
37
|
React.useEffect(() => {
|
|
36
38
|
let newSelectOptions: SelectOptionProps[] = initialSelectOptions;
|
|
37
39
|
|
|
@@ -44,7 +46,7 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
44
46
|
// When no options are found after filtering, display 'No results found'
|
|
45
47
|
if (!newSelectOptions.length) {
|
|
46
48
|
newSelectOptions = [
|
|
47
|
-
{
|
|
49
|
+
{ isAriaDisabled: true, children: `No results found for "${filterValue}"`, value: NO_RESULTS }
|
|
48
50
|
];
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -55,84 +57,124 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
setSelectOptions(newSelectOptions);
|
|
58
|
-
setActiveItem(null);
|
|
59
|
-
setFocusedItemIndex(null);
|
|
60
60
|
}, [filterValue]);
|
|
61
61
|
|
|
62
|
-
const
|
|
63
|
-
|
|
62
|
+
const createItemId = (value: any) => `select-typeahead-${value.replace(' ', '-')}`;
|
|
63
|
+
|
|
64
|
+
const setActiveAndFocusedItem = (itemIndex: number) => {
|
|
65
|
+
setFocusedItemIndex(itemIndex);
|
|
66
|
+
const focusedItem = selectOptions[itemIndex];
|
|
67
|
+
setActiveItemId(createItemId(focusedItem.value));
|
|
64
68
|
};
|
|
65
69
|
|
|
66
|
-
const
|
|
70
|
+
const resetActiveAndFocusedItem = () => {
|
|
71
|
+
setFocusedItemIndex(null);
|
|
72
|
+
setActiveItemId(null);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const closeMenu = () => {
|
|
76
|
+
setIsOpen(false);
|
|
77
|
+
resetActiveAndFocusedItem();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const onInputClick = () => {
|
|
81
|
+
if (!isOpen) {
|
|
82
|
+
setIsOpen(true);
|
|
83
|
+
} else if (!inputValue) {
|
|
84
|
+
closeMenu();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const selectOption = (value: string | number, content: string | number) => {
|
|
67
89
|
// eslint-disable-next-line no-console
|
|
68
|
-
console.log('selected',
|
|
90
|
+
console.log('selected', content);
|
|
91
|
+
|
|
92
|
+
setInputValue(String(content));
|
|
93
|
+
setFilterValue('');
|
|
94
|
+
setSelected(String(value));
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
closeMenu();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
|
|
100
|
+
if (value && value !== NO_RESULTS) {
|
|
101
|
+
const optionText = selectOptions.find((option) => option.value === value)?.children;
|
|
102
|
+
selectOption(value, optionText as string);
|
|
74
103
|
}
|
|
75
|
-
setIsOpen(false);
|
|
76
|
-
setFocusedItemIndex(null);
|
|
77
|
-
setActiveItem(null);
|
|
78
104
|
};
|
|
79
105
|
|
|
80
106
|
const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
|
|
81
107
|
setInputValue(value);
|
|
82
108
|
setFilterValue(value);
|
|
109
|
+
|
|
110
|
+
resetActiveAndFocusedItem();
|
|
111
|
+
|
|
112
|
+
if (value !== selected) {
|
|
113
|
+
setSelected('');
|
|
114
|
+
}
|
|
83
115
|
};
|
|
84
116
|
|
|
85
117
|
const handleMenuArrowKeys = (key: string) => {
|
|
86
|
-
let indexToFocus;
|
|
118
|
+
let indexToFocus = 0;
|
|
119
|
+
|
|
120
|
+
if (!isOpen) {
|
|
121
|
+
setIsOpen(true);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (selectOptions.every((option) => option.isDisabled)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
87
127
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
if (key === 'ArrowUp') {
|
|
129
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
130
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
131
|
+
indexToFocus = selectOptions.length - 1;
|
|
132
|
+
} else {
|
|
133
|
+
indexToFocus = focusedItemIndex - 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Skip disabled options
|
|
137
|
+
while (selectOptions[indexToFocus].isDisabled) {
|
|
138
|
+
indexToFocus--;
|
|
139
|
+
if (indexToFocus === -1) {
|
|
92
140
|
indexToFocus = selectOptions.length - 1;
|
|
93
|
-
} else {
|
|
94
|
-
indexToFocus = focusedItemIndex - 1;
|
|
95
141
|
}
|
|
96
142
|
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (key === 'ArrowDown') {
|
|
146
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
147
|
+
if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
|
|
148
|
+
indexToFocus = 0;
|
|
149
|
+
} else {
|
|
150
|
+
indexToFocus = focusedItemIndex + 1;
|
|
151
|
+
}
|
|
97
152
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
153
|
+
// Skip disabled options
|
|
154
|
+
while (selectOptions[indexToFocus].isDisabled) {
|
|
155
|
+
indexToFocus++;
|
|
156
|
+
if (indexToFocus === selectOptions.length) {
|
|
101
157
|
indexToFocus = 0;
|
|
102
|
-
} else {
|
|
103
|
-
indexToFocus = focusedItemIndex + 1;
|
|
104
158
|
}
|
|
105
159
|
}
|
|
106
|
-
|
|
107
|
-
setFocusedItemIndex(indexToFocus);
|
|
108
|
-
const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus];
|
|
109
|
-
setActiveItem(`select-typeahead-${focusedItem.value.replace(' ', '-')}`);
|
|
110
160
|
}
|
|
161
|
+
|
|
162
|
+
setActiveAndFocusedItem(indexToFocus);
|
|
111
163
|
};
|
|
112
164
|
|
|
113
165
|
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
114
|
-
const
|
|
115
|
-
const [firstMenuItem] = enabledMenuItems;
|
|
116
|
-
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
166
|
+
const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null;
|
|
117
167
|
|
|
118
168
|
switch (event.key) {
|
|
119
|
-
// Select the first available option
|
|
120
169
|
case 'Enter':
|
|
121
|
-
if (isOpen && focusedItem.value !==
|
|
122
|
-
|
|
123
|
-
setFilterValue('');
|
|
124
|
-
setSelected(String(focusedItem.children));
|
|
170
|
+
if (isOpen && focusedItem && focusedItem.value !== NO_RESULTS && !focusedItem.isAriaDisabled) {
|
|
171
|
+
selectOption(focusedItem.value, focusedItem.children as string);
|
|
125
172
|
}
|
|
126
173
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
174
|
+
if (!isOpen) {
|
|
175
|
+
setIsOpen(true);
|
|
176
|
+
}
|
|
130
177
|
|
|
131
|
-
break;
|
|
132
|
-
case 'Tab':
|
|
133
|
-
case 'Escape':
|
|
134
|
-
setIsOpen(false);
|
|
135
|
-
setActiveItem(null);
|
|
136
178
|
break;
|
|
137
179
|
case 'ArrowUp':
|
|
138
180
|
case 'ArrowDown':
|
|
@@ -142,6 +184,19 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
142
184
|
}
|
|
143
185
|
};
|
|
144
186
|
|
|
187
|
+
const onToggleClick = () => {
|
|
188
|
+
setIsOpen(!isOpen);
|
|
189
|
+
textInputRef?.current?.focus();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const onClearButtonClick = () => {
|
|
193
|
+
setSelected('');
|
|
194
|
+
setInputValue('');
|
|
195
|
+
setFilterValue('');
|
|
196
|
+
resetActiveAndFocusedItem();
|
|
197
|
+
textInputRef?.current?.focus();
|
|
198
|
+
};
|
|
199
|
+
|
|
145
200
|
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
|
146
201
|
<MenuToggle
|
|
147
202
|
ref={toggleRef}
|
|
@@ -154,34 +209,23 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
154
209
|
<TextInputGroup isPlain>
|
|
155
210
|
<TextInputGroupMain
|
|
156
211
|
value={inputValue}
|
|
157
|
-
onClick={
|
|
212
|
+
onClick={onInputClick}
|
|
158
213
|
onChange={onTextInputChange}
|
|
159
214
|
onKeyDown={onInputKeyDown}
|
|
160
215
|
id="typeahead-select-input"
|
|
161
216
|
autoComplete="off"
|
|
162
217
|
innerRef={textInputRef}
|
|
163
218
|
placeholder="Select a state"
|
|
164
|
-
{...(
|
|
219
|
+
{...(activeItemId && { 'aria-activedescendant': activeItemId })}
|
|
165
220
|
role="combobox"
|
|
166
221
|
isExpanded={isOpen}
|
|
167
222
|
aria-controls="select-typeahead-listbox"
|
|
168
223
|
/>
|
|
169
224
|
|
|
170
|
-
<TextInputGroupUtilities>
|
|
171
|
-
{
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
onClick={() => {
|
|
175
|
-
setSelected('');
|
|
176
|
-
setInputValue('');
|
|
177
|
-
setFilterValue('');
|
|
178
|
-
textInputRef?.current?.focus();
|
|
179
|
-
}}
|
|
180
|
-
aria-label="Clear input value"
|
|
181
|
-
>
|
|
182
|
-
<TimesIcon aria-hidden />
|
|
183
|
-
</Button>
|
|
184
|
-
)}
|
|
225
|
+
<TextInputGroupUtilities {...(!inputValue ? { style: { display: 'none' } } : {})}>
|
|
226
|
+
<Button variant="plain" onClick={onClearButtonClick} aria-label="Clear input value">
|
|
227
|
+
<TimesIcon aria-hidden />
|
|
228
|
+
</Button>
|
|
185
229
|
</TextInputGroupUtilities>
|
|
186
230
|
</TextInputGroup>
|
|
187
231
|
</MenuToggle>
|
|
@@ -193,10 +237,11 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
193
237
|
isOpen={isOpen}
|
|
194
238
|
selected={selected}
|
|
195
239
|
onSelect={onSelect}
|
|
196
|
-
onOpenChange={() => {
|
|
197
|
-
|
|
240
|
+
onOpenChange={(isOpen) => {
|
|
241
|
+
!isOpen && closeMenu();
|
|
198
242
|
}}
|
|
199
243
|
toggle={toggle}
|
|
244
|
+
shouldFocusFirstItemOnOpen={false}
|
|
200
245
|
>
|
|
201
246
|
<SelectList id="select-typeahead-listbox">
|
|
202
247
|
{selectOptions.map((option, index) => (
|
|
@@ -204,8 +249,7 @@ export const SelectBasic: React.FunctionComponent = () => {
|
|
|
204
249
|
key={option.value || option.children}
|
|
205
250
|
isFocused={focusedItemIndex === index}
|
|
206
251
|
className={option.className}
|
|
207
|
-
|
|
208
|
-
id={`select-typeahead-${option.value.replace(' ', '-')}`}
|
|
252
|
+
id={createItemId(option.value)}
|
|
209
253
|
{...option}
|
|
210
254
|
ref={null}
|
|
211
255
|
/>
|