feat(inputs): ✨ Add buttons input
This commit is contained in:
@ -21,12 +21,11 @@ type Props = {
|
||||
|
||||
export const BlockNode = ({ block }: Props) => {
|
||||
const { connectingIds, setConnectingIds, previewingIds } = useGraph()
|
||||
const { typebot, updateBlock, createStep } = useTypebot()
|
||||
const { draggedStep, draggedStepType, setDraggedStepType, setDraggedStep } =
|
||||
useDnd()
|
||||
const { typebot, updateBlock } = useTypebot()
|
||||
const { setMouseOverBlockId } = useDnd()
|
||||
const { draggedStep, draggedStepType } = useDnd()
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
const [titleValue, setTitleValue] = useState(block.title)
|
||||
const [showSortPlaceholders, setShowSortPlaceholders] = useState(false)
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const isPreviewing = useMemo(
|
||||
() =>
|
||||
@ -66,28 +65,16 @@ export const BlockNode = ({ block }: Props) => {
|
||||
useEventListener('mousemove', handleMouseMove)
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (draggedStepType || draggedStep) setShowSortPlaceholders(true)
|
||||
if (draggedStepType || draggedStep) setMouseOverBlockId(block.id)
|
||||
if (connectingIds)
|
||||
setConnectingIds({ ...connectingIds, target: { blockId: block.id } })
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setShowSortPlaceholders(false)
|
||||
setMouseOverBlockId(undefined)
|
||||
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
|
||||
}
|
||||
|
||||
const handleStepDrop = (index: number) => {
|
||||
setShowSortPlaceholders(false)
|
||||
if (draggedStepType) {
|
||||
createStep(block.id, draggedStepType, index)
|
||||
setDraggedStepType(undefined)
|
||||
}
|
||||
if (draggedStep) {
|
||||
createStep(block.id, draggedStep, index)
|
||||
setDraggedStep(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu<HTMLDivElement>
|
||||
renderMenu={() => <BlockNodeContextMenu blockId={block.id} />}
|
||||
@ -125,8 +112,6 @@ export const BlockNode = ({ block }: Props) => {
|
||||
<StepsList
|
||||
blockId={block.id}
|
||||
steps={filterTable(block.stepIds, typebot?.steps)}
|
||||
showSortPlaceholders={showSortPlaceholders}
|
||||
onMouseUp={handleStepDrop}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -0,0 +1,148 @@
|
||||
import {
|
||||
EditablePreview,
|
||||
EditableInput,
|
||||
Editable,
|
||||
useEventListener,
|
||||
Flex,
|
||||
Fade,
|
||||
IconButton,
|
||||
} from '@chakra-ui/react'
|
||||
import { PlusIcon } from 'assets/icons'
|
||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ChoiceInputStep, ChoiceItem } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { isDefined, isSingleChoiceInput } from 'utils'
|
||||
import { SourceEndpoint } from '../SourceEndpoint'
|
||||
import { ChoiceItemNodeContextMenu } from './ChoiceItemNodeContextMenu'
|
||||
|
||||
type ChoiceItemNodeProps = {
|
||||
item: ChoiceItem
|
||||
onMouseMoveBottomOfElement?: () => void
|
||||
onMouseMoveTopOfElement?: () => void
|
||||
onMouseDown?: (
|
||||
stepNodePosition: { absolute: Coordinates; relative: Coordinates },
|
||||
item: ChoiceItem
|
||||
) => void
|
||||
}
|
||||
|
||||
export const ChoiceItemNode = ({
|
||||
item,
|
||||
onMouseDown,
|
||||
onMouseMoveBottomOfElement,
|
||||
onMouseMoveTopOfElement,
|
||||
}: ChoiceItemNodeProps) => {
|
||||
const { deleteChoiceItem, updateChoiceItem, typebot, createChoiceItem } =
|
||||
useTypebot()
|
||||
const [mouseDownEvent, setMouseDownEvent] =
|
||||
useState<{ absolute: Coordinates; relative: Coordinates }>()
|
||||
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
if (!onMouseDown) return
|
||||
e.stopPropagation()
|
||||
const element = e.currentTarget as HTMLDivElement
|
||||
const rect = element.getBoundingClientRect()
|
||||
const relativeX = e.clientX - rect.left
|
||||
const relativeY = e.clientY - rect.top
|
||||
setMouseDownEvent({
|
||||
absolute: { x: e.clientX + relativeX, y: e.clientY + relativeY },
|
||||
relative: { x: relativeX, y: relativeY },
|
||||
})
|
||||
}
|
||||
|
||||
const handleGlobalMouseUp = () => {
|
||||
setMouseDownEvent(undefined)
|
||||
}
|
||||
useEventListener('mouseup', handleGlobalMouseUp)
|
||||
|
||||
const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!onMouseMoveBottomOfElement || !onMouseMoveTopOfElement) return
|
||||
const isMovingAndIsMouseDown =
|
||||
mouseDownEvent &&
|
||||
onMouseDown &&
|
||||
(event.movementX > 0 || event.movementY > 0)
|
||||
if (isMovingAndIsMouseDown) {
|
||||
onMouseDown(mouseDownEvent, item)
|
||||
deleteChoiceItem(item.id)
|
||||
setMouseDownEvent(undefined)
|
||||
}
|
||||
const element = event.currentTarget as HTMLDivElement
|
||||
const rect = element.getBoundingClientRect()
|
||||
const y = event.clientY - rect.top
|
||||
if (y > rect.height / 2) onMouseMoveBottomOfElement()
|
||||
else onMouseMoveTopOfElement()
|
||||
}
|
||||
|
||||
const handleInputSubmit = (content: string) =>
|
||||
updateChoiceItem(item.id, { content: content === '' ? undefined : content })
|
||||
|
||||
const handlePlusClick = () => {
|
||||
const nextIndex =
|
||||
(
|
||||
typebot?.steps.byId[item.stepId] as ChoiceInputStep
|
||||
).options.itemIds.indexOf(item.id) + 1
|
||||
createChoiceItem({ stepId: item.stepId }, nextIndex)
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => setIsMouseOver(true)
|
||||
const handleMouseLeave = () => setIsMouseOver(false)
|
||||
return (
|
||||
<ContextMenu<HTMLDivElement>
|
||||
renderMenu={() => <ChoiceItemNodeContextMenu itemId={item.id} />}
|
||||
>
|
||||
{(ref, isOpened) => (
|
||||
<Flex
|
||||
align="center"
|
||||
pos="relative"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
justifyContent="center"
|
||||
>
|
||||
<Editable
|
||||
ref={ref}
|
||||
px="4"
|
||||
py="2"
|
||||
rounded="md"
|
||||
bgColor="green.200"
|
||||
borderWidth="2px"
|
||||
borderColor={isOpened ? 'blue.400' : 'gray.400'}
|
||||
defaultValue={item.content ?? 'Click to edit'}
|
||||
flex="1"
|
||||
startWithEditView={!isDefined(item.content)}
|
||||
onSubmit={handleInputSubmit}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<EditablePreview />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
{typebot && isSingleChoiceInput(typebot.steps.byId[item.stepId]) && (
|
||||
<SourceEndpoint
|
||||
source={{
|
||||
blockId: typebot.steps.byId[item.stepId].blockId,
|
||||
stepId: item.stepId,
|
||||
choiceItemId: item.id,
|
||||
}}
|
||||
pos="absolute"
|
||||
right="15px"
|
||||
/>
|
||||
)}
|
||||
<Fade
|
||||
in={isMouseOver}
|
||||
style={{ position: 'absolute', bottom: '-15px', zIndex: 3 }}
|
||||
unmountOnExit
|
||||
>
|
||||
<IconButton
|
||||
aria-label="Add item"
|
||||
icon={<PlusIcon />}
|
||||
size="xs"
|
||||
onClick={handlePlusClick}
|
||||
/>
|
||||
</Fade>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { MenuList, MenuItem } from '@chakra-ui/react'
|
||||
import { TrashIcon } from 'assets/icons'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
|
||||
export const ChoiceItemNodeContextMenu = ({ itemId }: { itemId: string }) => {
|
||||
const { deleteChoiceItem } = useTypebot()
|
||||
|
||||
const handleDeleteClick = () => deleteChoiceItem(itemId)
|
||||
|
||||
return (
|
||||
<MenuList>
|
||||
<MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
)
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { Flex, FlexProps } from '@chakra-ui/react'
|
||||
import { ChoiceItem } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type ChoiceItemNodeOverlayProps = {
|
||||
item: ChoiceItem
|
||||
} & FlexProps
|
||||
|
||||
export const ChoiceItemNodeOverlay = ({
|
||||
item,
|
||||
...props
|
||||
}: ChoiceItemNodeOverlayProps) => {
|
||||
return (
|
||||
<Flex
|
||||
px="4"
|
||||
py="2"
|
||||
rounded="md"
|
||||
bgColor="green.200"
|
||||
borderWidth="2px"
|
||||
borderColor={'gray.400'}
|
||||
w="212px"
|
||||
pointerEvents="none"
|
||||
{...props}
|
||||
>
|
||||
{item.content ?? 'Click to edit'}
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
import { Flex, Portal, Stack, Text, useEventListener } from '@chakra-ui/react'
|
||||
import { useDnd } from 'contexts/DndContext'
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ChoiceInputStep, ChoiceItem } from 'models'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { SourceEndpoint } from '../SourceEndpoint'
|
||||
import { ChoiceItemNode } from './ChoiceItemNode'
|
||||
import { ChoiceItemNodeOverlay } from './ChoiceItemNodeOverlay'
|
||||
|
||||
type ChoiceItemsListProps = {
|
||||
step: ChoiceInputStep
|
||||
}
|
||||
|
||||
export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
|
||||
const { typebot, createChoiceItem } = useTypebot()
|
||||
const {
|
||||
draggedChoiceItem,
|
||||
mouseOverBlockId,
|
||||
setDraggedChoiceItem,
|
||||
setMouseOverBlockId,
|
||||
} = useDnd()
|
||||
const showSortPlaceholders = useMemo(
|
||||
() => mouseOverBlockId === step.blockId && draggedChoiceItem,
|
||||
[draggedChoiceItem, mouseOverBlockId, step.blockId]
|
||||
)
|
||||
const [position, setPosition] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
const [relativeCoordinates, setRelativeCoordinates] = useState({ x: 0, y: 0 })
|
||||
const [expandedPlaceholderIndex, setExpandedPlaceholderIndex] = useState<
|
||||
number | undefined
|
||||
>()
|
||||
|
||||
const handleStepMove = (event: MouseEvent) => {
|
||||
if (!draggedChoiceItem) return
|
||||
const { clientX, clientY } = event
|
||||
setPosition({
|
||||
...position,
|
||||
x: clientX - relativeCoordinates.x,
|
||||
y: clientY - relativeCoordinates.y,
|
||||
})
|
||||
}
|
||||
useEventListener('mousemove', handleStepMove)
|
||||
|
||||
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (expandedPlaceholderIndex === undefined || !draggedChoiceItem) return
|
||||
e.stopPropagation()
|
||||
setMouseOverBlockId(undefined)
|
||||
setExpandedPlaceholderIndex(undefined)
|
||||
setDraggedChoiceItem(undefined)
|
||||
createChoiceItem(draggedChoiceItem, expandedPlaceholderIndex)
|
||||
}
|
||||
|
||||
const handleStepMouseDown = (
|
||||
{ absolute, relative }: { absolute: Coordinates; relative: Coordinates },
|
||||
item: ChoiceItem
|
||||
) => {
|
||||
setPosition(absolute)
|
||||
setRelativeCoordinates(relative)
|
||||
setMouseOverBlockId(typebot?.steps.byId[item.stepId].blockId)
|
||||
setDraggedChoiceItem(item)
|
||||
}
|
||||
|
||||
const handleMouseOnTopOfStep = (stepIndex: number) => {
|
||||
if (!draggedChoiceItem) return
|
||||
setExpandedPlaceholderIndex(stepIndex === 0 ? 0 : stepIndex)
|
||||
}
|
||||
|
||||
const handleMouseOnBottomOfStep = (stepIndex: number) => {
|
||||
if (!draggedChoiceItem) return
|
||||
setExpandedPlaceholderIndex(stepIndex + 1)
|
||||
}
|
||||
|
||||
const stopPropagating = (e: React.MouseEvent) => e.stopPropagation()
|
||||
|
||||
return (
|
||||
<Stack
|
||||
flex={1}
|
||||
spacing={1}
|
||||
onMouseUpCapture={handleMouseUp}
|
||||
onClick={stopPropagating}
|
||||
>
|
||||
<Flex
|
||||
h={expandedPlaceholderIndex === 0 ? '50px' : '2px'}
|
||||
bgColor={'gray.400'}
|
||||
visibility={showSortPlaceholders ? 'visible' : 'hidden'}
|
||||
rounded="lg"
|
||||
transition={showSortPlaceholders ? 'height 200ms' : 'none'}
|
||||
/>
|
||||
{step.options.itemIds.map((itemId, idx) => (
|
||||
<Stack key={itemId} spacing={1}>
|
||||
{typebot?.choiceItems.byId[itemId] && (
|
||||
<ChoiceItemNode
|
||||
item={typebot?.choiceItems.byId[itemId]}
|
||||
onMouseMoveTopOfElement={() => handleMouseOnTopOfStep(idx)}
|
||||
onMouseMoveBottomOfElement={() => {
|
||||
handleMouseOnBottomOfStep(idx)
|
||||
}}
|
||||
onMouseDown={handleStepMouseDown}
|
||||
/>
|
||||
)}
|
||||
<Flex
|
||||
h={
|
||||
showSortPlaceholders && expandedPlaceholderIndex === idx + 1
|
||||
? '50px'
|
||||
: '2px'
|
||||
}
|
||||
bgColor={'gray.400'}
|
||||
visibility={showSortPlaceholders ? 'visible' : 'hidden'}
|
||||
rounded="lg"
|
||||
transition={showSortPlaceholders ? 'height 200ms' : 'none'}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack>
|
||||
<Flex
|
||||
px="4"
|
||||
py="2"
|
||||
bgColor="gray.200"
|
||||
borderWidth="2px"
|
||||
rounded="md"
|
||||
pos="relative"
|
||||
align="center"
|
||||
>
|
||||
<Text>Default</Text>
|
||||
<SourceEndpoint
|
||||
source={{
|
||||
blockId: step.blockId,
|
||||
stepId: step.id,
|
||||
}}
|
||||
pos="absolute"
|
||||
right="15px"
|
||||
/>
|
||||
</Flex>
|
||||
</Stack>
|
||||
|
||||
{draggedChoiceItem && draggedChoiceItem.stepId === step.id && (
|
||||
<Portal>
|
||||
<ChoiceItemNodeOverlay
|
||||
item={draggedChoiceItem}
|
||||
onMouseUp={handleMouseUp}
|
||||
pos="fixed"
|
||||
top="0"
|
||||
left="0"
|
||||
style={{
|
||||
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`,
|
||||
}}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { ChoiceItemsList as ChoiceInputStepNodeContent } from './ChoiceItemsList'
|
@ -1,6 +1,11 @@
|
||||
import { PopoverContent, PopoverArrow, PopoverBody } from '@chakra-ui/react'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { InputStepType, Step, TextInputOptions } from 'models'
|
||||
import {
|
||||
ChoiceInputOptions,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
TextInputOptions,
|
||||
} from 'models'
|
||||
import {
|
||||
TextInputSettingsBody,
|
||||
NumberInputSettingsBody,
|
||||
@ -8,10 +13,11 @@ import {
|
||||
UrlInputSettingsBody,
|
||||
DateInputSettingsBody,
|
||||
} from './bodies'
|
||||
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
|
||||
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
||||
|
||||
type Props = {
|
||||
step: Step
|
||||
step: InputStep
|
||||
}
|
||||
export const SettingsPopoverContent = ({ step }: Props) => {
|
||||
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()
|
||||
@ -28,8 +34,9 @@ export const SettingsPopoverContent = ({ step }: Props) => {
|
||||
|
||||
const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
const { updateStep } = useTypebot()
|
||||
const handleOptionsChange = (options: TextInputOptions) =>
|
||||
updateStep(step.id, { options } as Partial<Step>)
|
||||
const handleOptionsChange = (
|
||||
options: TextInputOptions | ChoiceInputOptions
|
||||
) => updateStep(step.id, { options } as Partial<InputStep>)
|
||||
|
||||
switch (step.type) {
|
||||
case InputStepType.TEXT: {
|
||||
@ -80,6 +87,14 @@ const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.CHOICE: {
|
||||
return (
|
||||
<ChoiceInputSettingsBody
|
||||
options={step.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { ChoiceInputOptions } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type ChoiceInputSettingsBodyProps = {
|
||||
options?: ChoiceInputOptions
|
||||
onOptionsChange: (options: ChoiceInputOptions) => void
|
||||
}
|
||||
|
||||
export const ChoiceInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: ChoiceInputSettingsBodyProps) => {
|
||||
const handleIsMultipleChange = (isMultipleChoice: boolean) =>
|
||||
options && onOptionsChange({ ...options, isMultipleChoice })
|
||||
const handleButtonLabelChange = (buttonLabel: string) =>
|
||||
options && onOptionsChange({ ...options, buttonLabel })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<SwitchWithLabel
|
||||
id={'is-multiple'}
|
||||
label={'Multiple choice?'}
|
||||
initialValue={options?.isMultipleChoice ?? false}
|
||||
onCheckChange={handleIsMultipleChange}
|
||||
/>
|
||||
{options?.isMultipleChoice && (
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="send">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="send"
|
||||
initialValue={options?.buttonLabel ?? 'Send'}
|
||||
delay={100}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react'
|
||||
import { ConnectingSourceIds, useGraph } from 'contexts/GraphContext'
|
||||
import React, { MouseEvent } from 'react'
|
||||
|
||||
export const SourceEndpoint = ({
|
||||
onConnectionDragStart,
|
||||
source,
|
||||
...props
|
||||
}: BoxProps & {
|
||||
onConnectionDragStart?: () => void
|
||||
source: ConnectingSourceIds
|
||||
}) => {
|
||||
const { setConnectingIds } = useGraph()
|
||||
|
||||
const handleMouseDown = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!onConnectionDragStart) return
|
||||
e.stopPropagation()
|
||||
onConnectionDragStart()
|
||||
setConnectingIds({ source })
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -8,18 +8,18 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Block, Step } from 'models'
|
||||
import { SourceEndpoint } from './SourceEndpoint'
|
||||
import { useGraph } from 'contexts/GraphContext'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { isDefined, isTextBubbleStep } from 'utils'
|
||||
import { isChoiceInput, isDefined, isInputStep, isTextBubbleStep } from 'utils'
|
||||
import { Coordinates } from '@dnd-kit/core/dist/types'
|
||||
import { TextEditor } from './TextEditor/TextEditor'
|
||||
import { StepNodeLabel } from './StepNodeLabel'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { StepNodeContent } from './StepNodeContent'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||
import { StepNodeContextMenu } from './RightClickMenu'
|
||||
import { SettingsPopoverContent } from './SettingsPopoverContent'
|
||||
import { DraggableStep } from 'contexts/DndContext'
|
||||
import { StepNodeContextMenu } from './StepNodeContextMenu'
|
||||
import { SourceEndpoint } from './SourceEndpoint'
|
||||
|
||||
export const StepNode = ({
|
||||
step,
|
||||
@ -38,7 +38,7 @@ export const StepNode = ({
|
||||
) => void
|
||||
}) => {
|
||||
const { setConnectingIds, connectingIds } = useGraph()
|
||||
const { deleteStep, typebot } = useTypebot()
|
||||
const { moveStep, typebot } = useTypebot()
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const [mouseDownEvent, setMouseDownEvent] =
|
||||
useState<{ absolute: Coordinates; relative: Coordinates }>()
|
||||
@ -69,9 +69,6 @@ export const StepNode = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleConnectionDragStart = () =>
|
||||
setConnectingIds({ source: { blockId: step.blockId, stepId: step.id } })
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
if (!onMouseDown) return
|
||||
e.stopPropagation()
|
||||
@ -104,7 +101,7 @@ export const StepNode = ({
|
||||
(event.movementX > 0 || event.movementY > 0)
|
||||
if (isMovingAndIsMouseDown && step.type !== 'start') {
|
||||
onMouseDown(mouseDownEvent, step)
|
||||
deleteStep(step.id)
|
||||
moveStep(step.id)
|
||||
setMouseDownEvent(undefined)
|
||||
}
|
||||
const element = event.currentTarget as HTMLDivElement
|
||||
@ -164,6 +161,7 @@ export const StepNode = ({
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onMouseUp={handleMouseUp}
|
||||
data-testid={`step-${step.id}`}
|
||||
w="full"
|
||||
>
|
||||
{connectedStubPosition === 'left' && (
|
||||
<Box
|
||||
@ -184,19 +182,24 @@ export const StepNode = ({
|
||||
rounded="lg"
|
||||
cursor={'pointer'}
|
||||
bgColor="white"
|
||||
align="flex-start"
|
||||
>
|
||||
<StepIcon type={step.type} />
|
||||
<StepNodeLabel {...step} />
|
||||
{isConnectable && (
|
||||
<StepIcon type={step.type} mt="1" />
|
||||
<StepNodeContent step={step} />
|
||||
{isConnectable && !isChoiceInput(step) && (
|
||||
<SourceEndpoint
|
||||
onConnectionDragStart={handleConnectionDragStart}
|
||||
source={{
|
||||
blockId: step.blockId,
|
||||
stepId: step.id,
|
||||
}}
|
||||
pos="absolute"
|
||||
right="20px"
|
||||
right="15px"
|
||||
top="19px"
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{isDefined(connectedStubPosition) && (
|
||||
{isDefined(connectedStubPosition) && !isChoiceInput(step) && (
|
||||
<Box
|
||||
h="2px"
|
||||
pos="absolute"
|
||||
@ -209,7 +212,7 @@ export const StepNode = ({
|
||||
)}
|
||||
</Flex>
|
||||
</PopoverTrigger>
|
||||
<SettingsPopoverContent step={step} />
|
||||
{isInputStep(step) && <SettingsPopoverContent step={step} />}
|
||||
</Popover>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
@ -1,19 +1,24 @@
|
||||
import { Flex, Text } from '@chakra-ui/react'
|
||||
import { Step, StartStep, BubbleStepType, InputStepType } from 'models'
|
||||
import { ChoiceItemsList } from './ChoiceInputStepNode/ChoiceItemsList'
|
||||
|
||||
export const StepNodeLabel = (props: Step | StartStep) => {
|
||||
switch (props.type) {
|
||||
type Props = {
|
||||
step: Step | StartStep
|
||||
isConnectable?: boolean
|
||||
}
|
||||
export const StepNodeContent = ({ step }: Props) => {
|
||||
switch (step.type) {
|
||||
case BubbleStepType.TEXT: {
|
||||
return (
|
||||
<Flex
|
||||
flexDir={'column'}
|
||||
opacity={props.content.html === '' ? '0.5' : '1'}
|
||||
opacity={step.content.html === '' ? '0.5' : '1'}
|
||||
className="slate-html-container"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
props.content.html === ''
|
||||
step.content.html === ''
|
||||
? `<p>Click to edit...</p>`
|
||||
: props.content.html,
|
||||
: step.content.html,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@ -21,47 +26,50 @@ export const StepNodeLabel = (props: Step | StartStep) => {
|
||||
case InputStepType.TEXT: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.NUMBER: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.EMAIL: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your email...'}
|
||||
{step.options?.labels?.placeholder ?? 'Type your email...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.URL: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||
{step.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.DATE: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.from ?? 'Pick a date...'}
|
||||
{step.options?.labels?.from ?? 'Pick a date...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.PHONE: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Your phone number...'}
|
||||
{step.options?.labels?.placeholder ?? 'Your phone number...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.CHOICE: {
|
||||
return <ChoiceItemsList step={step} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>{props.label}</Text>
|
||||
return <Text>{step.label}</Text>
|
||||
}
|
||||
default: {
|
||||
return <Text>No input</Text>
|
@ -1,7 +1,7 @@
|
||||
import { StackProps, HStack } from '@chakra-ui/react'
|
||||
import { StartStep, Step } from 'models'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { StepNodeLabel } from './StepNodeLabel'
|
||||
import { StepNodeContent } from './StepNodeContent'
|
||||
|
||||
export const StepNodeOverlay = ({
|
||||
step,
|
||||
@ -19,7 +19,7 @@ export const StepNodeOverlay = ({
|
||||
{...props}
|
||||
>
|
||||
<StepIcon type={step.type} />
|
||||
<StepNodeLabel {...step} />
|
||||
<StepNodeContent step={step} />
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
@ -2,24 +2,33 @@ import { useEventListener, Stack, Flex, Portal } from '@chakra-ui/react'
|
||||
import { Step, Table } from 'models'
|
||||
import { DraggableStep, useDnd } from 'contexts/DndContext'
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { StepNode, StepNodeOverlay } from './StepNode'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
|
||||
export const StepsList = ({
|
||||
blockId,
|
||||
steps,
|
||||
showSortPlaceholders,
|
||||
onMouseUp,
|
||||
}: {
|
||||
blockId: string
|
||||
steps: Table<Step>
|
||||
showSortPlaceholders: boolean
|
||||
onMouseUp: (index: number) => void
|
||||
}) => {
|
||||
const { draggedStep, setDraggedStep, draggedStepType } = useDnd()
|
||||
const {
|
||||
draggedStep,
|
||||
setDraggedStep,
|
||||
draggedStepType,
|
||||
mouseOverBlockId,
|
||||
setDraggedStepType,
|
||||
setMouseOverBlockId,
|
||||
} = useDnd()
|
||||
const { createStep } = useTypebot()
|
||||
const [expandedPlaceholderIndex, setExpandedPlaceholderIndex] = useState<
|
||||
number | undefined
|
||||
>()
|
||||
const showSortPlaceholders = useMemo(
|
||||
() => mouseOverBlockId === blockId && (draggedStep || draggedStepType),
|
||||
[mouseOverBlockId, blockId, draggedStep, draggedStepType]
|
||||
)
|
||||
const [position, setPosition] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@ -48,8 +57,16 @@ export const StepsList = ({
|
||||
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (expandedPlaceholderIndex === undefined) return
|
||||
e.stopPropagation()
|
||||
setMouseOverBlockId(undefined)
|
||||
setExpandedPlaceholderIndex(undefined)
|
||||
onMouseUp(expandedPlaceholderIndex)
|
||||
if (draggedStepType) {
|
||||
createStep(blockId, draggedStepType, expandedPlaceholderIndex)
|
||||
setDraggedStepType(undefined)
|
||||
}
|
||||
if (draggedStep) {
|
||||
createStep(blockId, draggedStep, expandedPlaceholderIndex)
|
||||
setDraggedStep(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStepMouseDown = (
|
||||
@ -58,6 +75,7 @@ export const StepsList = ({
|
||||
) => {
|
||||
setPosition(absolute)
|
||||
setRelativeCoordinates(relative)
|
||||
setMouseOverBlockId(blockId)
|
||||
setDraggedStep(step)
|
||||
}
|
||||
|
||||
@ -118,7 +136,6 @@ export const StepsList = ({
|
||||
<Portal>
|
||||
<StepNodeOverlay
|
||||
step={draggedStep}
|
||||
onMouseUp={handleMouseUp}
|
||||
pos="fixed"
|
||||
top="0"
|
||||
left="0"
|
||||
|
Reference in New Issue
Block a user