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

195 lines
5.1 KiB
TypeScript
Raw Normal View History

2021-12-23 09:37:42 +01:00
import {
useDisclosure,
useOutsideClick,
Flex,
Popover,
PopoverTrigger,
Input,
PopoverContent,
Button,
InputProps,
HStack,
2021-12-23 09:37:42 +01:00
} from '@chakra-ui/react'
import { Variable } from 'models'
2021-12-23 09:37:42 +01:00
import { useState, useRef, useEffect, ChangeEvent } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { isEmpty } from 'utils'
import { VariablesButton } from './buttons/VariablesButton'
2021-12-23 09:37:42 +01:00
type Props = {
selectedItem?: string
items: string[]
debounceTimeout?: number
withVariableButton?: boolean
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,
withVariableButton = false,
debounceTimeout = 1000,
2022-01-22 18:24:57 +01:00
onValueChange,
...inputProps
}: Props) => {
const [carretPosition, setCarretPosition] = useState<number>(0)
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 ?? '')
const debounced = useDebouncedCallback(
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValueChange ? onValueChange : () => {},
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
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<HTMLInputElement>(null)
2021-12-23 09:37:42 +01:00
useEffect(
() => () => {
debounced.flush()
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
2021-12-23 09:37:42 +01:00
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,
})
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!isOpen) onOpen()
2021-12-23 09:37:42 +01:00
setInputValue(e.target.value)
debounced(e.target.value)
2021-12-23 09:37:42 +01:00
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)
debounced(item)
onClose()
}
const handleVariableSelected = (variable?: Variable) => {
if (!inputRef.current || !variable) return
const cursorPosition = carretPosition
const textBeforeCursorPosition = inputRef.current.value.substring(
0,
cursorPosition
)
const textAfterCursorPosition = inputRef.current.value.substring(
cursorPosition,
inputRef.current.value.length
)
const newValue =
textBeforeCursorPosition +
`{{${variable.name}}}` +
textAfterCursorPosition
setInputValue(newValue)
debounced(newValue)
inputRef.current.focus()
setTimeout(() => {
if (!inputRef.current) return
inputRef.current.selectionStart = inputRef.current.selectionEnd =
carretPosition + `{{${variable.name}}}`.length
setCarretPosition(inputRef.current.selectionStart)
}, 100)
}
const handleKeyUp = () => {
if (!inputRef.current?.selectionStart) return
setCarretPosition(inputRef.current.selectionStart)
}
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>
<HStack spacing={0} align={'flex-end'} w="full">
<Input
ref={inputRef}
value={inputValue}
onChange={onInputChange}
onClick={onOpen}
type="text"
onKeyUp={handleKeyUp}
{...inputProps}
/>
{withVariableButton && (
<VariablesButton
onSelectVariable={handleVariableSelected}
onClick={onClose}
/>
)}
</HStack>
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>
)
}