Skip to Content
ComponentsIcignalWorkflowDesigner

WorkflowDesigner

캔버스 기반 워크플로우 편집기 컴포넌트


개요

WorkflowDesigner는 트리 구조의 워크플로우를 시각적으로 설계할 수 있는 캔버스 기반 편집기입니다. 노드와 커넥터를 자동 레이아웃하며, 팔레트에서 노드를 추가하고 캔버스에서 편집할 수 있습니다.

주요 특징

  • 자동 레이아웃: parentId 기반 트리 구조를 자동으로 배치
  • 팔레트: 좌측 사이드바에서 노드 유형 선택 및 추가
  • 캔버스 조작: 줌(휠), 패닝(스페이스+드래그, 미들 클릭), 전체 화면
  • 분기 노드: 조건 분기(Yes/No 등)와 라벨/색상 지원, 빈 분기 레인에 + 버튼 자동 표시 (childrenLayout: "branch")
  • 종속 노드: 특정 노드 추가 시 자동으로 따라 붙는 종속(attached) 노드 지원
  • 사이드 패널: sidePanel prop으로 우측 패널 렌더링 (전체 화면에서도 표시)
  • 커스텀 렌더링: renderNode로 노드 내부 커스터마이징
  • 읽기 전용: readOnly 모드 지원
  • Controlled/Uncontrolled: 선택 상태, 노드 배열 모두 제어 가능

아키텍처

영역설명
Palette좌측 사이드바. nodeTypes에 정의된 노드 유형을 그룹별로 표시
Header캔버스 상단. title/description 또는 커스텀 header 렌더링
Canvas중앙 캔버스. 노드, 커넥터, + 버튼을 렌더링
Toolbar좌측 하단. 줌, 패닝/선택 모드, 화면 맞춤, 전체 화면 버튼

기본 사용

워크플로우
시작
트리거
실행
액션
흐름
조건
시작
이메일 발송
알림 전송

Node Status

노드는 status 속성으로 시각적 상태를 표현합니다.

워크플로우 구성
액션
완료됨
오류 발생
오류
비활성
잠김

Status 설명

Status설명시각적 표현
default기본 상태기본 테두리
selected선택됨Primary 테두리 + 그림자
completed완료됨녹색 테두리
error오류빨간 테두리 + 그림자
disabled비활성50% 투명도, 클릭 불가
locked잠김70% 투명도, 잠금 아이콘

사용 예시

예시 1: 분기 워크플로우

조건 노드로 Yes/No 분기를 만들 수 있습니다.

워크플로우
시작
트리거
흐름
조건
실행
액션
YesNo
고객 방문
VIP 여부
VIP 혜택 안내
일반 안내

예시 2: onNodesChange를 활용한 편집 모드

onNodesChange를 전달하면 내장 추가/삭제 로직이 활성화됩니다.

import { WorkflowDesigner } from "@vortex/ui-icignal" import type { WorkflowNode, NodeTypeDefinition } from "@vortex/ui-icignal" import { useState } from "react" function WorkflowEditor() { const [nodes, setNodes] = useState<WorkflowNode[]>([ { id: "1", type: "trigger", title: "시작", parentId: null }, ]) const nodeTypes: NodeTypeDefinition[] = [ { type: "trigger", label: "트리거", maxCount: 1, maxChildren: 1 }, { type: "action", label: "액션", maxChildren: 1 }, ] return ( <div style={{ height: 600 }}> <WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} onNodesChange={setNodes} onNodeSelect={(id) => console.log("선택:", id)} /> </div> ) }

예시 3: 커스텀 노드 렌더링

renderNode prop으로 노드 내부를 자유롭게 커스터마이징할 수 있습니다.

<WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} onNodesChange={setNodes} renderNode={({ node, isSelected }) => ( <div className="p-3"> <div className="font-semibold">{node.title}</div> {node.description && ( <div className="text-xs text-muted-foreground mt-1"> {node.description} </div> )} {isSelected && ( <div className="text-xs text-primary mt-2">선택됨</div> )} </div> )} />

예시 4: 커스텀 헤더

header prop으로 캔버스 상단에 커스텀 UI를 배치하거나, title/description으로 간단히 텍스트를 표시합니다.

// 간단한 텍스트 헤더 <WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} title="캠페인 워크플로우" description="고객 여정을 설계하세요" /> // 커스텀 헤더 <WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} header={ <div className="flex items-center justify-between"> <h3>캠페인 A</h3> <button>저장</button> </div> } />

