chore: transfer repo
This commit is contained in:
107
components/home/ProductSlider.tsx
Normal file
107
components/home/ProductSlider.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user