portfolio/components/Mermaid.tsx
2026-01-30 12:40:25 +01:00

99 lines
3.6 KiB
TypeScript

"use client";
import { useEffect, useState, useRef } 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 [needsExpansion, setNeedsExpansion] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
mermaid.initialize({
startOnLoad: true,
theme: "dark",
securityLevel: "loose",
fontFamily: "monospace",
});
mermaid.contentLoaded();
if (contentRef.current) {
const height = contentRef.current.scrollHeight;
setNeedsExpansion(height > 400);
}
}, [chart]);
return (
<div className="relative max-w-4xl mx-auto group">
<motion.div
initial={false}
onClick={() => needsExpansion && setIsExpanded(!isExpanded)}
animate={{ height: isExpanded || !needsExpansion ? "auto" : "400px" }}
className={`relative bg-neutral-900/20 rounded-3xl border border-neutral-800/50 p-4 md:p-12 overflow-hidden transition-colors duration-500
${needsExpansion && !isExpanded ? "cursor-pointer hover:border-neutral-700" : "cursor-default"}`}
>
{/* 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">
<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>
<div ref={contentRef} className="mermaid flex justify-center">
{chart}
</div>
{/* The "Fade to Darkness" Overlay (when expansion is needed) */}
<AnimatePresence>
{needsExpansion && !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]/80 to-transparent pointer-events-none"
/>
)}
</AnimatePresence>
</motion.div>
{/* Expand/Collapse Button (when expansion is needed) */}
{needsExpansion && (
<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>
);
}