Files
apps-registry/workloads/ecommerce/components/product/product-carousel.tsx
2026-07-19 21:36:35 -05:00

49 lines
2.7 KiB
TypeScript

"use client";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { ProductCard } from "@/components/product/product-card";
import type { Product } from "@/lib/headless/types";
export function ProductCarousel({ products }: { products: Product[] }) {
const scroller = useRef<HTMLDivElement>(null);
const [canPrevious, setCanPrevious] = useState(false);
const [canNext, setCanNext] = useState(products.length > 1);
const updateControls = useCallback(() => {
const node = scroller.current;
if (!node) return;
setCanPrevious(node.scrollLeft > 8);
setCanNext(node.scrollLeft + node.clientWidth < node.scrollWidth - 8);
}, []);
useEffect(() => {
const node = scroller.current;
if (!node) return;
updateControls();
node.addEventListener("scroll", updateControls, { passive: true });
const observer = new ResizeObserver(updateControls);
observer.observe(node);
return () => {
node.removeEventListener("scroll", updateControls);
observer.disconnect();
};
}, [updateControls]);
const move = (direction: -1 | 1) => {
const node = scroller.current;
if (!node) return;
node.scrollBy({ left: direction * Math.max(280, node.clientWidth * 0.82), behavior: "smooth" });
};
return (
<div className="relative">
<button type="button" aria-label="Productos anteriores" disabled={!canPrevious} onClick={() => move(-1)} className="absolute -left-3 top-[38%] z-10 grid size-12 place-items-center rounded-full border border-slate-200 bg-white text-slate-950 shadow-xl transition hover:bg-violet-50 disabled:pointer-events-none disabled:opacity-0 sm:-left-6"><ChevronLeft className="size-6" /></button>
<div ref={scroller} role="region" aria-label="Carrusel de productos" tabIndex={0} onKeyDown={(event) => { if (event.key === "ArrowLeft") move(-1); if (event.key === "ArrowRight") move(1); }} className="flex snap-x snap-mandatory gap-5 overflow-x-auto scroll-smooth pb-5 pr-5 outline-none [overscroll-behavior-inline:contain] [scrollbar-width:none]">
{products.map((product, index) => <div key={product.id} className="w-[82%] shrink-0 snap-start sm:w-[44%] lg:w-[28%] xl:w-[23%]"><ProductCard product={product} priority={index < 2} /></div>)}
</div>
<button type="button" aria-label="Productos siguientes" disabled={!canNext} onClick={() => move(1)} className="absolute -right-3 top-[38%] z-10 grid size-12 place-items-center rounded-full border border-slate-200 bg-white text-slate-950 shadow-xl transition hover:bg-violet-50 disabled:pointer-events-none disabled:opacity-0 sm:-right-6"><ChevronRight className="size-6" /></button>
</div>
);
}