예시 5: 수평 레이아웃

layoutDirection="horizontal"로 왼→오른쪽 방향 트리를 구성합니다.

<WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} layoutDirection="horizontal" onNodesChange={setNodes} />

API Reference

WorkflowDesignerProps

Props

PropTypeDefaultDescription
nodesWorkflowNode[]필수노드 목록
nodeTypesNodeTypeDefinition[]필수노드 유형 정의 (팔레트에 표시)
connectorsWorkflowConnector[]자동 생성커넥터 목록 (생략 시 parentId에서 자동 생성)
selectedNodeIdstring | null-현재 선택된 노드 ID (controlled)
focusedNodeIdstring | nullnull포커스 하이라이트 노드 ID
invalidNodeIdsstring[][]유효성 오류 노드 ID 목록
renderNode(props: WorkflowNodeRenderProps) => ReactNode-커스텀 노드 렌더러
nodeActionsWorkflowNodeAction[]기본(삭제, 설정)노드 hover/선택 시 표시되는 액션 버튼
paletteTitlestring"워크플로우 구성"팔레트 타이틀
titlestring-캔버스 상단 타이틀
descriptionstring-캔버스 상단 설명
headerReactNode-커스텀 헤더 (설정 시 title/description보다 우선)
readOnlybooleanfalse읽기 전용 모드
defaultScalenumber1초기 줌 레벨
minScalenumber0.25최소 줌 레벨
maxScalenumber2최대 줌 레벨
nodeWidthnumber200노드 기본 너비 (px)
nodeGapXnumber60노드 간 수평 간격 (px)
nodeGapYnumber60노드 간 수직 간격 (px)
layoutDirection"vertical" | "horizontal""vertical"레이아웃 방향
generateNodeId() => stringcrypto.randomUUID노드 ID 생성기
sidePanelReactNode-우측 사이드 패널 (전체 화면 시에도 함께 표시됨)
classNamestring-루트 요소 CSS 클래스

Events

EventTypeDescription
onNodeSelect(nodeId: string | null) => void노드 선택 시
onNodeDblClick(nodeId: string) => void노드 더블클릭 시
onNodeAdd(nodeType: string, parentNodeId: string, insertBeforeNodeId?: string) => void노드 추가 시
onNodeDelete(nodeId: string) => void노드 삭제 시
onNodeSettings(nodeId: string) => void노드 설정 버튼 클릭 시
onNodesChange(nodes: WorkflowNode[]) => void내장 추가/삭제 후 노드 배열 변경 시
onChange(nodes: WorkflowNode[], connectors: WorkflowConnector[]) => void데이터 변경 시
onCanvasClick() => void캔버스 빈 영역 클릭 시 (선택 초기화)

WorkflowNode

노드 데이터 구조입니다.

PropertyTypeDefaultDescription
idstring필수노드 고유 식별자
typestring필수노드 유형 (NodeTypeDefinition.type과 매칭)
titlestring필수노드 타이틀
descriptionstring-노드 설명 (요약 정보)
captionstring-우상단 캡션 (날짜, 상태 텍스트 등)
statusWorkflowNodeStatus"default"노드 상태
iconReactNode-노드 아이콘
badgestring-상태 뱃지 텍스트
parentIdstring | null-부모 노드 ID (null = 루트 노드)
branchIndexnumber0부모 노드의 분기 인덱스 (0-based)
maxChildrennumber-이 노드의 최대 자식 수 (개별 오버라이드)
dataRecord<string, unknown>-사용자 정의 데이터
lockedbooleanfalse잠금 상태
invalidbooleanfalse유효성 오류 여부
attachedToIdstring-종속 대상 노드 ID (종속 노드일 때 설정)

NodeTypeDefinition

팔레트에 표시되는 노드 유형 정의입니다.

PropertyTypeDefaultDescription
typestring필수노드 유형 식별자
labelstring필수노드 유형 라벨
iconReactNode-노드 아이콘
groupstring-팔레트 그룹명
allowedAfterstring[]-허용된 부모 노드 유형 (비어있으면 제한 없음)
maxCountnumber-최대 배치 가능 수
maxChildrennumber1기본 최대 자식 수 (1=선형, 2+=분기)
isBranchingbooleanfalse조건 분기 노드 여부
branchLabelsstring[]-분기 라벨 (예: ["Yes", "No"])
branchColorsstring[]-분기 색상 (예: ["#22c55e", "#ef4444"])
childrenLayout"vertical" | "horizontal" | "branch" | string[]"vertical"자식 배치 방향 ("branch": 분기 레인별 배치 + 빈 레인 placeholder)
attachedNodesAttachedNodeDefinition[]-노드 추가 시 자동 생성되는 종속 노드 정의
hiddenbooleanfalsetrue이면 팔레트에서 숨김 (종속 노드 전용)

