2
0
Files
bot/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNode.tsx

156 lines
4.8 KiB
TypeScript
Raw Normal View History

2022-01-12 09:10:59 +01:00
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"
2022-01-19 09:44:21 +01:00
shadow="sm"
_hover={{ shadow: 'md' }}
transition="box-shadow 200ms"
borderWidth="1px"
rounded="md"
px="4"
py="2"
borderColor={isOpened ? 'blue.400' : 'gray.300'}
2022-01-12 09:10:59 +01:00
>
<Editable
ref={ref}
defaultValue={item.content ?? 'Click to edit'}
flex="1"
startWithEditView={!isDefined(item.content)}
onSubmit={handleInputSubmit}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
>
2022-01-19 09:44:21 +01:00
<EditablePreview
w="full"
color={item.content !== 'Click to edit' ? 'inherit' : 'gray.500'}
/>
2022-01-12 09:10:59 +01:00
<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"
2022-01-19 09:44:21 +01:00
shadow="md"
colorScheme="blue"
2022-01-12 09:10:59 +01:00
onClick={handlePlusClick}
/>
</Fade>
</Flex>
)}
</ContextMenu>
)
}