ERP-node/frontend/components/dataflow/node-editor/sidebar/NodePalette.tsx

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>
);
}