feat(flow): ✨ Edge menu on click
This commit is contained in:
@ -7,6 +7,9 @@ import {
|
|||||||
getSourceEndpointId,
|
getSourceEndpointId,
|
||||||
} from 'services/graph'
|
} from 'services/graph'
|
||||||
import { Edge as EdgeProps } from 'models'
|
import { Edge as EdgeProps } from 'models'
|
||||||
|
import { Portal, useDisclosure } from '@chakra-ui/react'
|
||||||
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
|
import { EdgeMenu } from './EdgeMenu'
|
||||||
|
|
||||||
export type AnchorsPositionProps = {
|
export type AnchorsPositionProps = {
|
||||||
sourcePosition: Coordinates
|
sourcePosition: Coordinates
|
||||||
@ -16,6 +19,7 @@ export type AnchorsPositionProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
||||||
|
const { deleteEdge } = useTypebot()
|
||||||
const {
|
const {
|
||||||
previewingEdge,
|
previewingEdge,
|
||||||
sourceEndpoints,
|
sourceEndpoints,
|
||||||
@ -23,7 +27,11 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
|||||||
blocksCoordinates,
|
blocksCoordinates,
|
||||||
graphOffsetY,
|
graphOffsetY,
|
||||||
} = useGraph()
|
} = useGraph()
|
||||||
const isPreviewing = previewingEdge?.id === edge.id
|
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
|
const [edgeMenuPosition, setEdgeMenuPosition] = useState({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
const isPreviewing = isMouseOver || previewingEdge?.id === edge.id
|
||||||
|
|
||||||
const sourceBlockCoordinates =
|
const sourceBlockCoordinates =
|
||||||
blocksCoordinates && blocksCoordinates[edge.from.blockId]
|
blocksCoordinates && blocksCoordinates[edge.from.blockId]
|
||||||
@ -74,14 +82,47 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => {
|
|||||||
sourceTop,
|
sourceTop,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const handleMouseEnter = () => setIsMouseOver(true)
|
||||||
|
|
||||||
|
const handleMouseLeave = () => setIsMouseOver(false)
|
||||||
|
|
||||||
|
const handleEdgeClick = (e: React.MouseEvent) => {
|
||||||
|
setEdgeMenuPosition({ x: e.clientX, y: e.clientY })
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteEdge = () => deleteEdge(edge.id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<path
|
<>
|
||||||
data-testid="edge"
|
<path
|
||||||
d={path}
|
data-testid="clickable-edge"
|
||||||
stroke={isPreviewing ? '#1a5fff' : '#718096'}
|
d={path}
|
||||||
strokeWidth="2px"
|
strokeWidth="12px"
|
||||||
markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
|
stroke="white"
|
||||||
fill="none"
|
fill="none"
|
||||||
/>
|
pointerEvents="stroke"
|
||||||
|
style={{ cursor: 'pointer', visibility: 'hidden' }}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onClick={handleEdgeClick}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
data-testid="edge"
|
||||||
|
d={path}
|
||||||
|
stroke={isPreviewing ? '#1a5fff' : '#718096'}
|
||||||
|
strokeWidth="2px"
|
||||||
|
markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<Portal>
|
||||||
|
<EdgeMenu
|
||||||
|
isOpen={isOpen}
|
||||||
|
position={edgeMenuPosition}
|
||||||
|
onDeleteEdge={handleDeleteEdge}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
37
apps/builder/components/shared/Graph/Edges/EdgeMenu.tsx
Normal file
37
apps/builder/components/shared/Graph/Edges/EdgeMenu.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react'
|
||||||
|
import { TrashIcon } from 'assets/icons'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean
|
||||||
|
position: { x: number; y: number }
|
||||||
|
onDeleteEdge: () => void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EdgeMenu = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
position,
|
||||||
|
onDeleteEdge,
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Menu isOpen={isOpen} gutter={0} onClose={onClose} isLazy>
|
||||||
|
<MenuButton
|
||||||
|
aria-hidden={true}
|
||||||
|
w={1}
|
||||||
|
h={1}
|
||||||
|
pos="absolute"
|
||||||
|
style={{
|
||||||
|
left: position.x,
|
||||||
|
top: position.y,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem icon={<TrashIcon />} onClick={onDeleteEdge}>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
@ -50,6 +50,11 @@ test.describe.parallel('Editor', () => {
|
|||||||
)
|
)
|
||||||
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
||||||
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
||||||
|
|
||||||
|
await page.click('[data-testid="clickable-edge"] >> nth=0', { force: true })
|
||||||
|
await page.click('text=Delete')
|
||||||
|
const total = await page.locator('[data-testid="edge"]').count()
|
||||||
|
expect(total).toBe(1)
|
||||||
})
|
})
|
||||||
test('Drag and drop steps and items should work', async ({ page }) => {
|
test('Drag and drop steps and items should work', async ({ page }) => {
|
||||||
const typebotId = generate()
|
const typebotId = generate()
|
||||||
|
Reference in New Issue
Block a user