"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Node as FlowNode, NodeProps, NodeResizer } from "@xyflow/react"; import { GROUP_COLORS, useWorkflowStore } from "@/store/workflowStore"; import { GroupColor, GroupNodeData } from "@/types"; type GroupNodeType = FlowNode; const COLOR_OPTIONS: { color: GroupColor; label: string }[] = [ { color: "neutral", label: "Gray" }, { color: "blue", label: "Blue" }, { color: "green", label: "Green" }, { color: "purple", label: "Purple" }, { color: "orange", label: "Orange" }, { color: "red", label: "Red" }, ]; export function GroupNode({ data, selected }: NodeProps) { const groupId = data.groupId; const group = useWorkflowStore((state) => state.groups[groupId]); const updateGroup = useWorkflowStore((state) => state.updateGroup); const deleteGroup = useWorkflowStore((state) => state.deleteGroup); const toggleGroupLock = useWorkflowStore((state) => state.toggleGroupLock); const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(group?.name || ""); const [showMenu, setShowMenu] = useState(false); const [showColorPicker, setShowColorPicker] = useState(false); const [isHovered, setIsHovered] = useState(false); const inputRef = useRef(null); const menuRef = useRef(null); useEffect(() => { if (!isEditing) setEditName(group?.name || ""); }, [group?.name, isEditing]); useEffect(() => { if (isEditing) { inputRef.current?.focus(); inputRef.current?.select(); } }, [isEditing]); useEffect(() => { if (!showMenu && !showColorPicker) return; const handleClickOutside = (event: MouseEvent) => { if (menuRef.current?.contains(event.target as Node)) return; setShowMenu(false); setShowColorPicker(false); }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [showColorPicker, showMenu]); const handleNameSubmit = useCallback(() => { const nextName = editName.trim(); if (nextName && nextName !== group?.name) { updateGroup(groupId, { name: nextName }); } else { setEditName(group?.name || ""); } setIsEditing(false); }, [editName, group?.name, groupId, updateGroup]); const handleNameKeyDown = useCallback( (event: React.KeyboardEvent) => { if (event.key === "Enter") { handleNameSubmit(); } else if (event.key === "Escape") { setEditName(group?.name || ""); setIsEditing(false); } }, [group?.name, handleNameSubmit] ); if (!group) return null; const bgColor = GROUP_COLORS[group.color]; const showResizeControls = selected || isHovered; return ( <>
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} style={{ backgroundColor: `${bgColor}60`, border: group.isNbpInput ? "3px dashed rgba(255,255,255,0.25)" : `1px solid ${bgColor}`, }} >
{group.locked && ( )} {isEditing ? ( setEditName(event.target.value)} onBlur={handleNameSubmit} onKeyDown={handleNameKeyDown} className="nodrag nopan w-36 border-0 bg-transparent p-0 text-xs font-medium text-white outline-none" /> ) : ( )}
{showMenu && (
{showColorPicker && (
{COLOR_OPTIONS.map(({ color, label }) => (
)}
)}
); }