Docs
Swipe Cards
Swipe Cards are interactive elements that users can swipe cards to browse or take actions.
Installation
Install dependencies
npm install framer-motion lucide-react
Run the following command
It will create a new file swipe-cards.tsx
inside the components/mage-ui/card
directory.
mkdir -p components/mage-ui/card && touch components/mage-ui/card/swipe-cards.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { Dispatch, SetStateAction, useState } from "react";
import { motion, useMotionValue, useTransform } from "framer-motion";
const SwipeCards = () => {
const [cards, setCards] = useState<Card[]>(cardData);
return (
<div
className="grid h-screen w-full place-items-center bg-neutral-100"
style={{
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke-width='2' stroke='%23d4d4d4'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
}}
>
<div className="relative h-96 w-72">
{cards.map((card) => (
<Card key={card.id} cards={cards} setCards={setCards} {...card} />
))}
</div>
</div>
);
};
const Card = ({
id,
url,
setCards,
cards,
}: {
id: number;
url: string;
setCards: Dispatch<SetStateAction<Card[]>>;
cards: Card[];
}) => {
const x = useMotionValue(0);
const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
const isFront = id === cards[cards.length - 1].id;
const rotate = useTransform(() => {
const offset = isFront ? 0 : id % 2 ? 6 : -6;
return `${rotateRaw.get() + offset}deg`;
});
const handleDragEnd = () => {
if (Math.abs(x.get()) > 100) {
setCards((pv) => pv.filter((v) => v.id !== id));
}
};
return (
<motion.div
className="absolute top-0 left-0 h-full w-full"
style={{
zIndex: isFront ? cards.length : id,
}}
>
<motion.img
src={url}
alt="Swipe card"
className="h-full w-full origin-bottom rounded-lg bg-white object-cover hover:cursor-grab active:cursor-grabbing"
style={{
x,
opacity,
rotate,
boxShadow: isFront
? "0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5)"
: "0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3)",
}}
animate={{
scale: isFront ? 1 : 0.98,
}}
drag={isFront ? "x" : false}
dragConstraints={{
left: 0,
right: 0,
}}
onDragEnd={handleDragEnd}
whileDrag={{ cursor: "grabbing" }}
dragElastic={0.7}
transition={{
type: "spring",
stiffness: 300,
damping: 20
}}
/>
</motion.div>
);
};
export default SwipeCards;
type Card = {
id: number;
url: string;
};
const cardData: Card[] = [
{
id: 1,
url: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?q=80&w=2370&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 2,
url: "https://images.unsplash.com/photo-1512374382149-233c42b6a83b?q=80&w=2235&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 3,
url: "https://images.unsplash.com/photo-1539185441755-769473a23570?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 4,
url: "https://images.unsplash.com/photo-1549298916-b41d501d3772?q=80&w=2224&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 5,
url: "https://images.unsplash.com/photo-1516478177764-9fe5bd7e9717?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 6,
url: "https://images.unsplash.com/photo-1570464197285-9949814674a7?q=80&w=2273&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 7,
url: "https://images.unsplash.com/photo-1578608712688-36b5be8823dc?q=80&w=2187&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 8,
url: "https://images.unsplash.com/photo-1505784045224-1247b2b29cf3?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
];