"use client"; import "photoswipe/style.css"; import PhotoSwipeLightbox from "photoswipe/lightbox"; import { useCallback, useEffect, useRef, useState } from "react"; import { AspectRatio } from "@/components/ui/aspect-ratio"; import { badgeVariants } from "@/components/ui/badge"; import { buttonVariants } from "@/components/ui/button"; import type { CarouselApi } from "@/components/ui/carousel"; import { Carousel, CarouselContent, CarouselItem, } from "@/components/ui/carousel"; import { useMediaQuery } from "@/hooks/useMediaQuery"; import { cn } from "@/lib/utils"; type ProductImages = { srcset: string; src: string; alt: string; width: number; height: number; sizes: string; thumbnail: string; }[]; interface ProductGallery4Props { images: ProductImages; galleryID: string; className?: string; } const PRODUCT_IMAGES = [ { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/pexels-429124762-20851442-3.jpg 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-2.png 427w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/pexels-429124762-20851442-3.jpg", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-2.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Elegant-Professional-Look-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Elegant-Professional-Look-1.png 427w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Elegant-Professional-Look-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Elegant-Professional-Look-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-1-with-Tote-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-1-with-Tote-1.png 427w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-1-with-Tote-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-1-with-Tote-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-1.png 427w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Fashion-Look-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Leather-Tote-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Leather-Tote-1.png 800w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Leather-Tote-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Leather-Tote-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Tote-Bag-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Tote-Bag-2.png 800w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Tote-Bag-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Woman-with-Tote-Bag-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, { srcset: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-in-Interior-2.png 800w, /block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-in-Interior-1.png 800w", src: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-in-Interior-2.png", thumbnail: "/block-media/shadcnblocks/block/ecommerce/clothes/Stylish-Woman-in-Interior-1.png", alt: "", width: 800, height: 1200, sizes: "(min-width: 800px) 800px, 100vw", }, ]; const ProductGallery4 = ({ images = PRODUCT_IMAGES, galleryID = "ProductGallery4", className, }: ProductGallery4Props) => { const [api, setApi] = useState(); const [current, setCurrent] = useState(0); const ignoreScroll = useRef(false); const isDesktop = useMediaQuery("(width >= 1024px)"); const slideHeightRef = useRef(null); const lightboxRef = useRef(null); useEffect(() => { const lightbox = new PhotoSwipeLightbox({ gallery: "#" + galleryID, children: "a", bgOpacity: 1, wheelToZoom: true, arrowPrev: false, arrowNext: false, close: false, zoom: false, counter: false, mainClass: "[&>div:first-child]:!bg-background", pswpModule: () => import("photoswipe"), }); lightbox.init(); lightboxRef.current = lightbox; const buttonClassName = cn( buttonVariants({ variant: "outline", size: "icon", className: "rounded-full", }), ); const indicatorClassName = cn(badgeVariants({ variant: "secondary" })); lightbox.on("uiRegister", () => { if (lightbox?.pswp?.ui) { lightbox.pswp.ui.registerElement({ name: "custom-close-btn", order: 10, isButton: false, appendTo: "root", className: "absolute top-5 right-5", html: ` `, onInit: (el, pswp) => { el.querySelector("#pswp-close")?.addEventListener("click", () => pswp.close(), ); }, }); } if (lightbox?.pswp?.ui) { lightbox.pswp.ui.registerElement({ name: "custom-indicator-btn", order: 10, isButton: false, appendTo: "root", className: "absolute top-5 left-5", html: `
`, onInit: (el, pswp) => { const indicatorElem = el.querySelector("#pswp-indicator"); let prevIndex = -1; pswp.on("change", () => { prevIndex = pswp.currIndex; if (indicatorElem) { indicatorElem.innerHTML = `${prevIndex + 1} / ${pswp.getNumItems()}`; } }); }, }); } if (lightbox?.pswp?.ui) { lightbox.pswp.ui.registerElement({ name: "custom-next-prev-btns", order: 10, isButton: false, appendTo: "root", className: "absolute top-1/2 inset-x-0 -translate-y-1/2", html: `
`, onInit: (el, pswp) => { el.querySelector("#pswp-prev")?.addEventListener("click", () => pswp.prev(), ); el.querySelector("#pswp-next")?.addEventListener("click", () => pswp.next(), ); }, }); } }); return () => lightbox.destroy(); }, [galleryID]); const onSelect = useCallback((api: CarouselApi) => { if (!api) return; setCurrent(api.selectedScrollSnap() + 1); }, []); useEffect(() => { if (!api) return; api.on("reInit", onSelect); api.on("select", onSelect); return () => { api?.off("select", onSelect); }; }, [api, onSelect]); useEffect(() => { if (lightboxRef.current && api) { lightboxRef.current.on("change", () => { api?.scrollTo(lightboxRef.current?.pswp?.currIndex || 0); }); } }, [api, current]); useEffect(() => { if (!api || !isDesktop) return; const slides = api.slideNodes(); if (slides.length === 0) return; slideHeightRef.current = slides[0].offsetHeight; let lastIndex = -1; let ticking = false; const updateCurrentIndex = () => { if (!slideHeightRef.current) return; const scrollTop = window.scrollY; const slideHeight = slideHeightRef.current; const index = Math.round(scrollTop / slideHeight); if (index !== lastIndex) { lastIndex = index; setCurrent(index); } }; const onScroll = () => { if (ignoreScroll.current || ticking) return; ticking = true; requestAnimationFrame(() => { updateCurrentIndex(); ticking = false; }); }; const onResize = () => { const newHeight = slides[0].offsetHeight; slideHeightRef.current = newHeight; updateCurrentIndex(); }; window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", onResize); return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onResize); }; }, [api, isDesktop, setCurrent]); const handleThumbnailClick = (e: React.MouseEvent) => { const target = e.currentTarget; const index = Number(target.getAttribute("data-index")); const slides = api?.slideNodes(); if (slides && index >= 0 && index < slides.length) { const offsetTop = slides[index].getBoundingClientRect().top + window.scrollY; const headerOffset = 80; ignoreScroll.current = true; window.scrollTo({ top: offsetTop - headerOffset, behavior: "smooth", }); setCurrent(index); setTimeout(() => { ignoreScroll.current = false; }, 500); } }; if (!images) return; return (
    {images.map((img, index) => (
  1. ))}
{images.map((img, index) => ( {img.alt} ))}
    {images.map((_, index) => ( ))}
); }; export { ProductGallery4 };