49 lines
2.7 KiB
TypeScript
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>
|
|
);
|
|
}
|