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
avataar

Dependencies

npm installframer-motionlucide-react

Code

"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

PropTypeDefaultDescription
childrenReact.ReactNodeundefinedThe elements or components (such as an avatar image) to be wrapped by the profile snapshot trigger.
authorstring"Mritunjay"The username or name of the author displayed within the profile snapshot card.