"use client";
import { useState, useEffect, useCallback, ReactNode } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import Image from "next/image";
// =============================================================================
// CAROUSEL
// Sliding content carousel
// =============================================================================
interface CarouselProps {
children: ReactNode[];
autoPlay?: boolean;
autoPlayInterval?: number;
showArrows?: boolean;
showDots?: boolean;
className?: string;
}
export function Carousel({
children,
autoPlay = false,
autoPlayInterval = 5000,
showArrows = true,
showDots = true,
className,
}: CarouselProps) {
const [current, setCurrent] = useState(0);
const [direction, setDirection] = useState(0);
const slideCount = children.length;
const next = useCallback(() => {
setDirection(1);
setCurrent((prev) => (prev + 1) % slideCount);
}, [slideCount]);
const prev = useCallback(() => {
setDirection(-1);
setCurrent((prev) => (prev - 1 + slideCount) % slideCount);
}, [slideCount]);
const goTo = (index: number) => {
setDirection(index > current ? 1 : -1);
setCurrent(index);
};
useEffect(() => {
if (!autoPlay) return;
const timer = setInterval(next, autoPlayInterval);
return () => clearInterval(timer);
}, [autoPlay, autoPlayInterval, next]);
const variants = {
enter: (direction: number) => ({
x: direction > 0 ? 1000 : -1000,
opacity: 0,
}),
center: {
x: 0,
opacity: 1,
},
exit: (direction: number) => ({
x: direction < 0 ? 1000 : -1000,
opacity: 0,
}),
};
return (
{/* Arrows */}
{showArrows && slideCount > 1 && (
<>
>
)}
{/* Dots */}
{showDots && slideCount > 1 && (
{children.map((_, index) => (
)}
);
}
// =============================================================================
// IMAGE CAROUSEL
// Carousel with images
// =============================================================================
interface ImageCarouselProps {
images: { src: string; alt: string; caption?: string }[];
autoPlay?: boolean;
autoPlayInterval?: number;
showArrows?: boolean;
showDots?: boolean;
showCaptions?: boolean;
className?: string;
}
export function ImageCarousel({
images,
autoPlay = true,
autoPlayInterval = 5000,
showArrows = true,
showDots = true,
showCaptions = true,
className,
}: ImageCarouselProps) {
return (
{images.map((image, index) => (
{showCaptions && image.caption && (
)}
))}
);
}
// =============================================================================
// TESTIMONIAL CAROUSEL
// Carousel for testimonials
// =============================================================================
interface Testimonial {
content: string;
author: string;
role?: string;
avatar?: string;
rating?: number;
}
interface TestimonialCarouselProps {
testimonials: Testimonial[];
autoPlay?: boolean;
className?: string;
}
export function TestimonialCarousel({
testimonials,
autoPlay = true,
className,
}: TestimonialCarouselProps) {
const [current, setCurrent] = useState(0);
useEffect(() => {
if (!autoPlay) return;
const timer = setInterval(() => {
setCurrent((prev) => (prev + 1) % testimonials.length);
}, 6000);
return () => clearInterval(timer);
}, [autoPlay, testimonials.length]);
return (
{/* Rating */}
{testimonials[current].rating && (
{Array.from({ length: 5 }).map((_, i) => (
★
))}
)}
{/* Quote */}
“{testimonials[current].content}”
{/* Author */}
{testimonials[current].avatar && (
)}
{testimonials[current].author}
{testimonials[current].role && (
{testimonials[current].role}
)}
{/* Dots */}
{testimonials.length > 1 && (
{testimonials.map((_, index) => (
)}
);
}