import {useDraggable} from '@neodrag/react'; import { useReactFlow, type XYPosition } from '@xyflow/react'; import { type ReactNode, useCallback, useRef, useState } from 'react'; // Is used to make sure each subsequent node gets a unique id, so the ReactFlow implementation // can distinguish between different nodes. let id = 0; const getId = () => `dndnode_${id++}`; /** * DraggableNodeProps dictates the type properties of a DraggableNode */ interface DraggableNodeProps { className?: string; children: ReactNode; nodeType: string; onDrop: (nodeType: string, position: XYPosition) => void; } /** * Definition of a node inside the drag and drop toolbar, * these nodes require an onDrop function to be supplied * that dictates how the node is created in the graph. * * @param className * @param children * @param nodeType * @param onDrop * @constructor */ function DraggableNode({className, children, nodeType, onDrop}: DraggableNodeProps) { const draggableRef = useRef(null); const [position, setPosition] = useState({x: 0, y: 0}); // @ts-ignore useDraggable(draggableRef, { position: position, onDrag: ({offsetX, offsetY}) => { // Calculate position relative to the viewport setPosition({ x: offsetX, y: offsetY, }); }, onDragEnd: ({event}) => { setPosition({x: 0, y: 0}); onDrop(nodeType, { x: event.clientX, y: event.clientY, }); }, }); return (
{children}
); } /** * the DndToolbar defines how the drag and drop toolbar component works * and includes the default onDrop behavior through handleNodeDrop * @constructor */ export function DndToolbar() { const {setNodes, screenToFlowPosition} = useReactFlow(); /** * handleNodeDrop implements the default onDrop behavior */ const handleNodeDrop = useCallback( (nodeType: string, screenPosition: XYPosition) => { const flow = document.querySelector('.react-flow'); const flowRect = flow?.getBoundingClientRect(); const isInFlow = flowRect && screenPosition.x >= flowRect.left && screenPosition.x <= flowRect.right && screenPosition.y >= flowRect.top && screenPosition.y <= flowRect.bottom; // Create a new node and add it to the flow if (isInFlow) { const position = screenToFlowPosition(screenPosition); const newNode = () => { switch (nodeType) { case "phase": return { id: getId(), type: nodeType, position, data: {label: `"new"`, number: (-1)}, }; case "start": return { id: getId(), type: nodeType, position, data: {label: `new start node`}, }; case "end": return { id: getId(), type: nodeType, position, data: {label: `new end node`}, }; case "norm": return { id: getId(), type: nodeType, position, data: {label: `new norm node`}, }; default: { return { id: getId(), type: nodeType, position, data: {label: `new default node`}, }; } } } setNodes((nds) => nds.concat(newNode())); } }, [setNodes, screenToFlowPosition], ); return (
You can drag these nodes to the pane to create new nodes.
phase Node norm Node
); }