// @flow strict import * as React from 'react'; import { // $FlowFixMe[untyped-import] autoUpdate, // $FlowFixMe[untyped-import] flip, // $FlowFixMe[untyped-import] FloatingFocusManager, // $FlowFixMe[untyped-import] FloatingPortal, // $FlowFixMe[untyped-import] offset, // $FlowFixMe[untyped-import] useFloating, } from '@floating-ui/react'; import {useReferenceElementWidth} from '../../hooks'; import {spaceNone, spaceXXSmall} from '../../styles/variables/_space'; import {classify} from '../../utils/classify'; import {type ClickAwayRefType, ClickAway} from '../../utils/click-away'; import {mergeRefs} from '../../utils/merge-refs'; import type {InputProps} from '../Input'; import {Input} from '../Input'; import type {MenuOption, MenuProps} from '../Menu'; import {Menu} from '../Menu'; import {type ElevationType, getElevationValue} from '../Tooltip'; import css from './Dropdown.module.css'; type ClassNames = $ReadOnly<{ wrapper?: string, box?: string, iconRight?: string, }>; export type DropdownProps = { ...InputProps, classNames?: ClassNames, onChange?: (option: MenuOption, ?SyntheticEvent) => mixed, onMenuOpen?: () => mixed, onMenuClose?: () => mixed, scrollMenuToBottom?: boolean, dropdownInputText?: string, menu?: MenuProps, elevation?: ElevationType, clickAwayRef?: ClickAwayRefType, ... }; export const Dropdown: React$AbstractComponent< DropdownProps, HTMLInputElement, > = React.forwardRef( ( { size = 'medium', classNames, placeholder = 'Select...', onChange, menu, onMenuOpen, onMenuClose, scrollMenuToBottom = false, dropdownInputText = '', clickAwayRef, elevation = 'modal', ...inputProps }: DropdownProps, ref, ): React.Node => { const menuRef = React.useRef(); const {x, y, refs, strategy, context} = useFloating({ open: true, strategy: 'absolute', placement: 'bottom-start', whileElementsMounted: autoUpdate, middleware: [flip(), offset(parseInt(spaceXXSmall))], }); const dropdownWidth = useReferenceElementWidth(refs.reference?.current); const onMenuToggle = (isOpen: boolean) => { isOpen ? onMenuOpen && onMenuOpen() : onMenuClose && onMenuClose(); if (scrollMenuToBottom && menuRef.current && isOpen) { menuRef.current.scrollTop = isOpen ? menuRef.current.scrollHeight : 0; } }; return ( {({isOpen, onOpen, clickAway, boundaryRef, triggerRef}) => (
{ if (e.keyCode === 32) { e.preventDefault(); isOpen ? clickAway() : onOpen(); } }} boxRef={mergeRefs([refs.setReference, triggerRef])} size={size} placeholder={placeholder} value={dropdownInputText} classNames={{ box: classify(css.inputBox, classNames?.box), iconRight: classNames?.iconRight, }} iconRightName={isOpen ? 'angle-up' : 'angle-down'} readOnly onContainerClick={(e) => { e.stopPropagation(); onOpen(); }} ref={ref} /> {isOpen && menu && (
element. This means the menu would otherwise default to the body's width. To support fluid width, we must manually set the dropdown width here; otherwise, it uses a fixed width. Also, Only treat menu as non-fluid if isFluid is strictly false, since default is true in menu and undefined means fluid. */ ...(menu.isFluid !== false && { '--dropdown-width': dropdownWidth, }), '--menu-elevation': getElevationValue(elevation), }} > { onChange && onChange(option, e); if ( // option.keepMenuOpenOnOptionSelect - to allow the menu persist its open stat upon option selection in normal variant !option.keepMenuOpenOnOptionSelect && (!menu.optionsVariant || menu.optionsVariant === 'normal') ) { clickAway(); refs.reference.current.querySelector('input').focus(); } }} size={menu.size || size} onTabOut={clickAway} ref={menuRef} />
)}
)}
); }, );