UNPKG

nishant-design-system

Version:
165 lines (153 loc) 4.58 kB
// @flow strict import * as React from 'react'; import { // $FlowFixMe[untyped-import] autoUpdate, // $FlowFixMe[untyped-import] flip, // $FlowFixMe[untyped-import] offset, // $FlowFixMe[untyped-import] useFloating, } from '@floating-ui/react-dom'; import {colorBackgroundTertiary} from '../../styles/variables/_color'; import {sizeFluid} from '../../styles/variables/_size'; import {spaceNone, spaceXXSmall} from '../../styles/variables/_space'; import {classify} from '../../utils/classify'; import {ClickAway} from '../../utils/click-away'; import type {InputProps} from '../Input'; import type {MenuOption, MenuProps} from '../Menu'; import {Menu} from '../Menu'; import {SearchInput} from '../SearchInput'; import css from './Typeahead.module.css'; type ClassNames = $ReadOnly<{wrapper?: string, box?: string}>; export type TypeaheadProps = { ...InputProps, classNames?: ClassNames, onSelect?: (option: MenuOption) => mixed, onSearch?: (evt: SyntheticInputEvent<HTMLInputElement>) => mixed, onMenuOpen?: () => mixed, onMenuClose?: () => mixed, typeaheadInputText?: string, menu?: MenuProps, onClear?: () => void, isLoading?: boolean, menuOpenOffset?: number, ... }; export const Typeahead = ({ size = 'medium', classNames, placeholder = 'Select...', onSelect, onSearch, onClear, menu, onMenuOpen, onMenuClose, typeaheadInputText = '', isLoading, menuOpenOffset = 1, ...inputProps }: TypeaheadProps): React.Node => { const typeaheadRef = React.useRef(); const [filteredOptions, setFilteredOptions] = React.useState(menu?.options); const {x, y, reference, floating, strategy} = useFloating({ strategy: 'absolute', placement: 'bottom-start', whileElementsMounted: autoUpdate, middleware: [flip(), offset(parseInt(spaceXXSmall))], }); const onMenuToggle = (isOpen: boolean) => { isOpen ? onMenuOpen && onMenuOpen() : onMenuClose && onMenuClose(); }; React.useEffect(() => { const optionsFiltered = menu?.options && menu.options.filter((option) => { if (!option.label || !typeaheadInputText) { return true; } else { return ( option.label .toLowerCase() .indexOf(typeaheadInputText.toLowerCase()) !== -1 ); } }); setFilteredOptions(optionsFiltered || []); }, [typeaheadInputText, menu?.options]); return ( <ClickAway onChange={onMenuToggle}> {({isOpen, onOpen, cancelNext, clickAway}) => ( <div data-testid="Typeahead" className={classify(css.typeaheadContainer, classNames?.wrapper)} ref={typeaheadRef} onClickCapture={cancelNext} > <SearchInput {...inputProps} boxRef={reference} size={size} placeholder={placeholder} value={typeaheadInputText} classNames={{box: classNames?.box}} isLoading={isLoading} onChange={(e) => { e.stopPropagation(); onSearch && onSearch(e); if (e.target.value.length >= menuOpenOffset) { onOpen(); } else { clickAway(); } }} onFocus={(_e) => { if (typeaheadInputText.length >= menuOpenOffset) { onOpen(); } else { clickAway(); } }} onClear={(_e) => { onClear?.(); }} /> {isOpen && !isLoading && menu && filteredOptions && !!filteredOptions.length && ( <div onClickCapture={cancelNext} ref={floating} style={{ position: strategy, top: y ?? spaceNone, left: x ?? spaceNone, width: sizeFluid, backgroundColor: colorBackgroundTertiary, }} > <Menu {...menu} options={filteredOptions} onSelect={(option) => { onSelect && onSelect(option); if ( !menu.optionsVariant || menu.optionsVariant === 'normal' ) { clickAway(); } }} size={menu.size || size} /> </div> )} </div> )} </ClickAway> ); };