111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* 노드 팔레트 사이드바
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
import { NODE_CATEGORIES, getNodesByCategory } from "./nodePaletteConfig";
|
|
import type { NodePaletteItem } from "@/types/node-editor";
|
|
|
|
export function NodePalette() {
|
|
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
|
|
new Set(), // 기본적으로 모든 아코디언 닫힘
|
|
);
|
|
|
|
const toggleCategory = (categoryId: string) => {
|
|
setExpandedCategories((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(categoryId)) {
|
|
next.delete(categoryId);
|
|
} else {
|
|
next.add(categoryId);
|
|
}
|
|
return next;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="flex h-full flex-col bg-white">
|
|
{/* 헤더 */}
|
|
<div className="border-b bg-gray-50 p-4">
|
|
<h3 className="text-sm font-semibold text-gray-900">노드 라이브러리</h3>
|
|
<p className="mt-1 text-xs text-gray-500">캔버스로 드래그하여 추가</p>
|
|
</div>
|
|
|
|
{/* 노드 목록 */}
|
|
<div className="flex-1 overflow-y-auto p-2">
|
|
{NODE_CATEGORIES.map((category) => {
|
|
const isExpanded = expandedCategories.has(category.id);
|
|
const nodes = getNodesByCategory(category.id);
|
|
|
|
return (
|
|
<div key={category.id} className="mb-2">
|
|
{/* 카테고리 헤더 */}
|
|
<button
|
|
onClick={() => toggleCategory(category.id)}
|
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm font-medium text-gray-700 hover:bg-gray-100"
|
|
>
|
|
{isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
|
<span>{category.label}</span>
|
|
<span className="ml-auto text-xs text-gray-400">{nodes.length}</span>
|
|
</button>
|
|
|
|
{/* 노드 아이템들 */}
|
|
{isExpanded && (
|
|
<div className="mt-1 ml-2 space-y-1">
|
|
{nodes.map((node) => (
|
|
<NodePaletteItemComponent key={node.type} node={node} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* 푸터 도움말 */}
|
|
<div className="border-t bg-gray-50 p-3">
|
|
<p className="text-xs text-gray-500">💡 노드를 드래그하여 캔버스에 배치하고 연결선으로 연결하세요</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 노드 팔레트 아이템 컴포넌트
|
|
*/
|
|
function NodePaletteItemComponent({ node }: { node: NodePaletteItem }) {
|
|
const onDragStart = (event: React.DragEvent) => {
|
|
event.dataTransfer.setData("application/reactflow", node.type);
|
|
event.dataTransfer.effectAllowed = "move";
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="group cursor-move rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-all hover:border-gray-300 hover:shadow-md"
|
|
draggable
|
|
onDragStart={onDragStart}
|
|
title={node.description}
|
|
>
|
|
<div className="flex items-start gap-2">
|
|
{/* 색상 인디케이터 (좌측) */}
|
|
<div className="h-8 w-1 flex-shrink-0 rounded" style={{ backgroundColor: node.color }} />
|
|
|
|
{/* 라벨 및 설명 */}
|
|
<div className="min-w-0 flex-1">
|
|
<div className="text-sm font-medium text-gray-900">{node.label}</div>
|
|
<div className="mt-0.5 truncate text-xs text-gray-500">{node.description}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 하단 색상 인디케이터 (hover 시) */}
|
|
<div
|
|
className="mt-2 h-1 w-full rounded-full opacity-0 transition-opacity group-hover:opacity-100"
|
|
style={{ backgroundColor: node.color }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|