feat: [US-020] - Create custom dialogue node component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda 2026-01-21 12:47:35 -03:00
parent 037c67849a
commit e31a0b7bae
2 changed files with 90 additions and 1 deletions

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { useCallback } from 'react' import { useCallback, useMemo } from 'react'
import ReactFlow, { import ReactFlow, {
Background, Background,
BackgroundVariant, BackgroundVariant,
@ -11,9 +11,11 @@ import ReactFlow, {
Connection, Connection,
Node, Node,
Edge, Edge,
NodeTypes,
} from 'reactflow' } from 'reactflow'
import 'reactflow/dist/style.css' import 'reactflow/dist/style.css'
import Toolbar from '@/components/editor/Toolbar' import Toolbar from '@/components/editor/Toolbar'
import DialogueNode from '@/components/editor/nodes/DialogueNode'
import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart' import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart'
type FlowchartEditorProps = { type FlowchartEditorProps = {
@ -46,6 +48,14 @@ function toReactFlowEdges(edges: FlowchartEdge[]): Edge[] {
export default function FlowchartEditor({ export default function FlowchartEditor({
initialData, initialData,
}: FlowchartEditorProps) { }: FlowchartEditorProps) {
// Define custom node types - memoized to prevent re-renders
const nodeTypes: NodeTypes = useMemo(
() => ({
dialogue: DialogueNode,
}),
[]
)
const [nodes, setNodes, onNodesChange] = useNodesState( const [nodes, setNodes, onNodesChange] = useNodesState(
toReactFlowNodes(initialData.nodes) toReactFlowNodes(initialData.nodes)
) )
@ -97,6 +107,7 @@ export default function FlowchartEditor({
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}

View File

@ -0,0 +1,78 @@
'use client'
import { useCallback, ChangeEvent } from 'react'
import { Handle, Position, NodeProps, useReactFlow } from 'reactflow'
type DialogueNodeData = {
speaker?: string
text: string
}
export default function DialogueNode({ id, data }: NodeProps<DialogueNodeData>) {
const { setNodes } = useReactFlow()
const updateNodeData = useCallback(
(field: keyof DialogueNodeData, value: string) => {
setNodes((nodes) =>
nodes.map((node) =>
node.id === id
? { ...node, data: { ...node.data, [field]: value } }
: node
)
)
},
[id, setNodes]
)
const handleSpeakerChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
updateNodeData('speaker', e.target.value)
},
[updateNodeData]
)
const handleTextChange = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
updateNodeData('text', e.target.value)
},
[updateNodeData]
)
return (
<div className="min-w-[200px] rounded-lg border-2 border-blue-500 bg-blue-50 p-3 shadow-md dark:border-blue-400 dark:bg-blue-950">
<Handle
type="target"
position={Position.Top}
id="input"
className="!h-3 !w-3 !border-2 !border-blue-500 !bg-white dark:!bg-zinc-800"
/>
<div className="mb-2 text-xs font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300">
Dialogue
</div>
<input
type="text"
value={data.speaker || ''}
onChange={handleSpeakerChange}
placeholder="Speaker"
className="mb-2 w-full rounded border border-blue-300 bg-white px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-blue-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400"
/>
<textarea
value={data.text || ''}
onChange={handleTextChange}
placeholder="Dialogue text..."
rows={3}
className="w-full resize-none rounded border border-blue-300 bg-white px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-blue-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400"
/>
<Handle
type="source"
position={Position.Bottom}
id="output"
className="!h-3 !w-3 !border-2 !border-blue-500 !bg-white dark:!bg-zinc-800"
/>
</div>
)
}