"use client"; import React, { useState, useEffect, useCallback, useRef } from 'react'; export interface CarouselItem { /** Image source */ src?: string; /** Alt text for image */ alt?: string; /** Caption title */ title?: string; /** Caption description */ description?: string; /** Custom content instead of image */ content?: React.ReactNode; } export interface CarouselProps { /** Carousel items */ items: CarouselItem[]; /** Show indicators */ indicators?: boolean; /** Show controls (prev/next) */ controls?: boolean; /** Auto play */ autoPlay?: boolean; /** Interval in ms */ interval?: number; /** Pause on hover */ pauseOnHover?: boolean; /** Enable keyboard navigation */ keyboard?: boolean; /** Enable touch/swipe */ touch?: boolean; /** Crossfade animation */ fade?: boolean; /** Dark variant */ dark?: boolean; /** Active index (controlled) */ activeIndex?: number; /** Callback when slide changes */ onSlide?: (index: number) => void; /** Additional CSS classes */ className?: string; } export const Carousel: React.FC = ({ items, indicators = true, controls = true, autoPlay = false, interval = 5000, pauseOnHover = true, keyboard = true, touch = true, fade = false, dark = false, activeIndex: controlledIndex, onSlide, className = '', }) => { const [currentIndex, setCurrentIndex] = useState(controlledIndex ?? 0); const [isPaused, setIsPaused] = useState(false); const [touchStart, setTouchStart] = useState(null); const carouselRef = useRef(null); const activeIndex = controlledIndex ?? currentIndex; const goToSlide = useCallback((index: number) => { const newIndex = index < 0 ? items.length - 1 : index >= items.length ? 0 : index; if (controlledIndex === undefined) { setCurrentIndex(newIndex); } onSlide?.(newIndex); }, [items.length, controlledIndex, onSlide]); const goToPrev = useCallback(() => { goToSlide(activeIndex - 1); }, [activeIndex, goToSlide]); const goToNext = useCallback(() => { goToSlide(activeIndex + 1); }, [activeIndex, goToSlide]); // Auto play useEffect(() => { if (!autoPlay || isPaused) return; const timer = setInterval(() => { goToNext(); }, interval); return () => clearInterval(timer); }, [autoPlay, isPaused, interval, goToNext]); // Keyboard navigation useEffect(() => { if (!keyboard) return; const handleKeydown = (e: KeyboardEvent) => { if (e.key === 'ArrowLeft') { goToPrev(); } else if (e.key === 'ArrowRight') { goToNext(); } }; const carousel = carouselRef.current; carousel?.addEventListener('keydown', handleKeydown); return () => carousel?.removeEventListener('keydown', handleKeydown); }, [keyboard, goToPrev, goToNext]); // Touch handling const handleTouchStart = (e: React.TouchEvent) => { if (!touch) return; setTouchStart(e.touches[0].clientX); }; const handleTouchEnd = (e: React.TouchEvent) => { if (!touch || touchStart === null) return; const touchEnd = e.changedTouches[0].clientX; const diff = touchStart - touchEnd; if (Math.abs(diff) > 50) { if (diff > 0) { goToNext(); } else { goToPrev(); } } setTouchStart(null); }; const classes = [ 'll-carousel', fade && 'll-carousel-fade', dark && 'll-carousel-dark', className, ].filter(Boolean).join(' '); return (
pauseOnHover && setIsPaused(true)} onMouseLeave={() => pauseOnHover && setIsPaused(false)} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} > {/* Indicators */} {indicators && items.length > 1 && (
{items.map((_, index) => (
)} {/* Slides */}
{items.map((item, index) => (
{item.content || ( {item.alt )} {(item.title || item.description) && (
{item.title &&
{item.title}
} {item.description &&

{item.description}

}
)}
))}
{/* Controls */} {controls && items.length > 1 && ( <> )}
); };