AttachedNodeDefinition

종속 노드 자동 생성 시 사용되는 정의입니다. NodeTypeDefinition.attachedNodes에서 사용됩니다.

PropertyTypeDescription
typestring종속 노드의 유형 (nodeTypestype과 매칭)
titlestring종속 노드의 기본 타이틀

WorkflowNodeAction

노드 hover/선택 시 상단에 표시되는 액션 버튼입니다.

PropertyTypeDescription
idstring액션 식별자
iconReactNode액션 아이콘
labelstring액션 라벨 (툴팁)
onClick(nodeId: string) => void클릭 핸들러
visible(node: WorkflowNode) => boolean표시 조건 (생략 시 항상 표시)

WorkflowNodeRenderProps

renderNode 콜백에 전달되는 props입니다.

PropertyTypeDescription
nodeWorkflowNode노드 데이터
isSelectedboolean선택 상태
isFocusedboolean포커스 상태
isHighlightedboolean하이라이트 상태

WorkflowConnector

노드 간 연결선 데이터입니다. 생략 시 parentId 관계에서 자동 생성됩니다.

PropertyTypeDescription
idstring커넥터 고유 식별자
sourceIdstring시작 노드 ID
targetIdstring끝 노드 ID
labelstring커넥터 라벨
colorstring커넥터 색상
branchIndexnumber분기 인덱스

기본 사용법

import { WorkflowDesigner } from "@vortex/ui-icignal" import type { WorkflowNode, NodeTypeDefinition } from "@vortex/ui-icignal" import { useState } from "react" const nodeTypes: NodeTypeDefinition[] = [ { type: "trigger", label: "트리거", maxCount: 1, maxChildren: 1 }, { type: "action", label: "액션", maxChildren: 1 }, { type: "condition", label: "조건", maxChildren: 2, isBranching: true, branchLabels: ["Yes", "No"], branchColors: ["#22c55e", "#ef4444"] }, ] function MyWorkflow() { const [nodes, setNodes] = useState<WorkflowNode[]>([ { id: "1", type: "trigger", title: "시작", parentId: null }, ]) const [selected, setSelected] = useState<string | null>(null) return ( <div style={{ height: 600 }}> <WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} selectedNodeId={selected} onNodeSelect={setSelected} onNodesChange={setNodes} onNodeDelete={(id) => console.log("삭제:", id)} onNodeSettings={(id) => console.log("설정:", id)} /> </div> ) }

캔버스 조작

마우스/키보드

조작동작
마우스 휠줌 인/아웃 (마우스 위치 기준)
스페이스 + 드래그캔버스 패닝
미들 클릭 + 드래그캔버스 패닝
노드 클릭노드 선택
노드 더블클릭onNodeDblClick 호출
캔버스 빈 영역 클릭선택 초기화

툴바 버튼

버튼동작
확대 (ZoomIn)줌 인 (+0.15 step)
축소 (ZoomOut)줌 아웃 (-0.15 step)
이동 (Hand)패닝 모드
선택 (MousePointer2)선택 모드 (기본)
화면 맞춤 (Focus)모든 노드가 보이도록 줌/패닝 조정
전체 화면 (Maximize/Minimize)전체 화면 토글 (ESC 키로 해제). 전체 화면 모드에서는 Minimize 아이콘으로 전환

노드 추가/삭제 플로우

추가

  1. 커넥터 중간 또는 리프 노드 하단의 + 버튼 클릭
  2. 팔레트에서 추가 가능한 노드 유형이 하이라이트됨 (allowedAfter 기반 필터링)
  3. 노드 유형 클릭 → onNodesChange로 새 노드가 포함된 배열 전달
  4. onNodeAdd 콜백도 함께 호출 (기존 호환)

삭제

  1. 노드 hover/선택 시 상단 삭제 버튼 클릭
  2. onNodesChange가 있으면 내장 로직으로 해당 노드 + 모든 하위 노드를 일괄 삭제
    • 종속 노드(attachedToId)도 함께 삭제됨
    • 종속 노드는 단독 삭제 불가 (소유 노드 삭제 시에만 제거)
  3. onNodeDelete 콜백도 함께 호출

