2
0
Files
bot/apps/builder/components/shared/VariableSearchInput.tsx
2022-03-22 15:32:48 +01:00

192 lines
5.1 KiB
TypeScript

import {
useDisclosure,
useOutsideClick,
Flex,
Popover,
PopoverTrigger,
Input,
PopoverContent,
Button,
InputProps,
IconButton,
} from '@chakra-ui/react'
import { PlusIcon, TrashIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext'
import cuid from 'cuid'
import { Variable } from 'models'
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { byId, isNotDefined } from 'utils'
type Props = {
initialVariableId?: string
debounceTimeout?: number
isDefaultOpen?: boolean
onSelectVariable: (
variable: Pick<Variable, 'id' | 'name'> | undefined
) => void
} & InputProps
export const VariableSearchInput = ({
initialVariableId,
onSelectVariable,
isDefaultOpen,
debounceTimeout = 1000,
...inputProps
}: Props) => {
const { onOpen, onClose, isOpen } = useDisclosure()
const { typebot, createVariable, deleteVariable } = useTypebot()
const variables = typebot?.variables ?? []
const [inputValue, setInputValue] = useState(
variables.find(byId(initialVariableId))?.name ?? ''
)
const debounced = useDebouncedCallback(
(value) => {
const variable = variables.find((v) => v.name === value)
if (variable) onSelectVariable(variable)
},
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
)
const [filteredItems, setFilteredItems] = useState<Variable[]>(
variables ?? []
)
const dropdownRef = useRef(null)
const inputRef = useRef(null)
useOutsideClick({
ref: dropdownRef,
handler: onClose,
})
useEffect(() => {
if (isDefaultOpen) onOpen()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(
() => () => {
debounced.flush()
},
[debounced]
)
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value)
debounced(e.target.value)
onOpen()
if (e.target.value === '') {
setFilteredItems([...variables.slice(0, 50)])
return
}
setFilteredItems([
...variables
.filter((item) =>
item.name.toLowerCase().includes((e.target.value ?? '').toLowerCase())
)
.slice(0, 50),
])
}
const handleVariableNameClick = (variable: Variable) => () => {
setInputValue(variable.name)
onSelectVariable(variable)
onClose()
}
const handleCreateNewVariableClick = () => {
if (!inputValue || inputValue === '') return
const id = cuid()
onSelectVariable({ id, name: inputValue })
createVariable({ id, name: inputValue })
onClose()
}
const handleDeleteVariableClick =
(variable: Variable) => (e: React.MouseEvent) => {
e.stopPropagation()
deleteVariable(variable.id)
setFilteredItems(filteredItems.filter((item) => item.id !== variable.id))
if (variable.name === inputValue) {
setInputValue('')
debounced('')
}
}
return (
<Flex ref={dropdownRef} w="full">
<Popover
isOpen={isOpen}
initialFocusRef={inputRef}
matchWidth
isLazy
offset={[0, 2]}
>
<PopoverTrigger>
<Input
data-testid="variables-input"
ref={inputRef}
value={inputValue}
onChange={onInputChange}
onClick={onOpen}
placeholder={inputProps.placeholder ?? 'Select a variable'}
{...inputProps}
/>
</PopoverTrigger>
<PopoverContent
maxH="35vh"
overflowY="scroll"
role="menu"
w="inherit"
shadow="lg"
>
{(inputValue?.length ?? 0) > 0 &&
isNotDefined(variables.find((v) => v.name === inputValue)) && (
<Button
role="menuitem"
minH="40px"
onClick={handleCreateNewVariableClick}
fontSize="16px"
fontWeight="normal"
rounded="none"
colorScheme="gray"
variant="ghost"
justifyContent="flex-start"
leftIcon={<PlusIcon />}
>
Create "{inputValue}"
</Button>
)}
{filteredItems.length > 0 && (
<>
{filteredItems.map((item, idx) => {
return (
<Button
role="menuitem"
minH="40px"
key={idx}
onClick={handleVariableNameClick(item)}
fontSize="16px"
fontWeight="normal"
rounded="none"
colorScheme="gray"
variant="ghost"
justifyContent="space-between"
>
{item.name}
<IconButton
icon={<TrashIcon />}
aria-label="Remove variable"
size="xs"
onClick={handleDeleteVariableClick(item)}
/>
</Button>
)
})}
</>
)}
</PopoverContent>
</Popover>
</Flex>
)
}