feat(inputs): ✨ Add Condition step
This commit is contained in:
@ -1,7 +1,13 @@
|
||||
import { PopoverContent, PopoverArrow, PopoverBody } from '@chakra-ui/react'
|
||||
import {
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
useEventListener,
|
||||
} from '@chakra-ui/react'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import {
|
||||
ChoiceInputOptions,
|
||||
ConditionOptions,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
LogicStepType,
|
||||
@ -9,6 +15,7 @@ import {
|
||||
Step,
|
||||
TextInputOptions,
|
||||
} from 'models'
|
||||
import { useRef } from 'react'
|
||||
import {
|
||||
TextInputSettingsBody,
|
||||
NumberInputSettingsBody,
|
||||
@ -17,6 +24,7 @@ import {
|
||||
DateInputSettingsBody,
|
||||
} from './bodies'
|
||||
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
|
||||
import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
|
||||
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
||||
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
|
||||
|
||||
@ -25,12 +33,17 @@ type Props = {
|
||||
}
|
||||
|
||||
export const SettingsPopoverContent = ({ step }: Props) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()
|
||||
|
||||
const handleMouseWheel = (e: WheelEvent) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
useEventListener('wheel', handleMouseWheel, ref.current)
|
||||
return (
|
||||
<PopoverContent onMouseDown={handleMouseDown}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody p="6">
|
||||
<PopoverBody p="6" overflowY="scroll" maxH="400px" ref={ref}>
|
||||
<SettingsPopoverBodyContent step={step} />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
@ -40,7 +53,11 @@ export const SettingsPopoverContent = ({ step }: Props) => {
|
||||
const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
const { updateStep } = useTypebot()
|
||||
const handleOptionsChange = (
|
||||
options: TextInputOptions | ChoiceInputOptions | SetVariableOptions
|
||||
options:
|
||||
| TextInputOptions
|
||||
| ChoiceInputOptions
|
||||
| SetVariableOptions
|
||||
| ConditionOptions
|
||||
) => updateStep(step.id, { options } as Partial<InputStep>)
|
||||
|
||||
switch (step.type) {
|
||||
@ -108,6 +125,14 @@ const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case LogicStepType.CONDITION: {
|
||||
return (
|
||||
<ConditionSettingsBody
|
||||
options={step.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
|
@ -0,0 +1,157 @@
|
||||
import { Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
|
||||
import { PlusIcon, TrashIcon } from 'assets/icons'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { DropdownList } from 'components/shared/DropdownList'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import {
|
||||
Comparison,
|
||||
ComparisonOperators,
|
||||
LogicalOperator,
|
||||
Table,
|
||||
Variable,
|
||||
} from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { generate } from 'short-uuid'
|
||||
import { useImmer } from 'use-immer'
|
||||
|
||||
type Props = {
|
||||
initialComparisons: Table<Comparison>
|
||||
logicalOperator: LogicalOperator
|
||||
onLogicalOperatorChange: (logicalOperator: LogicalOperator) => void
|
||||
onComparisonsChange: (comparisons: Table<Comparison>) => void
|
||||
}
|
||||
|
||||
export const ComparisonsList = ({
|
||||
initialComparisons,
|
||||
logicalOperator,
|
||||
onLogicalOperatorChange,
|
||||
onComparisonsChange,
|
||||
}: Props) => {
|
||||
const [comparisons, setComparisons] = useImmer(initialComparisons)
|
||||
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
onComparisonsChange(comparisons)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [comparisons])
|
||||
|
||||
const createComparison = () => {
|
||||
setComparisons((comparisons) => {
|
||||
const id = generate()
|
||||
comparisons.byId[id] = {
|
||||
id,
|
||||
comparisonOperator: ComparisonOperators.EQUAL,
|
||||
}
|
||||
comparisons.allIds.push(id)
|
||||
})
|
||||
}
|
||||
|
||||
const updateComparison = (
|
||||
comparisonId: string,
|
||||
updates: Partial<Omit<Comparison, 'id'>>
|
||||
) =>
|
||||
setComparisons((comparisons) => {
|
||||
comparisons.byId[comparisonId] = {
|
||||
...comparisons.byId[comparisonId],
|
||||
...updates,
|
||||
}
|
||||
})
|
||||
|
||||
const deleteComparison = (comparisonId: string) => () => {
|
||||
setComparisons((comparisons) => {
|
||||
delete comparisons.byId[comparisonId]
|
||||
const index = comparisons.allIds.indexOf(comparisonId)
|
||||
if (index !== -1) comparisons.allIds.splice(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
const handleVariableSelected =
|
||||
(comparisonId: string) => (variable: Variable) => {
|
||||
updateComparison(comparisonId, { variableId: variable.id })
|
||||
}
|
||||
|
||||
const handleComparisonOperatorSelected =
|
||||
(comparisonId: string) => (dropdownItem: ComparisonOperators) =>
|
||||
updateComparison(comparisonId, {
|
||||
comparisonOperator: dropdownItem,
|
||||
})
|
||||
const handleLogicalOperatorSelected = (dropdownItem: LogicalOperator) =>
|
||||
onLogicalOperatorChange(dropdownItem)
|
||||
|
||||
const handleValueChange = (comparisonId: string) => (value: string) =>
|
||||
updateComparison(comparisonId, { value })
|
||||
|
||||
const handleMouseEnter = (comparisonId: string) => () => {
|
||||
setShowDeleteId(comparisonId)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => setShowDeleteId(undefined)
|
||||
|
||||
return (
|
||||
<Stack spacing="4" py="4">
|
||||
{comparisons.allIds.map((comparisonId, idx) => (
|
||||
<>
|
||||
{idx > 0 && (
|
||||
<Flex justify="center">
|
||||
<DropdownList<LogicalOperator>
|
||||
currentItem={logicalOperator}
|
||||
onItemSelect={handleLogicalOperatorSelected}
|
||||
items={Object.values(LogicalOperator)}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex
|
||||
pos="relative"
|
||||
onMouseEnter={handleMouseEnter(comparisonId)}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<Stack
|
||||
key={comparisonId}
|
||||
bgColor="blue.50"
|
||||
p="4"
|
||||
rounded="md"
|
||||
flex="1"
|
||||
>
|
||||
<VariableSearchInput
|
||||
initialVariableId={comparisons.byId[comparisonId].variableId}
|
||||
onSelectVariable={handleVariableSelected(comparisonId)}
|
||||
bgColor="white"
|
||||
placeholder="Search for a variable"
|
||||
/>
|
||||
<DropdownList<ComparisonOperators>
|
||||
currentItem={comparisons.byId[comparisonId].comparisonOperator}
|
||||
onItemSelect={handleComparisonOperatorSelected(comparisonId)}
|
||||
items={Object.values(ComparisonOperators)}
|
||||
bgColor="white"
|
||||
/>
|
||||
{comparisons.byId[comparisonId].comparisonOperator !==
|
||||
ComparisonOperators.IS_SET && (
|
||||
<DebouncedInput
|
||||
delay={100}
|
||||
initialValue={comparisons.byId[comparisonId].value ?? ''}
|
||||
onChange={handleValueChange(comparisonId)}
|
||||
bgColor="white"
|
||||
placeholder="Type a value..."
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<Fade in={showDeleteId === comparisonId}>
|
||||
<IconButton
|
||||
icon={<TrashIcon />}
|
||||
aria-label="Remove comparison"
|
||||
onClick={deleteComparison(comparisonId)}
|
||||
pos="absolute"
|
||||
left="-10px"
|
||||
top="-10px"
|
||||
size="sm"
|
||||
/>
|
||||
</Fade>
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
<Button leftIcon={<PlusIcon />} onClick={createComparison} flexShrink={0}>
|
||||
Add
|
||||
</Button>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { Comparison, ConditionOptions, LogicalOperator, Table } from 'models'
|
||||
import React from 'react'
|
||||
import { ComparisonsList } from './ComparisonsList'
|
||||
|
||||
type ConditionSettingsBodyProps = {
|
||||
options: ConditionOptions
|
||||
onOptionsChange: (options: ConditionOptions) => void
|
||||
}
|
||||
|
||||
export const ConditionSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: ConditionSettingsBodyProps) => {
|
||||
const handleComparisonsChange = (comparisons: Table<Comparison>) =>
|
||||
onOptionsChange({ ...options, comparisons })
|
||||
const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) =>
|
||||
onOptionsChange({ ...options, logicalOperator })
|
||||
|
||||
return (
|
||||
<ComparisonsList
|
||||
initialComparisons={options.comparisons}
|
||||
logicalOperator={options.logicalOperator ?? LogicalOperator.AND}
|
||||
onLogicalOperatorChange={handleLogicalOperatorChange}
|
||||
onComparisonsChange={handleComparisonsChange}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { ConditionSettingsBody } from './ConditonSettingsBody'
|
Reference in New Issue
Block a user