🐛 (editor) Improve variables dropdown auto focus
This commit is contained in:
@@ -11,21 +11,20 @@ import {
|
|||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
PopoverAnchor,
|
PopoverAnchor,
|
||||||
Portal,
|
Portal,
|
||||||
|
Tag,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
||||||
import { useTypebot } from '@/features/editor'
|
import { useTypebot } from '@/features/editor'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { byId, isDefined, isNotDefined } from 'utils'
|
||||||
import { byId, env, isDefined, isNotDefined } from 'utils'
|
|
||||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||||
import { useParentModal } from '@/features/graph'
|
import { useParentModal } from '@/features/graph'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialVariableId?: string
|
initialVariableId?: string
|
||||||
debounceTimeout?: number
|
autoFocus?: boolean
|
||||||
isDefaultOpen?: boolean
|
|
||||||
onSelectVariable: (
|
onSelectVariable: (
|
||||||
variable: Pick<Variable, 'id' | 'name'> | undefined
|
variable: Pick<Variable, 'id' | 'name'> | undefined
|
||||||
) => void
|
) => void
|
||||||
@@ -34,8 +33,7 @@ type Props = {
|
|||||||
export const VariableSearchInput = ({
|
export const VariableSearchInput = ({
|
||||||
initialVariableId,
|
initialVariableId,
|
||||||
onSelectVariable,
|
onSelectVariable,
|
||||||
isDefaultOpen,
|
autoFocus,
|
||||||
debounceTimeout = 1000,
|
|
||||||
...inputProps
|
...inputProps
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const bg = useColorModeValue('gray.200', 'gray.700')
|
const bg = useColorModeValue('gray.200', 'gray.700')
|
||||||
@@ -46,13 +44,6 @@ export const VariableSearchInput = ({
|
|||||||
const [inputValue, setInputValue] = useState(
|
const [inputValue, setInputValue] = useState(
|
||||||
variables.find(byId(initialVariableId))?.name ?? ''
|
variables.find(byId(initialVariableId))?.name ?? ''
|
||||||
)
|
)
|
||||||
const debounced = useDebouncedCallback(
|
|
||||||
(value) => {
|
|
||||||
const variable = variables.find((v) => v.name === value)
|
|
||||||
if (variable) onSelectVariable(variable)
|
|
||||||
},
|
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
|
||||||
)
|
|
||||||
const [filteredItems, setFilteredItems] = useState<Variable[]>(
|
const [filteredItems, setFilteredItems] = useState<Variable[]>(
|
||||||
variables ?? []
|
variables ?? []
|
||||||
)
|
)
|
||||||
@@ -67,25 +58,17 @@ export const VariableSearchInput = ({
|
|||||||
|
|
||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: dropdownRef,
|
ref: dropdownRef,
|
||||||
handler: onClose,
|
handler: () => onClose,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDefaultOpen) onOpen()
|
if (autoFocus) onOpen()
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
debounced.flush()
|
|
||||||
},
|
|
||||||
[debounced]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputValue(e.target.value)
|
setInputValue(e.target.value)
|
||||||
debounced(e.target.value)
|
|
||||||
if (e.target.value === '') {
|
if (e.target.value === '') {
|
||||||
onSelectVariable(undefined)
|
onSelectVariable(undefined)
|
||||||
setFilteredItems([...variables.slice(0, 50)])
|
setFilteredItems([...variables.slice(0, 50)])
|
||||||
@@ -104,6 +87,7 @@ export const VariableSearchInput = ({
|
|||||||
setInputValue(variable.name)
|
setInputValue(variable.name)
|
||||||
onSelectVariable(variable)
|
onSelectVariable(variable)
|
||||||
setKeyboardFocusIndex(undefined)
|
setKeyboardFocusIndex(undefined)
|
||||||
|
inputRef.current?.blur()
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +96,7 @@ export const VariableSearchInput = ({
|
|||||||
const id = 'v' + cuid()
|
const id = 'v' + cuid()
|
||||||
onSelectVariable({ id, name: inputValue })
|
onSelectVariable({ id, name: inputValue })
|
||||||
createVariable({ id, name: inputValue })
|
createVariable({ id, name: inputValue })
|
||||||
|
inputRef.current?.blur()
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +107,6 @@ export const VariableSearchInput = ({
|
|||||||
setFilteredItems(filteredItems.filter((item) => item.id !== variable.id))
|
setFilteredItems(filteredItems.filter((item) => item.id !== variable.id))
|
||||||
if (variable.name === inputValue) {
|
if (variable.name === inputValue) {
|
||||||
setInputValue('')
|
setInputValue('')
|
||||||
debounced('')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +131,12 @@ export const VariableSearchInput = ({
|
|||||||
if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) {
|
if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) {
|
||||||
if (keyboardFocusIndex === 0 && isCreateVariableButtonDisplayed)
|
if (keyboardFocusIndex === 0 && isCreateVariableButtonDisplayed)
|
||||||
handleCreateNewVariableClick()
|
handleCreateNewVariableClick()
|
||||||
else handleVariableNameClick(filteredItems[keyboardFocusIndex])()
|
else
|
||||||
|
handleVariableNameClick(
|
||||||
|
filteredItems[
|
||||||
|
keyboardFocusIndex - (isCreateVariableButtonDisplayed ? 1 : 0)
|
||||||
|
]
|
||||||
|
)()
|
||||||
return setKeyboardFocusIndex(undefined)
|
return setKeyboardFocusIndex(undefined)
|
||||||
}
|
}
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
@@ -199,6 +188,7 @@ export const VariableSearchInput = ({
|
|||||||
role="menu"
|
role="menu"
|
||||||
w="inherit"
|
w="inherit"
|
||||||
shadow="lg"
|
shadow="lg"
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{isCreateVariableButtonDisplayed && (
|
{isCreateVariableButtonDisplayed && (
|
||||||
<Button
|
<Button
|
||||||
@@ -215,7 +205,10 @@ export const VariableSearchInput = ({
|
|||||||
leftIcon={<PlusIcon />}
|
leftIcon={<PlusIcon />}
|
||||||
bgColor={keyboardFocusIndex === 0 ? bg : 'transparent'}
|
bgColor={keyboardFocusIndex === 0 ? bg : 'transparent'}
|
||||||
>
|
>
|
||||||
Create "{inputValue}"
|
Create
|
||||||
|
<Tag colorScheme="orange" ml="1">
|
||||||
|
{inputValue}
|
||||||
|
</Tag>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{filteredItems.length > 0 && (
|
{filteredItems.length > 0 && (
|
||||||
@@ -230,7 +223,6 @@ export const VariableSearchInput = ({
|
|||||||
role="menuitem"
|
role="menuitem"
|
||||||
minH="40px"
|
minH="40px"
|
||||||
key={idx}
|
key={idx}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
onClick={handleVariableNameClick(item)}
|
onClick={handleVariableNameClick(item)}
|
||||||
fontSize="16px"
|
fontSize="16px"
|
||||||
fontWeight="normal"
|
fontWeight="normal"
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const TextBubbleEditorContent = ({
|
|||||||
<VariableSearchInput
|
<VariableSearchInput
|
||||||
onSelectVariable={handleVariableSelected}
|
onSelectVariable={handleVariableSelected}
|
||||||
placeholder="Search for a variable"
|
placeholder="Search for a variable"
|
||||||
isDefaultOpen
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ type Props = {
|
|||||||
export const ToolBar = ({ onVariablesButtonClick, ...props }: Props) => {
|
export const ToolBar = ({ onVariablesButtonClick, ...props }: Props) => {
|
||||||
const editor = usePlateEditorRef()
|
const editor = usePlateEditorRef()
|
||||||
const handleVariablesButtonMouseDown = (e: React.MouseEvent) => {
|
const handleVariablesButtonMouseDown = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
onVariablesButtonClick()
|
onVariablesButtonClick()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ test.describe('Text bubble block', () => {
|
|||||||
await page.press('div[role="textbox"]', 'Shift+Enter')
|
await page.press('div[role="textbox"]', 'Shift+Enter')
|
||||||
await page.click('button[aria-label="Insert variable"]')
|
await page.click('button[aria-label="Insert variable"]')
|
||||||
await page.fill('[data-testid="variables-input"]', 'test')
|
await page.fill('[data-testid="variables-input"]', 'test')
|
||||||
await page.click('text=Create "test"')
|
await page.getByRole('menuitem', { name: 'Create test' }).click()
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -169,5 +169,5 @@ const fillInSpreadsheetInfo = async (page: Page) => {
|
|||||||
|
|
||||||
const createNewVar = async (page: Page, name: string) => {
|
const createNewVar = async (page: Page, name: string) => {
|
||||||
await page.fill('input[placeholder="Select a variable"] >> nth=-1', name)
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', name)
|
||||||
await page.click(`text=Create "${name}"`)
|
await page.getByRole('menuitem', { name: `Create ${name}` }).click()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const DataVariableInputs = ({
|
|||||||
item,
|
item,
|
||||||
onItemChange,
|
onItemChange,
|
||||||
dataItems,
|
dataItems,
|
||||||
debounceTimeout,
|
|
||||||
}: TableListItemProps<ResponseVariableMapping> & { dataItems: string[] }) => {
|
}: TableListItemProps<ResponseVariableMapping> & { dataItems: string[] }) => {
|
||||||
const handleBodyPathChange = (bodyPath: string) =>
|
const handleBodyPathChange = (bodyPath: string) =>
|
||||||
onItemChange({ ...item, bodyPath })
|
onItemChange({ ...item, bodyPath })
|
||||||
@@ -24,7 +23,6 @@ export const DataVariableInputs = ({
|
|||||||
value={item.bodyPath}
|
value={item.bodyPath}
|
||||||
onValueChange={handleBodyPathChange}
|
onValueChange={handleBodyPathChange}
|
||||||
placeholder="Select the data"
|
placeholder="Select the data"
|
||||||
debounceTimeout={debounceTimeout}
|
|
||||||
withVariableButton
|
withVariableButton
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -34,7 +32,6 @@ export const DataVariableInputs = ({
|
|||||||
onSelectVariable={handleVariableChange}
|
onSelectVariable={handleVariableChange}
|
||||||
placeholder="Search for a variable"
|
placeholder="Search for a variable"
|
||||||
initialVariableId={item.variableId}
|
initialVariableId={item.variableId}
|
||||||
debounceTimeout={debounceTimeout}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export const VariableForTestInputs = ({
|
|||||||
id={'name' + item.id}
|
id={'name' + item.id}
|
||||||
initialVariableId={item.variableId}
|
initialVariableId={item.variableId}
|
||||||
onSelectVariable={handleVariableSelect}
|
onSelectVariable={handleVariableSelect}
|
||||||
debounceTimeout={debounceTimeout}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ test.describe('Set variable block', () => {
|
|||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Type a number...')
|
await page.click('text=Type a number...')
|
||||||
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Num')
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Num')
|
||||||
await page.click('text=Create "Num"')
|
await page.getByRole('menuitem', { name: 'Create Num' }).click()
|
||||||
|
|
||||||
await page.click('text=Click to edit... >> nth = 0')
|
await page.click('text=Click to edit... >> nth = 0')
|
||||||
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Total')
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Total')
|
||||||
await page.click('text=Create "Total"')
|
await page.getByRole('menuitem', { name: 'Create Total' }).click()
|
||||||
await page.fill('textarea', '1000 * {{Num}}')
|
await page.fill('textarea', '1000 * {{Num}}')
|
||||||
|
|
||||||
await page.click('text=Click to edit...', { force: true })
|
await page.click('text=Click to edit...', { force: true })
|
||||||
@@ -30,7 +30,7 @@ test.describe('Set variable block', () => {
|
|||||||
'input[placeholder="Select a variable"] >> nth=-1',
|
'input[placeholder="Select a variable"] >> nth=-1',
|
||||||
'Custom var'
|
'Custom var'
|
||||||
)
|
)
|
||||||
await page.click('text=Create "Custom var"')
|
await page.getByRole('menuitem', { name: 'Create Custom var' }).click()
|
||||||
await page.fill('textarea', 'Custom value')
|
await page.fill('textarea', 'Custom value')
|
||||||
|
|
||||||
await page.click('text=Click to edit...', { force: true })
|
await page.click('text=Click to edit...', { force: true })
|
||||||
@@ -38,7 +38,7 @@ test.describe('Set variable block', () => {
|
|||||||
'input[placeholder="Select a variable"] >> nth=-1',
|
'input[placeholder="Select a variable"] >> nth=-1',
|
||||||
'Addition'
|
'Addition'
|
||||||
)
|
)
|
||||||
await page.click('text=Create "Addition"')
|
await page.getByRole('menuitem', { name: 'Create Addition' }).click()
|
||||||
await page.fill('textarea', '1000 + {{Total}}')
|
await page.fill('textarea', '1000 + {{Total}}')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
|
|||||||
onSelectVariable={onSelectVariable}
|
onSelectVariable={onSelectVariable}
|
||||||
placeholder="Search for a variable"
|
placeholder="Search for a variable"
|
||||||
shadow="lg"
|
shadow="lg"
|
||||||
isDefaultOpen
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
Reference in New Issue
Block a user