diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 8a7ffb04571..a727e1f32c8 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -1,17 +1,5 @@ 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'; +import { SelectOptionProps, SelectTypeahead } from '@patternfly/react-core'; const initialSelectOptions: SelectOptionProps[] = [ { value: 'Alabama', children: 'Alabama' }, @@ -22,188 +10,16 @@ const initialSelectOptions: SelectOptionProps[] = [ { value: 'North Carolina', children: 'North Carolina' } ]; -export const SelectBasic: 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 [activeItem, setActiveItem] = React.useState(null); - const textInputRef = React.useRef(); - - 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()) - ); - - // When no options are found after filtering, display 'No results found' - if (!newSelectOptions.length) { - newSelectOptions = [ - { isDisabled: false, children: `No results found for "${filterValue}"`, value: 'no results' } - ]; - } - - // Open the menu when the input value changes and the new value is not empty - if (!isOpen) { - setIsOpen(true); - } - } - - setSelectOptions(newSelectOptions); - setActiveItem(null); - setFocusedItemIndex(null); - }, [filterValue]); - - const onToggleClick = () => { - setIsOpen(!isOpen); - }; - - const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { +export const SelectTypeaheadDemo: React.FunctionComponent = () => { + const onSelect = (value: string | number | undefined) => { // eslint-disable-next-line no-console - console.log('selected', value); - - if (value && value !== 'no results') { - setInputValue(value as string); - setFilterValue(''); - setSelected(value as string); - } - setIsOpen(false); - setFocusedItemIndex(null); - setActiveItem(null); - }; - - const onTextInputChange = (_event: React.FormEvent, value: string) => { - setInputValue(value); - setFilterValue(value); + console.log('selected:', value); }; - const handleMenuArrowKeys = (key: string) => { - let indexToFocus; - - if (isOpen) { - 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; - } - } - - 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; - } - } - - setFocusedItemIndex(indexToFocus); - const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; - setActiveItem(`select-typeahead-${focusedItem.value.replace(' ', '-')}`); - } - }; - - const onInputKeyDown = (event: React.KeyboardEvent) => { - const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); - const [firstMenuItem] = enabledMenuItems; - const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; - - switch (event.key) { - // Select the first available option - case 'Enter': - if (isOpen && focusedItem.value !== 'no results') { - setInputValue(String(focusedItem.children)); - setFilterValue(''); - setSelected(String(focusedItem.children)); - } - - setIsOpen((prevIsOpen) => !prevIsOpen); - setFocusedItemIndex(null); - setActiveItem(null); - - break; - case 'Tab': - case 'Escape': - setIsOpen(false); - setActiveItem(null); - break; - case 'ArrowUp': - case 'ArrowDown': - event.preventDefault(); - handleMenuArrowKeys(event.key); - break; - } + const onInput = (value: string) => { + // eslint-disable-next-line no-console + console.log('new input:', value); }; - const toggle = (toggleRef: React.Ref) => ( - - - - - - {!!inputValue && ( - - )} - - - - ); - - return ( - - ); + return ; }; diff --git a/packages/react-core/src/demos/SelectTypeahead.tsx b/packages/react-core/src/demos/SelectTypeahead.tsx new file mode 100644 index 00000000000..17c7aecc1c2 --- /dev/null +++ b/packages/react-core/src/demos/SelectTypeahead.tsx @@ -0,0 +1,206 @@ +import React from 'react'; +import { Select, SelectOption, SelectList, SelectOptionProps } from '../components/Select'; +import { MenuToggle, MenuToggleElement } from '../components/MenuToggle'; +import { TextInputGroup, TextInputGroupMain, TextInputGroupUtilities } from '../components/TextInputGroup'; +import { Button } from '../components/Button'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; + +export const SelectTypeahead: React.FunctionComponent = ({ + initialOptions, + onSelect, + onInput +}: { + initialOptions: SelectOptionProps[]; + onSelect?: (newValue: string) => void; + onInput?: (newValue: string) => void; +}) => { + 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(initialOptions); + const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); + const [activeItem, setActiveItem] = React.useState(null); + const textInputRef = React.useRef(); + + React.useEffect(() => { + let newSelectOptions: SelectOptionProps[] = initialOptions; + + // Filter menu items based on the text input value when one exists + if (filterValue) { + newSelectOptions = initialOptions.filter((menuItem: SelectOptionProps) => + String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase()) + ); + + // When no options are found after filtering, display 'No results found' + if (!newSelectOptions.length) { + newSelectOptions = [ + { isDisabled: false, children: `No results found for "${filterValue}"`, value: 'no results' } + ]; + } + + // Open the menu when the input value changes and the new value is not empty + if (!isOpen) { + setIsOpen(true); + } + } + + setSelectOptions(newSelectOptions); + setActiveItem(null); + setFocusedItemIndex(null); + }, [filterValue]); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const _onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { + if (value && value !== 'no results') { + setInputValue(value as string); + setFilterValue(''); + setSelected(value as string); + onSelect(value as string); + } + setIsOpen(false); + setFocusedItemIndex(null); + setActiveItem(null); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setInputValue(value); + onInput(value); + setFilterValue(value); + }; + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus; + + if (isOpen) { + 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; + } + } + + 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; + } + } + + setFocusedItemIndex(indexToFocus); + const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; + setActiveItem(`select-typeahead-${focusedItem.value.replace(' ', '-')}`); + } + }; + + const onInputKeyDown = (event: React.KeyboardEvent) => { + const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); + const [firstMenuItem] = enabledMenuItems; + const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + + switch (event.key) { + // Select the first available option + case 'Enter': + if (isOpen && focusedItem.value !== 'no results') { + setInputValue(String(focusedItem.children)); + setFilterValue(''); + onSelect(String(focusedItem.children)); + setSelected(String(focusedItem.children)); + } + + setIsOpen((prevIsOpen) => !prevIsOpen); + setFocusedItemIndex(null); + setActiveItem(null); + + break; + case 'Tab': + case 'Escape': + setIsOpen(false); + setActiveItem(null); + break; + case 'ArrowUp': + case 'ArrowDown': + event.preventDefault(); + handleMenuArrowKeys(event.key); + break; + } + }; + + const toggle = (toggleRef: React.Ref) => ( + + + + + + {!!inputValue && ( + + )} + + + + ); + + return ( + + ); +}; +SelectTypeahead.displayName = 'SelectTypeahead'; diff --git a/packages/react-core/src/demos/index.ts b/packages/react-core/src/demos/index.ts index e10143716db..8665f6284e9 100644 --- a/packages/react-core/src/demos/index.ts +++ b/packages/react-core/src/demos/index.ts @@ -1,2 +1,3 @@ export * from './DashboardHeader'; export * from './DashboardWrapper'; +export * from './SelectTypeahead';