2
0
Files
bot/apps/builder/components/shared/SearchableDropdown.tsx

144 lines
3.5 KiB
TypeScript
Raw Normal View History

2021-12-23 09:37:42 +01:00
import {
useDisclosure,
useOutsideClick,
Flex,
Popover,
PopoverTrigger,
Input,
PopoverContent,
Button,
InputProps,
2021-12-23 09:37:42 +01:00
} from '@chakra-ui/react'
import { useState, useRef, useEffect, ChangeEvent } from 'react'
2022-01-22 18:24:57 +01:00
import { useDebounce } from 'use-debounce'
2021-12-23 09:37:42 +01:00
type Props = {
selectedItem?: string
items: string[]
debounceTimeout?: number
2022-01-22 18:24:57 +01:00
onValueChange?: (value: string) => void
} & InputProps
2022-01-22 18:24:57 +01:00
2021-12-23 09:37:42 +01:00
export const SearchableDropdown = ({
selectedItem,
items,
debounceTimeout = 1000,
2022-01-22 18:24:57 +01:00
onValueChange,
...inputProps
}: Props) => {
2021-12-23 09:37:42 +01:00
const { onOpen, onClose, isOpen } = useDisclosure()
2022-01-22 18:24:57 +01:00
const [inputValue, setInputValue] = useState(selectedItem ?? '')
2022-03-09 15:12:00 +01:00
const [debouncedInputValue] = useDebounce(
inputValue,
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
2022-03-09 15:12:00 +01:00
)
2021-12-23 09:37:42 +01:00
const [filteredItems, setFilteredItems] = useState([
...items
.filter((item) =>
item.toLowerCase().includes((selectedItem ?? '').toLowerCase())
)
.slice(0, 50),
])
const dropdownRef = useRef(null)
const inputRef = useRef(null)
useEffect(() => {
if (filteredItems.length > 0) return
setFilteredItems([
...items
.filter((item) =>
item.toLowerCase().includes((selectedItem ?? '').toLowerCase())
)
.slice(0, 50),
])
if (inputRef.current === document.activeElement) onOpen()
2021-12-23 09:37:42 +01:00
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items])
useOutsideClick({
ref: dropdownRef,
handler: onClose,
})
2022-01-22 18:24:57 +01:00
useEffect(() => {
onValueChange &&
debouncedInputValue !== selectedItem &&
onValueChange(debouncedInputValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedInputValue])
2021-12-23 09:37:42 +01:00
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!isOpen) onOpen()
2021-12-23 09:37:42 +01:00
setInputValue(e.target.value)
if (e.target.value === '') {
setFilteredItems([...items.slice(0, 50)])
return
}
setFilteredItems([
...items
.filter((item) =>
item.toLowerCase().includes((inputValue ?? '').toLowerCase())
)
.slice(0, 50),
])
}
const handleItemClick = (item: string) => () => {
setInputValue(item)
onClose()
}
2021-12-23 09:37:42 +01:00
return (
<Flex ref={dropdownRef} w="full">
<Popover
isOpen={isOpen}
initialFocusRef={inputRef}
matchWidth
offset={[0, 0]}
isLazy
>
2021-12-23 09:37:42 +01:00
<PopoverTrigger>
<Input
ref={inputRef}
value={inputValue}
onChange={onInputChange}
onClick={onOpen}
type="text"
{...inputProps}
2021-12-23 09:37:42 +01:00
/>
</PopoverTrigger>
<PopoverContent
maxH="35vh"
overflowY="scroll"
role="menu"
w="inherit"
shadow="lg"
>
{filteredItems.length > 0 && (
2021-12-23 09:37:42 +01:00
<>
{filteredItems.map((item, idx) => {
return (
<Button
minH="40px"
key={idx}
onClick={handleItemClick(item)}
2021-12-23 09:37:42 +01:00
fontSize="16px"
fontWeight="normal"
rounded="none"
colorScheme="gray"
role="menuitem"
2021-12-23 09:37:42 +01:00
variant="ghost"
justifyContent="flex-start"
>
{item}
</Button>
)
})}
</>
)}
</PopoverContent>
</Popover>
</Flex>
)
}