"use client"; // Adapted from Kokonut UI (@dorianbaffier), MIT. See THIRD_PARTY_NOTICES.md. import { Children, useEffect, useId, useRef, useState, type ComponentProps, } from "react"; export interface CarouselProps extends ComponentProps<"section"> { label: string; } export function Carousel({ children, label, className = "", ...props }: CarouselProps) { const slides = Children.toArray(children); const ref = useRef(null); const id = useId(); const [active, setActive] = useState(0); const index = Math.min(active, Math.max(0, slides.length - 1)); const move = (next: number) => { const el = ref.current; if (!el) return; const selected = Math.max(0, Math.min(next, slides.length - 1)); const slide = el.children[selected] as HTMLElement | undefined; if (slide) el.scrollTo({ left: slide.offsetLeft, behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth", }); }; useEffect(() => { const el = ref.current; if (!el) return; const update = () => { let closest = 0, distance = Infinity; Array.from(el.children).forEach((child, i) => { const d = Math.abs((child as HTMLElement).offsetLeft - el.scrollLeft); if (d < distance) { closest = i; distance = d; } }); setActive(closest); }; const observer = new ResizeObserver(update); observer.observe(el); el.addEventListener("scroll", update, { passive: true }); update(); return () => { observer.disconnect(); el.removeEventListener("scroll", update); }; }, [slides.length]); return (
{ if (event.target !== event.currentTarget) return; const next = event.key === "ArrowRight" ? index + 1 : event.key === "ArrowLeft" ? index - 1 : event.key === "Home" ? 0 : event.key === "End" ? slides.length - 1 : undefined; if (next !== undefined) { event.preventDefault(); move(next); } }} > {slides.map((slide, i) => (
{slide}
))}
{slides.length ? `${index + 1} of ${slides.length}` : "No slides"}
); }