Profile Snapshot
The ProfileSnapshot is an interactive Next.js component that displays a user profile preview (tooltip or card) when the user hovers over or clicks an element. It is built using Tailwind CSS for styling, Framer Motion for smooth animations, and Lucide React for icons.
Live Preview
Dependencies
npm install
framer-motionlucide-reactCode
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Plus } from "lucide-react";
interface AccordionItem {
id: number;
title: string;
content: string;
tag: string;
}
const items: AccordionItem[] = [
{
id: 1,
title: "System Architecture",
tag: "Core",
content:
"Building robust foundations using modular design patterns and scalable infrastructure to ensure long-term reliability.",
},
{
id: 2,
title: "User Experience",
tag: "Design",
content:
"Crafting intuitive interfaces that prioritize human interaction over rigid, robotic patterns and artificial aesthetics.",
},
{
id: 3,
title: "Performance Metrics",
tag: "Engine",
content:
"Optimizing every millisecond of execution time to create a seamless, high-performance environment for the end user.",
},
];
export default function StaggeredAccordion() {
const [openId, setOpenId] = useState<number | null>(1);
const containerVariants = {
open: {
height: "auto",
transition: {
height: { duration: 0.4, ease: [0.04, 0.62, 0.23, 0.98] },
staggerChildren: 0.1,
delayChildren: 0.1,
},
},
closed: {
height: 0,
transition: {
height: { duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] },
staggerChildren: 0.05,
staggerDirection: -1,
},
},
};
const itemVariants = {
open: { x: 0, opacity: 1, transition: { duration: 0.3 } },
closed: { x: -20, opacity: 0, transition: { duration: 0.2 } },
};
return (
<div className="w-full max-w-2xl mx-auto space-y-4 p-6">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div
key={item.id}
className="group overflow-hidden rounded-2xl border border-stone-800 bg-stone-950 shadow-sm"
>
<button
onClick={() => setOpenId(isOpen ? null : item.id)}
className="flex w-full items-center justify-between p-6 text-left transition-colors hover:bg-stone-900"
>
<div className="flex items-center gap-4">
<span className="text-xs font-mono text-stone-500 uppercase tracking-tighter">
0{item.id}
</span>
<h3
className={`text-xl font-medium tracking-tight ${isOpen ? "text-stone-100" : "text-stone-400"}`}
>
{item.title}
</h3>
</div>
<div className="flex items-center gap-4">
<span className="hidden sm:inline-block text-[10px] px-2 py-1 rounded border border-stone-800 text-stone-500 uppercase font-semibold">
{item.tag}
</span>
<motion.div
animate={{ rotate: isOpen ? 45 : 0 }}
className={`p-1 rounded-full ${isOpen ? "bg-orange-900/20 text-orange-500" : "text-stone-600"}`}
>
<Plus size={20} />
</motion.div>
</div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
variants={containerVariants}
initial="closed"
animate="open"
exit="closed"
className="bg-stone-900/30"
>
<div className="px-16 pb-8">
<motion.p
variants={itemVariants}
className="text-stone-400 leading-relaxed max-w-md"
>
{item.content}
</motion.p>
<motion.div
variants={itemVariants}
className="mt-6 flex gap-4"
>
<button className="text-xs font-bold uppercase tracking-widest text-orange-500 hover:text-orange-400 transition-colors border-b border-orange-900/50 pb-1">
View Specs
</button>
<button className="text-xs font-bold uppercase tracking-widest text-stone-500 hover:text-stone-300 transition-colors border-b border-stone-800 pb-1">
Documentation
</button>
</motion.div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | undefined | The elements or components (such as an avatar image) to be wrapped by the profile snapshot trigger. |
| author | string | "Mritunjay" | The username or name of the author displayed within the profile snapshot card. |