portfolio/components/ImageCarousel.tsx
2026-01-30 11:22:44 +01:00

110 lines
3.5 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronLeft, ChevronRight } from "lucide-react";
interface GalleryProps {
images: string[];
}
export default function ImageCarousel({ images }: GalleryProps) {
const [[page, direction], setPage] = useState([0, 0]);
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
const imageIndex = Math.abs(page % images.length);
const paginate = useCallback((newDirection: number) => {
setPage([page + newDirection, newDirection]);
}, [page]);
// AUTO-PLAY LOGIC
useEffect(() => {
if (!isAutoPlaying) return;
const interval = setInterval(() => {
paginate(1);
}, 5000); // 5 seconds is the "sweet spot" for technical analysis
return () => clearInterval(interval);
}, [paginate, isAutoPlaying]);
const variants = {
enter: (direction: number) => ({
x: direction > 0 ? 1000 : -1000,
opacity: 0,
}),
center: { zIndex: 1, x: 0, opacity: 1 },
exit: (direction: number) => ({
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0,
}),
};
return (
<div
className="relative aspect-video w-full overflow-hidden rounded-3xl border border-neutral-800 bg-neutral-900"
onMouseEnter={() => setIsAutoPlaying(false)} // Pause on hover
onMouseLeave={() => setIsAutoPlaying(true)} // Resume when mouse leaves
>
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={page}
src={images[imageIndex]}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.2 },
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragStart={() => setIsAutoPlaying(false)} // Kill auto-play on interaction
onDragEnd={(e, { offset }) => {
const swipe = Math.abs(offset.x) > 50;
if (swipe) paginate(offset.x > 0 ? -1 : 1);
}}
className="absolute h-full w-full object-cover cursor-grab active:cursor-grabbing"
/>
</AnimatePresence>
{/* Navigation Arrows */}
<div className="absolute inset-0 z-10 flex items-center justify-between p-4 pointer-events-none">
<button
className="p-2 rounded-full bg-black/50 backdrop-blur-md border border-white/10 text-white pointer-events-auto hover:bg-black/80 transition-all"
onClick={() => {
setIsAutoPlaying(false); // Kill auto-play
paginate(-1);
}}
>
<ChevronLeft size={24} />
</button>
<button
className="p-2 rounded-full bg-black/50 backdrop-blur-md border border-white/10 text-white pointer-events-auto hover:bg-black/80 transition-all"
onClick={() => {
setIsAutoPlaying(false); // Kill auto-play
paginate(1);
}}
>
<ChevronRight size={24} />
</button>
</div>
{/* Progress Bar (Visual Timer) */}
{isAutoPlaying && (
<motion.div
key={imageIndex}
initial={{ width: "0%" }}
animate={{ width: "100%" }}
transition={{ duration: 5, ease: "linear" }}
className="absolute bottom-0 left-0 h-1 bg-blue-500/50 z-20"
/>
)}
</div>
);
}