feat: [US-022] - Create custom choice 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:52:47 -03:00
parent 77c85deb1d
commit 91b627907b
2 changed files with 100 additions and 0 deletions

View File

@ -19,6 +19,7 @@ import { nanoid } from 'nanoid'
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 DialogueNode from '@/components/editor/nodes/DialogueNode'
import ChoiceNode from '@/components/editor/nodes/ChoiceNode'
import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart' import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart'
type FlowchartEditorProps = { type FlowchartEditorProps = {
@ -54,6 +55,7 @@ function FlowchartEditorInner({ initialData }: FlowchartEditorProps) {
const nodeTypes: NodeTypes = useMemo( const nodeTypes: NodeTypes = useMemo(
() => ({ () => ({
dialogue: DialogueNode, dialogue: DialogueNode,
choice: ChoiceNode,
}), }),
[] []
) )

View File

@ -0,0 +1,98 @@
'use client'
import { useCallback, ChangeEvent } from 'react'
import { Handle, Position, NodeProps, useReactFlow } from 'reactflow'
type ChoiceOption = {
id: string
label: string
}
type ChoiceNodeData = {
prompt: string
options: ChoiceOption[]
}
export default function ChoiceNode({ id, data }: NodeProps<ChoiceNodeData>) {
const { setNodes } = useReactFlow()
const updatePrompt = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setNodes((nodes) =>
nodes.map((node) =>
node.id === id
? { ...node, data: { ...node.data, prompt: e.target.value } }
: node
)
)
},
[id, setNodes]
)
const updateOptionLabel = useCallback(
(optionId: string, label: string) => {
setNodes((nodes) =>
nodes.map((node) =>
node.id === id
? {
...node,
data: {
...node.data,
options: node.data.options.map((opt: ChoiceOption) =>
opt.id === optionId ? { ...opt, label } : opt
),
},
}
: node
)
)
},
[id, setNodes]
)
return (
<div className="min-w-[220px] rounded-lg border-2 border-green-500 bg-green-50 p-3 shadow-md dark:border-green-400 dark:bg-green-950">
<Handle
type="target"
position={Position.Top}
id="input"
className="!h-3 !w-3 !border-2 !border-green-500 !bg-white dark:!bg-zinc-800"
/>
<div className="mb-2 text-xs font-semibold uppercase tracking-wide text-green-700 dark:text-green-300">
Choice
</div>
<input
type="text"
value={data.prompt || ''}
onChange={updatePrompt}
placeholder="What do you choose?"
className="mb-3 w-full rounded border border-green-300 bg-white px-2 py-1 text-sm focus:border-green-500 focus:outline-none focus:ring-1 focus:ring-green-500 dark:border-green-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400"
/>
<div className="space-y-2">
{data.options.map((option, index) => (
<div key={option.id} className="relative">
<input
type="text"
value={option.label}
onChange={(e) => updateOptionLabel(option.id, e.target.value)}
placeholder={`Option ${index + 1}`}
className="w-full rounded border border-green-300 bg-white px-2 py-1 pr-3 text-sm focus:border-green-500 focus:outline-none focus:ring-1 focus:ring-green-500 dark:border-green-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400"
/>
<Handle
type="source"
position={Position.Bottom}
id={`option-${index}`}
className="!h-3 !w-3 !border-2 !border-green-500 !bg-white dark:!bg-zinc-800"
style={{
left: `${((index + 1) / (data.options.length + 1)) * 100}%`,
}}
/>
</div>
))}
</div>
</div>
)
}