import { useDisclosure, Flex, Popover, Input, PopoverContent, Button, useColorModeValue, PopoverAnchor, Portal, InputGroup, InputRightElement, Text, Box, IconButton, HStack, } from '@chakra-ui/react' import { useState, useRef, ChangeEvent } from 'react' import { isDefined } from '@typebot.io/lib' import { useOutsideClick } from '@/hooks/useOutsideClick' import { useParentModal } from '@/features/graph/providers/ParentModalProvider' import { ChevronDownIcon, CloseIcon } from '../icons' const dropdownCloseAnimationDuration = 300 type Item = | string | { icon?: JSX.Element label: string value: string extras?: Record } type Props = { isPopoverMatchingInputWidth?: boolean selectedItem?: string items: T[] placeholder?: string onSelect?: (value: string | undefined, item?: T) => void } export const Select = ({ isPopoverMatchingInputWidth = true, selectedItem, placeholder, items, onSelect, }: Props) => { const focusedItemBgColor = useColorModeValue('gray.200', 'gray.700') const selectedItemBgColor = useColorModeValue('blue.50', 'blue.400') const [isTouched, setIsTouched] = useState(false) const { onOpen, onClose, isOpen } = useDisclosure() const [inputValue, setInputValue] = useState( getItemLabel( items.find((item) => typeof item === 'string' ? selectedItem === item : selectedItem === item.value ) ) ) const [keyboardFocusIndex, setKeyboardFocusIndex] = useState< number | undefined >() const dropdownRef = useRef(null) const itemsRef = useRef<(HTMLButtonElement | null)[]>([]) const inputRef = useRef(null) const { ref: parentModalRef } = useParentModal() const filteredItems = ( isTouched ? [ ...items.filter((item) => getItemLabel(item) .toLowerCase() .includes((inputValue ?? '').toLowerCase()) ), ] : items ).slice(0, 50) const closeDropdown = () => { onClose() setTimeout(() => { setIsTouched(false) }, dropdownCloseAnimationDuration) } useOutsideClick({ ref: dropdownRef, handler: closeDropdown, isEnabled: isOpen, }) const updateInputValue = (e: ChangeEvent) => { if (!isOpen) onOpen() if (!isTouched) setIsTouched(true) setInputValue(e.target.value) } const handleItemClick = (item: T) => () => { if (!isTouched) setIsTouched(true) setInputValue(getItemLabel(item)) onSelect?.(getItemValue(item), item) setKeyboardFocusIndex(undefined) closeDropdown() } const updateFocusedDropdownItem = ( e: React.KeyboardEvent ) => { if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) { e.preventDefault() handleItemClick(filteredItems[keyboardFocusIndex])() return setKeyboardFocusIndex(undefined) } if (e.key === 'ArrowDown') { e.preventDefault() if (keyboardFocusIndex === undefined) return setKeyboardFocusIndex(0) if (keyboardFocusIndex === filteredItems.length - 1) return setKeyboardFocusIndex(0) itemsRef.current[keyboardFocusIndex + 1]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', }) return setKeyboardFocusIndex(keyboardFocusIndex + 1) } if (e.key === 'ArrowUp') { e.preventDefault() if (keyboardFocusIndex === 0 || keyboardFocusIndex === undefined) return setKeyboardFocusIndex(filteredItems.length - 1) itemsRef.current[keyboardFocusIndex - 1]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', }) setKeyboardFocusIndex(keyboardFocusIndex - 1) } } const clearSelection = (e: React.MouseEvent) => { e.preventDefault() setInputValue('') onSelect?.(undefined) setKeyboardFocusIndex(undefined) closeDropdown() } return ( {!isTouched && ( {inputValue} )} {selectedItem && ( } aria-label={'Clear'} size="sm" variant="ghost" pointerEvents="all" /> )} e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()} > {filteredItems.length > 0 && ( <> {filteredItems.map((item, idx) => { return ( ) })} )} ) } const getItemLabel = (item?: Item) => { if (!item) return '' if (typeof item === 'object') return item.label return item } const getItemValue = (item: Item) => { if (typeof item === 'object') return item.value return item }