전체 화면 (Fullscreen)

전체 화면 모드는 createPortal을 사용하여 document.body에 렌더링됩니다.

  • 진입: 툴바의 전체 화면 버튼 클릭
  • 해제: ESC 키 또는 전체 화면 버튼 재클릭
  • z-index: 50 (모달/토스트와 같은 Portal 레벨이며, DOM 순서로 모달이 위에 표시됨)
  • sidePanel: 전체 화면에서도 sidePanel prop이 정상 표시됨

분기 노드 (Branch Nodes)

childrenLayout: "branch"를 설정하면 분기 노드의 각 레인별로 자식이 배치되고, 자식이 없는 빈 레인에는 자동으로 + 버튼(placeholder)이 표시됩니다. 각 + 버튼에는 branchLabelsbranchColors가 함께 적용되어 Yes/No 같은 분기 경로를 한 번에 시각화할 수 있습니다.

정의

const nodeTypes: NodeTypeDefinition[] = [ { type: "condition", label: "조건", maxChildren: 2, isBranching: true, branchLabels: ["Yes", "No"], branchColors: ["#22c55e", "#ef4444"], childrenLayout: "branch", // 핵심: 레인별 배치 + 빈 레인 placeholder }, ]

동작

  • 레인 개수: branchLabels.lengthmaxChildren → 기본 2 순으로 결정됨
  • 빈 레인 placeholder: 자식이 없는 레인마다 라벨/색상이 적용된 + 버튼이 자동 표시됨
  • + 버튼 클릭: 해당 레인의 branchIndex로 새 자식이 추가됨 (자동 위치 이동 없음)
  • 일부만 채워진 경우: 채워진 레인은 기존 분기 커넥터로, 빈 레인에는 placeholder가 표시됨
  • 모든 레인이 채워지면 placeholder는 숨겨짐

vs. isBranching: true 단독

설정레인 공간 예약빈 레인 + 버튼분기 색상/라벨
isBranching: true❌ (우측 + 만)
childrenLayout: "branch"

기존 isBranching: true 설정은 호환성을 위해 유지되며, 새 분기 UX(빈 레인 + 버튼)를 원하면 childrenLayout: "branch"를 함께 지정하세요.


종속 노드 (Attached Nodes)

특정 노드 유형을 추가할 때 자동으로 함께 생성되는 종속 노드를 정의할 수 있습니다.

정의

const nodeTypes: NodeTypeDefinition[] = [ { type: "message", label: "메시지", maxChildren: 1, attachedNodes: [ { type: "schedule", title: "발송 스케줄" }, ], }, { type: "schedule", label: "발송 스케줄", maxChildren: 1, hidden: true, // 팔레트에서 숨김 }, ]

동작

  • “메시지” 노드 추가 시 → “발송 스케줄” 종속 노드가 자동 생성됨
  • 종속 노드는 attachedToId로 소유 노드에 연결됨
  • 종속 노드는 팔레트에서 숨겨지고(hidden: true), 단독 삭제 불가
  • 소유 노드 삭제 시 종속 노드도 함께 삭제됨
  • 종속 노드와 다음 노드 사이의 + 버튼은 숨겨짐

사이드 패널 (sidePanel)

sidePanel prop을 사용하여 워크플로우 디자이너 내부에 우측 패널을 렌더링할 수 있습니다. 전체 화면 모드에서도 Portal 내부에 포함되어 정상 표시됩니다.

<WorkflowDesigner nodes={nodes} nodeTypes={nodeTypes} onNodesChange={setNodes} sidePanel={ selectedNode ? ( <div className="absolute inset-y-0 right-0 w-80 border-l bg-background shadow-lg z-10"> <h3>{selectedNode.title} 설정</h3> {/* 설정 폼 */} </div> ) : undefined } />

접근성

권장 사항

  • ✅ 각 노드에 title을 설정하여 용도를 명확히 전달
  • readOnly 모드에서 + 버튼과 노드 액션 버튼이 자동 숨김
  • ✅ 키보드: 스페이스바로 패닝 모드 전환
  • ❌ 컨테이너에 충분한 높이(min-height: 400px)를 지정하지 않으면 콘텐츠가 잘릴 수 있음

관련 컴포넌트

  • DataTable: 테이블 형태의 데이터 표시
  • Editor: 리치 텍스트 편집기
Last updated on