--- name: image-carousel description: Creates image carousels with hover-activated auto-advance, touch swipe support, and animated progress indicators. Use when building image galleries, product showcases, or any multi-image display with navigation. --- # Image Carousel Pattern Build smooth image carousels that auto-advance on hover with touch swipe support and animated progress indicators. ## Core Features - **Hover-activated**: Auto-advance starts only when user hovers (not on page load) - **Touch swipe**: Mobile-friendly swipe navigation with threshold detection - **Progress indicators**: Glassmorphic pill indicators with animated fill - **Pause on interaction**: Manual navigation pauses auto-advance temporarily ## State Management ```tsx const [currentIndex, setCurrentIndex] = useState(0); const [isHovered, setIsHovered] = useState(false); const [progressKey, setProgressKey] = useState(0); // Forces animation restart const [isPaused, setIsPaused] = useState(false); const [touchStart, setTouchStart] = useState(null); ``` ## Core Implementation ```tsx "use client"; import { useState, useEffect } from "react"; import Image from "next/image"; const images = [ "/images/image-1.jpeg", "/images/image-2.jpeg", "/images/image-3.jpeg", ]; function ImageCarousel() { const [currentIndex, setCurrentIndex] = useState(0); const [isHovered, setIsHovered] = useState(false); const [progressKey, setProgressKey] = useState(0); const [isPaused, setIsPaused] = useState(false); const [touchStart, setTouchStart] = useState(null); // Auto-advance effect - only when hovered and not paused useEffect(() => { if (!isHovered || isPaused) return; const interval = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % images.length); setProgressKey((prev) => prev + 1); }, 3000); return () => clearInterval(interval); }, [isHovered, isPaused]); // Touch handlers const handleTouchStart = (e: React.TouchEvent) => { setTouchStart(e.touches[0].clientX); }; const handleTouchEnd = (e: React.TouchEvent) => { if (touchStart === null) return; const touchEnd = e.changedTouches[0].clientX; const diff = touchStart - touchEnd; const threshold = 50; if (Math.abs(diff) > threshold) { if (diff > 0) { // Swipe left - next image setCurrentIndex((prev) => (prev + 1) % images.length); } else { // Swipe right - previous image setCurrentIndex((prev) => (prev - 1 + images.length) % images.length); } setProgressKey((prev) => prev + 1); setIsPaused(true); setTimeout(() => setIsPaused(false), 3000); } setTouchStart(null); }; return (
{ setIsHovered(true); setProgressKey((prev) => prev + 1); }} onMouseLeave={() => setIsHovered(false)} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} > {/* Images with fade transition */} {images.map((src, index) => ( {`Image ))} {/* Glassmorphic indicator container */}
{images.map((_, index) => ( ))}
); } ``` ## Required CSS (add to globals.css) ```css /* Carousel progress animation */ @keyframes carousel-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } .animate-carousel-progress { animation: carousel-progress linear forwards; } ``` ## Key Behaviors ### Auto-Advance Logic | State | Behavior | |-------|----------| | Not hovered | No auto-advance | | Hovered + not paused | Auto-advance every 3s | | Hovered + paused | No auto-advance (resumes after 3s) | ### Touch Swipe - Threshold: 50px minimum swipe distance - Left swipe: Next image - Right swipe: Previous image - After swipe: Pause auto-advance for 3s ### Progress Indicator - Expands from dot (w-2) to pill (w-6) when active - Shows animated fill overlay only when hovering and not paused - `progressKey` forces animation restart on index change ## Indicator Sizing | Context | Active Width | Inactive Width | Height | |---------|-------------|----------------|--------| | Preview (compact) | `w-4` | `w-1.5` | `h-1.5` | | Detail page | `w-6` | `w-2` | `h-2` | ## Timing Configuration | Duration | Use | |----------|-----| | `3000ms` | Auto-advance interval | | `3000ms` | Pause duration after manual interaction | | `700ms` | Image fade transition | | `300ms` | Indicator pill expansion | ## Checklist - [ ] `touch-pan-y` on container for proper scroll behavior - [ ] Images use `fill` prop with `object-cover` - [ ] `progressKey` state for animation restart - [ ] Pause timeout clears and resumes correctly - [ ] `animate-carousel-progress` keyframes added to globals.css