// root/src/app/page.tsx
import SwipeFeed from "@/components/SwiperFeed";
export default function Home() {
return (
<main className="h-screen w-screen overflow-hidden bg-black">
<SwipeFeed />
</main>
);
}
"use client";
// root/src/components/SwiperFeed.tsx
import { useEffect, useRef, useState } from "react";
import { sampleContentData } from "@/lib/sample-data";
import SwipeCard from "./SwipeCard";
export default function SwipeFeed() {
const [current, setCurrent] = useState(0);
const [deltaY, setDeltaY] = useState(0);
const [clientHeight, setClientHeight] = useState(0);
const touchStartY = useRef<number | null>(null);
const [dragging, setDragging] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
setClientHeight(window.innerHeight);
}
}, []);
const handleSwipe = (direction: "up" | "down") => {
setCurrent((prev) => {
if (direction === "up" && prev < sampleContentData.length - 1) return prev + 1;
if (direction === "down" && prev > 0) return prev - 1;
return prev;
});
};
const handleTouchStart = (e: React.TouchEvent) => {
touchStartY.current = e.touches[0].clientY;
setDragging(true);
};
const handleTouchMove = (e: React.TouchEvent) => {
if (touchStartY.current === null) return;
const currentY = e.touches[0].clientY;
setDeltaY((currentY - touchStartY.current) * 5.5);
};
const handleTouchEnd = () => {
setDragging(false);
const swipeThreshold = 100;
if (deltaY < -swipeThreshold) handleSwipe("up");
else if (deltaY > swipeThreshold) handleSwipe("down");
setDeltaY(0);
touchStartY.current = null;
};
const renderCard = (index: number, offset: number) => {
if (index < 0 || index >= sampleContentData.length || clientHeight === 0) return null;
let translate = offset * clientHeight;
if (dragging && offset === 0) translate += deltaY;
if (dragging && offset !== 0) translate += deltaY;
console.log({ translate });
return (
<SwipeCard
key={index}
data={sampleContentData[index]}
isActive={true}
index={index}
style={{
transform: `translateY(${translate}px)`,
zIndex: 10 - Math.abs(offset),
}}
/>
);
};
return (
<div
className="h-full w-full relative overflow-hidden touch-none"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{renderCard(current - 1, -1)}
{renderCard(current, 0)}
{renderCard(current + 1, 1)}
</div>
);
}
// root/src/components/SwiperCard.tsx
interface Props {
data: {
title: string;
description: string;
image: string;
};
style?: React.CSSProperties;
isActive: boolean;
index: number;
}
export default function SwipeCard({ data, style }: Props) {
return (
<div
className="absolute top-0 left-0 w-full h-full bg-cover bg-no-repeat bg-center text-white p-4 transition-transform duration-300 ease-in-out"
style={{
backgroundImage: `url(${data.image})`,
...style,
}}
>
<div className="bg-black bg-opacity-40 p-4 rounded-xl mt-10 max-w-lg mx-auto">
<h2 className="text-2xl font-bold">{data.title}</h2>
<p className="mt-2 text-lg">{data.description}</p>
</div>
</div>
);
}
// root/src/lib/sample-data.ts
export const sampleContentData = [
{
title: "Welcome to the App",
description: "Swipe up to learn more.",
image: "/images/slide1.png", // change the image path
},
{
title: "Stay Informed",
description: "Quick, fun, and useful content.",
image: "/images/slide2.png", // change the image path
},
{
title: "Keep Swiping",
description: "Discover more as you go.",
image: "/images/slide3.png", // change the image path
},
];
Place the image in public/images
and change the image path in sample-data.ts
.
Run: npm run dev
Top comments (0)