Card Stack
Preview
This is truly amazing.
Manu Arora
Founder, Algochurn
I'm going to Mars.
Elon Musk
Founder, SpaceX
I'm building a clock.
Jeff Bezos
Founder, Amazon
Code
"use client";
import { useState, type FC } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from 'lucide-react';
interface CardStackProps {
items: {
id: number;
name: string;
designation: string;
content: React.ReactNode;
}[];
className?: string;
offset?: number;
scaleFactor?: number;
}
const CardStack: FC<CardStackProps> = ({
items,
className,
offset = 10,
scaleFactor = 0.06,
}) => {
const [cards, setCards] = useState(items);
const cycleCards = (direction: "next" | "prev") => {
setCards((prevCards) => {
const newCards = [...prevCards];
if (direction === "next") {
const first = newCards.shift()!;
newCards.push(first);
} else {
const last = newCards.pop()!;
newCards.unshift(last);
}
return newCards;
});
};
return (
<div className={cn("relative h-80 w-full max-w-sm", className)}>
<AnimatePresence>
{cards.map((card, index) => {
const isTop = index === cards.length - 1;
return (
<motion.div
key={card.id}
className="absolute bg-black/50 border border-white/10 backdrop-blur-md w-full h-full rounded-2xl p-6 shadow-xl flex flex-col justify-between"
style={{
transformOrigin: "top center",
}}
initial={{
scale: 1 - index * scaleFactor,
top: index * -offset,
opacity: isTop ? 1 : 0.5,
}}
animate={{
scale: 1 - index * scaleFactor,
top: index * -offset,
opacity: isTop ? 1 : 0.5,
}}
exit={{
top: -offset * 2,
opacity: 0,
}}
transition={{
type: "spring",
stiffness: 300,
damping: 30,
}}
>
<div className="font-normal text-neutral-200">
{card.content}
</div>
<div>
<p className="text-white font-medium">{card.name}</p>
<p className="text-neutral-400 font-normal">
{card.designation}
</p>
</div>
</motion.div>
);
})}
</AnimatePresence>
<div className="absolute bottom-[-60px] left-1/2 -translate-x-1/2 flex items-center gap-4">
<Button onClick={() => cycleCards("prev")} variant="outline" size="icon" className="rounded-full bg-black/50 hover:bg-white/10">
<ChevronLeft />
</Button>
<Button onClick={() => cycleCards("next")} variant="outline" size="icon" className="rounded-full bg-black/50 hover:bg-white/10">
<ChevronRight />
</Button>
</div>
</div>
);
};
export default CardStack;