Files
glassmond/components/product-gallery.tsx
T
Alireza HassaniandCursor 98fde02322 Create Glassmond storefront with Meshkee integration
Build the RTL Next.js experience with live catalog, journal, customer interactions, and SEO-ready canonical routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 00:08:00 +03:30

113 lines
3.6 KiB
TypeScript

"use client";
import Image from "next/image";
import { ChevronLeft, ChevronRight, Expand, X } from "lucide-react";
import { useEffect, useState, type MouseEvent } from "react";
export function ProductGallery({
images,
alt,
}: {
images: string[];
alt: string;
}) {
const [active, setActive] = useState(0);
const [lightbox, setLightbox] = useState(false);
const [origin, setOrigin] = useState("50% 50%");
const previous = () => setActive((value) => (value - 1 + images.length) % images.length);
const next = () => setActive((value) => (value + 1) % images.length);
useEffect(() => {
if (!lightbox) return;
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") setLightbox(false);
if (event.key === "ArrowLeft") next();
if (event.key === "ArrowRight") previous();
};
document.body.style.overflow = "hidden";
window.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = "";
window.removeEventListener("keydown", onKey);
};
}, [lightbox, images.length]);
function updateZoomOrigin(event: MouseEvent<HTMLButtonElement>) {
const bounds = event.currentTarget.getBoundingClientRect();
const x = ((event.clientX - bounds.left) / bounds.width) * 100;
const y = ((event.clientY - bounds.top) / bounds.height) * 100;
setOrigin(`${x}% ${y}%`);
}
if (images.length === 0) return null;
return (
<>
<div className="product-gallery">
<button
className="product-gallery-main"
type="button"
onClick={() => setLightbox(true)}
onMouseMove={updateZoomOrigin}
onMouseLeave={() => setOrigin("50% 50%")}
aria-label="نمایش تصویر بزرگ"
>
<Image
src={images[active]}
alt={`${alt} - تصویر ${active + 1}`}
fill
loading="eager"
sizes="(max-width: 900px) 100vw, 55vw"
style={{ transformOrigin: origin }}
/>
<span><Expand /></span>
</button>
{images.length > 1 && (
<div className="gallery-thumbnails">
{images.map((image, index) => (
<button
key={`${image}-${index}`}
type="button"
className={index === active ? "active" : ""}
onClick={() => setActive(index)}
aria-label={`تصویر ${index + 1}`}
>
<Image
src={image}
alt={`${alt} - تصویر کوچک ${index + 1}`}
fill
sizes="90px"
/>
</button>
))}
</div>
)}
</div>
{lightbox && (
<div className="lightbox" role="dialog" aria-modal="true" aria-label={`گالری ${alt}`}>
<button className="lightbox-close" type="button" onClick={() => setLightbox(false)} aria-label="بستن">
<X />
</button>
{images.length > 1 && (
<>
<button className="lightbox-prev" type="button" onClick={previous} aria-label="تصویر قبلی">
<ChevronRight />
</button>
<button className="lightbox-next" type="button" onClick={next} aria-label="تصویر بعدی">
<ChevronLeft />
</button>
</>
)}
<div className="lightbox-image">
<Image src={images[active]} alt={`${alt} - تصویر بزرگ`} fill sizes="100vw" />
</div>
<span className="lightbox-count">{active + 1} / {images.length}</span>
</div>
)}
</>
);
}