import React from 'react'; import { Select, SelectOption, SelectList, SelectOptionProps, MenuToggle, MenuToggleElement, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, Button } from '@patternfly/react-core'; import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; let initialSelectOptions: SelectOptionProps[] = [ { value: 'Alabama', children: 'Alabama' }, { value: 'Florida', children: 'Florida' }, { value: 'New Jersey', children: 'New Jersey' }, { value: 'New Mexico', children: 'New Mexico' }, { value: 'New York', children: 'New York' }, { value: 'North Carolina', children: 'North Carolina' } ]; export const SelectTypeaheadCreatable: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState(''); const [inputValue, setInputValue] = React.useState(''); const [filterValue, setFilterValue] = React.useState(''); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); const [activeItemId, setActiveItemId] = React.useState(null); const textInputRef = React.useRef(); const CREATE_NEW = 'create'; React.useEffect(() => { let newSelectOptions: SelectOptionProps[] = initialSelectOptions; // Filter menu items based on the text input value when one exists if (filterValue) { newSelectOptions = initialSelectOptions.filter((menuItem) => String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase()) ); // If no option matches the filter exactly, display creation option if (!initialSelectOptions.some((option) => option.value === filterValue)) { newSelectOptions = [...newSelectOptions, { children: `Create new option "${filterValue}"`, value: CREATE_NEW }]; } // Open the menu when the input value changes and the new value is not empty if (!isOpen) { setIsOpen(true); } } setSelectOptions(newSelectOptions); }, [filterValue]); const createItemId = (value: any) => `select-typeahead-${value.replace(' ', '-')}`; const setActiveAndFocusedItem = (itemIndex: number) => { setFocusedItemIndex(itemIndex); const focusedItem = selectOptions[itemIndex]; setActiveItemId(createItemId(focusedItem.value)); }; const resetActiveAndFocusedItem = () => { setFocusedItemIndex(null); setActiveItemId(null); }; const closeMenu = () => { setIsOpen(false); resetActiveAndFocusedItem(); }; const onInputClick = () => { if (!isOpen) { setIsOpen(true); } else if (!inputValue) { closeMenu(); } }; const selectOption = (value: string | number, content: string | number) => { // eslint-disable-next-line no-console console.log('selected', content); setInputValue(String(content)); setFilterValue(''); setSelected(String(value)); closeMenu(); }; const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { if (value) { if (value === CREATE_NEW) { if (!initialSelectOptions.some((item) => item.children === filterValue)) { initialSelectOptions = [...initialSelectOptions, { value: filterValue, children: filterValue }]; } setSelected(filterValue); setFilterValue(''); closeMenu(); } else { const optionText = selectOptions.find((option) => option.value === value)?.children; selectOption(value, optionText as string); } } }; const onTextInputChange = (_event: React.FormEvent, value: string) => { setInputValue(value); setFilterValue(value); resetActiveAndFocusedItem(); if (value !== selected) { setSelected(''); } }; const handleMenuArrowKeys = (key: string) => { let indexToFocus = 0; if (!isOpen) { setIsOpen(true); } if (selectOptions.every((option) => option.isDisabled)) { return; } if (key === 'ArrowUp') { // When no index is set or at the first index, focus to the last, otherwise decrement focus index if (focusedItemIndex === null || focusedItemIndex === 0) { indexToFocus = selectOptions.length - 1; } else { indexToFocus = focusedItemIndex - 1; } // Skip disabled options while (selectOptions[indexToFocus].isDisabled) { indexToFocus--; if (indexToFocus === -1) { indexToFocus = selectOptions.length - 1; } } } if (key === 'ArrowDown') { // When no index is set or at the last index, focus to the first, otherwise increment focus index if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { indexToFocus = 0; } else { indexToFocus = focusedItemIndex + 1; } // Skip disabled options while (selectOptions[indexToFocus].isDisabled) { indexToFocus++; if (indexToFocus === selectOptions.length) { indexToFocus = 0; } } } setActiveAndFocusedItem(indexToFocus); }; const onInputKeyDown = (event: React.KeyboardEvent) => { const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null; switch (event.key) { case 'Enter': if (isOpen && focusedItem && !focusedItem.isAriaDisabled) { onSelect(undefined, focusedItem.value as string); } if (!isOpen) { setIsOpen(true); } break; case 'ArrowUp': case 'ArrowDown': event.preventDefault(); handleMenuArrowKeys(event.key); break; } }; const onToggleClick = () => { setIsOpen(!isOpen); textInputRef?.current?.focus(); }; const onClearButtonClick = () => { setSelected(''); setInputValue(''); setFilterValue(''); resetActiveAndFocusedItem(); textInputRef?.current?.focus(); }; const toggle = (toggleRef: React.Ref) => (
} /> ); return (
); };