107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { getProductColors } from "@/components/products/utils/colorUtils";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { ProductCard } from "@/components/ui/ProductCard";
|
|
import { cn } from "@/lib/utils";
|
|
import useEmblaCarousel from "embla-carousel-react";
|
|
import { Product } from "lib/shopify/types";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { FiChevronLeft, FiChevronRight } from "react-icons/fi";
|
|
|
|
interface ProductSliderProps {
|
|
products: Product[];
|
|
}
|
|
|
|
/**
|
|
* A product slider component that displays products in a horizontal scrollable slider.
|
|
* Uses the Embla Carousel library for the sliding functionality.
|
|
*/
|
|
export function ProductSlider({ products }: ProductSliderProps) {
|
|
const [emblaRef, emblaApi] = useEmblaCarousel({
|
|
align: "start",
|
|
containScroll: false,
|
|
loop: false,
|
|
});
|
|
|
|
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
|
|
const scrollPrev = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollPrev();
|
|
}, [emblaApi]);
|
|
|
|
const scrollNext = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollNext();
|
|
}, [emblaApi]);
|
|
|
|
const onSelect = useCallback(() => {
|
|
if (!emblaApi) return;
|
|
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
}, [emblaApi]);
|
|
|
|
useEffect(() => {
|
|
if (!emblaApi) return;
|
|
onSelect();
|
|
emblaApi.on("select", onSelect);
|
|
emblaApi.on("reInit", onSelect);
|
|
}, [emblaApi, onSelect]);
|
|
|
|
return (
|
|
<div className="relative">
|
|
<div className="overflow-visible" ref={emblaRef}>
|
|
<div className="flex">
|
|
{products.map((product, index) => (
|
|
<div
|
|
key={product.id}
|
|
className={cn(
|
|
"w-[85%] min-[500px]:w-[90%] min-[700px]:w-[48.5%] min-[1100px]:w-[32%] min-w-0 flex-grow-0 flex-shrink-0 mr-[2%]",
|
|
)}
|
|
>
|
|
<ProductCard
|
|
title={product.title}
|
|
variant={product.variants[0]?.title || ""}
|
|
price={parseFloat(product.priceRange.maxVariantPrice.amount)}
|
|
imageSrc={product.featuredImage?.url || "/assets/images/placeholder_image.svg"}
|
|
slug={product.handle}
|
|
product={product}
|
|
colors={getProductColors(product)}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Previous button */}
|
|
<Button
|
|
onClick={scrollPrev}
|
|
disabled={!prevBtnEnabled}
|
|
variant="default"
|
|
size="sm"
|
|
className={cn(
|
|
"absolute left-[-18px] top-1/2 transform -translate-y-1/2 z-10",
|
|
"w-9 h-9 text-sm bg-white shadow-md border border-gray-200 rounded-full",
|
|
!prevBtnEnabled && "opacity-50 cursor-not-allowed"
|
|
)}
|
|
>
|
|
<FiChevronLeft className="w-5 h-5" />
|
|
</Button>
|
|
|
|
{/* Next button */}
|
|
<Button
|
|
onClick={scrollNext}
|
|
disabled={!nextBtnEnabled}
|
|
variant="default"
|
|
size="sm"
|
|
className={cn(
|
|
"absolute right-[-18px] top-1/2 transform -translate-y-1/2 z-10",
|
|
"w-9 h-9 text-sm bg-white shadow-md border border-gray-200 rounded-full",
|
|
!nextBtnEnabled && "opacity-50 cursor-not-allowed"
|
|
)}
|
|
>
|
|
<FiChevronRight className="w-5 h-5" />
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|