111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
"use client";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import mermaid from "mermaid";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { Maximize2, Minimize2 } from "lucide-react";
|
|
|
|
export default function Mermaid({ chart }: { chart: string }) {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
const [isRendered, setIsRendered] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: "dark",
|
|
securityLevel: "loose",
|
|
fontFamily: "monospace",
|
|
});
|
|
|
|
// Render the chart once. Use a timeout to ensure DOM is stable.
|
|
const renderChart = async () => {
|
|
try {
|
|
await mermaid.contentLoaded();
|
|
setIsRendered(true);
|
|
} catch (err) {
|
|
console.error("Mermaid render failed:", err);
|
|
}
|
|
};
|
|
|
|
renderChart();
|
|
}, [chart]); // Only re-run if the chart string itself changes
|
|
|
|
return (
|
|
<div className="relative max-w-4xl mx-auto group">
|
|
<motion.div
|
|
layout
|
|
initial={false}
|
|
animate={{
|
|
height: isExpanded ? "auto" : "400px",
|
|
}}
|
|
transition={{ duration: 0.6, ease: [0.23, 1, 0.32, 1] }}
|
|
className={`relative bg-neutral-900/20 rounded-3xl border border-neutral-800/50 overflow-hidden transition-colors duration-500 ${
|
|
!isExpanded
|
|
? "hover:border-neutral-700 cursor-pointer"
|
|
: "cursor-default"
|
|
}`}
|
|
onClick={() => !isExpanded && setIsExpanded(true)}
|
|
>
|
|
{/* Legend */}
|
|
<div className="absolute top-6 right-8 flex flex-col gap-2 z-20 bg-black/40 backdrop-blur-md p-3 rounded-xl border border-white/5 pointer-events-none">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
|
<span className="text-[9px] font-mono text-neutral-400 uppercase tracking-wider">
|
|
Traffic Flow
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 rounded-full bg-green-500" />
|
|
<span className="text-[9px] font-mono text-neutral-400 uppercase tracking-wider">
|
|
Service Node
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chart Area */}
|
|
<div
|
|
className={`p-4 md:p-12 transition-opacity duration-500 ${isRendered ? "opacity-100" : "opacity-0"}`}
|
|
>
|
|
<div className="mermaid flex justify-center">{chart}</div>
|
|
</div>
|
|
|
|
{/* Fade Overlay */}
|
|
<AnimatePresence>
|
|
{!isExpanded && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-[#0a0a0a] via-[#0a0a0a]/90 to-transparent pointer-events-none"
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
</motion.div>
|
|
|
|
{/* Toggle Button */}
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setIsExpanded(!isExpanded);
|
|
}}
|
|
className={`absolute -bottom-5 left-1/2 -translate-x-1/2 z-30 flex items-center gap-2 px-5 py-2.5 rounded-full text-[10px] font-mono uppercase tracking-[0.2em] transition-all border shadow-xl
|
|
${
|
|
isExpanded
|
|
? "bg-neutral-800 border-neutral-700 text-white"
|
|
: "bg-neutral-900 border-neutral-800 text-neutral-500 group-hover:bg-neutral-800 group-hover:border-neutral-600 group-hover:text-white"
|
|
}`}
|
|
>
|
|
{isExpanded ? (
|
|
<>
|
|
<Minimize2 size={12} /> Collapse Logic
|
|
</>
|
|
) : (
|
|
<>
|
|
<Maximize2 size={12} /> Expand Architecture
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|