Wire homepage specials, product/blog pages, instant search, and the customer portal so the live pharmacy site can be managed from the dashboard. Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { X } from "lucide-react";
|
|
import { Placeholder } from "@/components/Placeholder";
|
|
|
|
export function ProductGallery({ images, alt }: { images: string[]; alt: string }) {
|
|
const gallery = images.length ? images : ["/placeholders/product.svg"];
|
|
const [active, setActive] = useState(0);
|
|
const [lightbox, setLightbox] = useState(false);
|
|
const [origin, setOrigin] = useState("50% 50%");
|
|
const [zoomed, setZoomed] = useState(false);
|
|
const current = gallery[active] ?? gallery[0];
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
<button
|
|
type="button"
|
|
className="relative block w-full overflow-hidden rounded-2xl bg-sky"
|
|
onClick={() => setLightbox(true)}
|
|
onMouseEnter={() => setZoomed(true)}
|
|
onMouseLeave={() => {
|
|
setZoomed(false);
|
|
setOrigin("50% 50%");
|
|
}}
|
|
onMouseMove={(event) => {
|
|
const box = event.currentTarget.getBoundingClientRect();
|
|
const x = ((event.clientX - box.left) / box.width) * 100;
|
|
const y = ((event.clientY - box.top) / box.height) * 100;
|
|
setOrigin(`${x}% ${y}%`);
|
|
}}
|
|
>
|
|
<Placeholder
|
|
src={current}
|
|
fallbackSrc="/placeholders/product.svg"
|
|
alt={alt}
|
|
className="aspect-square w-full object-cover transition-transform duration-150"
|
|
/>
|
|
<span
|
|
className="pointer-events-none absolute inset-0 bg-cover bg-no-repeat"
|
|
style={{
|
|
backgroundImage: `url(${current})`,
|
|
transform: zoomed ? "scale(1.85)" : "scale(1)",
|
|
transformOrigin: origin,
|
|
opacity: zoomed ? 1 : 0,
|
|
}}
|
|
/>
|
|
</button>
|
|
{gallery.length > 1 ? (
|
|
<div className="mt-3 flex gap-2 overflow-x-auto">
|
|
{gallery.map((image, index) => (
|
|
<button
|
|
key={`${image}-${index}`}
|
|
type="button"
|
|
onClick={() => setActive(index)}
|
|
className={`size-16 shrink-0 overflow-hidden rounded-xl border ${index === active ? "border-primary" : "border-line"}`}
|
|
>
|
|
<Placeholder src={image} className="size-full object-cover" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{lightbox ? (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-navy/80 p-4" onClick={() => setLightbox(false)}>
|
|
<button type="button" className="absolute top-4 left-4 grid size-10 place-items-center rounded-full bg-white text-navy" aria-label="بستن">
|
|
<X className="size-5" />
|
|
</button>
|
|
<img
|
|
src={current}
|
|
alt={alt}
|
|
className="max-h-[90vh] max-w-full rounded-2xl object-contain"
|
|
onClick={(event) => event.stopPropagation()}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|