import React from 'react'; import { Select, SelectOption, SelectList, SelectOptionProps, MenuToggle, MenuToggleElement, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, Label, LabelGroup, 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 SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [inputValue, setInputValue] = React.useState(''); const [selected, setSelected] = React.useState([]); const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); const [activeItemId, setActiveItemId] = React.useState(null); const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created 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 (inputValue) { newSelectOptions = initialSelectOptions.filter((menuItem) => String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) ); // If no option matches the filter exactly, display creation option if (!initialSelectOptions.some((option) => option.value === inputValue)) { newSelectOptions = [...newSelectOptions, { children: `Create new option "${inputValue}"`, value: CREATE_NEW }]; } // Open the menu when the input value changes and the new value is not empty if (!isOpen) { setIsOpen(true); } } setSelectOptions(newSelectOptions); }, [inputValue, onCreation]); const createItemId = (value: any) => `select-multi-create-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 onSelect = (value: string) => { if (value) { if (value === CREATE_NEW) { if (!initialSelectOptions.some((item) => item.value === inputValue)) { initialSelectOptions = [...initialSelectOptions, { value: inputValue, children: inputValue }]; } setSelected( selected.includes(inputValue) ? selected.filter((selection) => selection !== inputValue) : [...selected, inputValue] ); setOnCreation(!onCreation); resetActiveAndFocusedItem(); } else { // eslint-disable-next-line no-console console.log('selected', value); setSelected( selected.includes(value) ? selected.filter((selection) => selection !== value) : [...selected, value] ); } } textInputRef.current?.focus(); }; const onTextInputChange = (_event: React.FormEvent, value: string) => { setInputValue(value); resetActiveAndFocusedItem(); }; 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(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(''); resetActiveAndFocusedItem(); textInputRef?.current?.focus(); }; const getChildren = (value: string) => initialSelectOptions.find((option) => option.value === value)?.children; const toggle = (toggleRef: React.Ref) => ( {selected.map((selection, index) => ( ))}