Initial commit - Sitemap Builder app
This commit is contained in:
149
src/components/flow/CustomNode.tsx
Normal file
149
src/components/flow/CustomNode.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useMemo } from "react";
|
||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export interface PageNodeData {
|
||||
title: string;
|
||||
status: "draft" | "ready" | "review";
|
||||
content?: string;
|
||||
notes?: string;
|
||||
hasAttachments?: boolean;
|
||||
hasLinks?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const statusConfig = {
|
||||
draft: {
|
||||
label: "Szkic",
|
||||
className: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
|
||||
},
|
||||
ready: {
|
||||
label: "Gotowe",
|
||||
className: "bg-green-500/20 text-green-400 border-green-500/30",
|
||||
},
|
||||
review: {
|
||||
label: "Do poprawki",
|
||||
className: "bg-red-500/20 text-red-400 border-red-500/30",
|
||||
},
|
||||
};
|
||||
|
||||
// Different styles for node types
|
||||
const nodeTypeConfig = {
|
||||
page: {
|
||||
bgClass: "bg-slate-800/90",
|
||||
borderClass: "border-slate-600",
|
||||
selectedBorderClass: "border-blue-500",
|
||||
accentColor: "blue",
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Strona",
|
||||
},
|
||||
note: {
|
||||
bgClass: "bg-amber-900/40",
|
||||
borderClass: "border-amber-600/50",
|
||||
selectedBorderClass: "border-amber-400",
|
||||
accentColor: "amber",
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Notatka",
|
||||
},
|
||||
section: {
|
||||
bgClass: "bg-purple-900/40",
|
||||
borderClass: "border-purple-600/50",
|
||||
selectedBorderClass: "border-purple-400",
|
||||
accentColor: "purple",
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
),
|
||||
label: "Sekcja",
|
||||
},
|
||||
};
|
||||
|
||||
function CustomNode({ data, selected, type }: NodeProps) {
|
||||
const nodeData = data as PageNodeData;
|
||||
const status = statusConfig[nodeData.status] || statusConfig.draft;
|
||||
const nodeType = nodeTypeConfig[type as keyof typeof nodeTypeConfig] || nodeTypeConfig.page;
|
||||
|
||||
const hasContent = useMemo(() => {
|
||||
return nodeData.hasAttachments || nodeData.hasLinks;
|
||||
}, [nodeData.hasAttachments, nodeData.hasLinks]);
|
||||
|
||||
const handleColorClass = nodeType.accentColor === "amber"
|
||||
? "!bg-amber-500"
|
||||
: nodeType.accentColor === "purple"
|
||||
? "!bg-purple-500"
|
||||
: "!bg-blue-500";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
${nodeType.bgClass} backdrop-blur-sm border-2 rounded-xl shadow-xl
|
||||
min-w-[180px] max-w-[240px] transition-all duration-200
|
||||
${selected ? `${nodeType.selectedBorderClass} shadow-${nodeType.accentColor}-500/25` : `${nodeType.borderClass} hover:border-opacity-80`}
|
||||
`}
|
||||
>
|
||||
{/* Top Handle */}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
className={`!w-3 !h-3 ${handleColorClass} !border-2 !border-slate-800`}
|
||||
/>
|
||||
|
||||
{/* Type indicator */}
|
||||
<div className={`px-3 py-1.5 border-b border-white/10 flex items-center gap-2 text-${nodeType.accentColor}-400`}>
|
||||
{nodeType.icon}
|
||||
<span className="text-xs font-medium opacity-70">{nodeType.label}</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<h3 className="font-semibold text-white text-sm leading-tight line-clamp-2">
|
||||
{nodeData.title || "Nowa strona"}
|
||||
</h3>
|
||||
{hasContent && (
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className={`w-4 h-4 text-${nodeType.accentColor}-400`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Badge variant="outline" className={`text-xs ${status.className}`}>
|
||||
{status.label}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Bottom Handle */}
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
className={`!w-3 !h-3 ${handleColorClass} !border-2 !border-slate-800`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(CustomNode);
|
||||
|
||||
Reference in New Issue
Block a user