Merge feat/product-gallery-hover-zoom into main
Build and Push Medusa / Escaneo de Secretos (Gitleaks) (push) Successful in 21s
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 19s
Build and Push Medusa / Construir y publicar Medusa (push) Successful in 23m54s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 9m11s

This commit is contained in:
devops committed 2026-09-06 19:19:19 +00:00
commit 8fd0514e76
1 file changed
+36 -3
@@ -2,19 +2,36 @@
import Image from "next/image";
import { Maximize2, Minus, Plus, RotateCcw, ZoomIn } from "lucide-react";
import { PointerEvent, WheelEvent, useRef, useState } from "react";
import { MouseEvent, PointerEvent, WheelEvent, useRef, useState } from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
interface Offset { x: number; y: number }
interface DragState { pointerId: number; startX: number; startY: number; originX: number; originY: number }
interface HoverZoomOrigin { x: number; y: number }
// Zoom automatico al pasar el cursor (estilo lupa clasico de e-commerce)
// sobre la imagen principal -- solo mouse (mousemove no dispara de forma
// continua en touch, asi que en mobile simplemente no aparece el efecto,
// sin romper nada). Independiente del zoom interactivo del modal
// (click -> Dialog con scale/drag), que sigue igual.
const HOVER_ZOOM_SCALE = 2.2;
export function ProductGallery({ images, name }: { images: string[]; name: string }) {
const [selected, setSelected] = useState(images[0] ?? "");
const [open, setOpen] = useState(false);
const [scale, setScale] = useState(1);
const [offset, setOffset] = useState<Offset>({ x: 0, y: 0 });
const [hoverZoom, setHoverZoom] = useState<HoverZoomOrigin | null>(null);
const drag = useRef<DragState | null>(null);
const trackHoverZoom = (event: MouseEvent<HTMLButtonElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
setHoverZoom({
x: ((event.clientX - rect.left) / rect.width) * 100,
y: ((event.clientY - rect.top) / rect.height) * 100,
});
};
const applyScale = (next: number) => {
const value = Math.min(3, Math.max(1, Number(next.toFixed(2))));
setScale(value);
@@ -44,8 +61,24 @@ export function ProductGallery({ images, name }: { images: string[]; name: strin
</div>
<Dialog open={open} onOpenChange={changeOpen}>
<DialogTrigger asChild>
<button type="button" className="group relative order-1 aspect-square overflow-hidden rounded-[2rem] bg-slate-50 lg:order-2">
<Image src={selected} alt={name} fill priority sizes="(max-width: 1024px) 100vw, 50vw" className="object-cover transition duration-500 group-hover:scale-[1.03]" />
<button
type="button"
className="group relative order-1 aspect-square cursor-zoom-in overflow-hidden rounded-[2rem] bg-slate-50 lg:order-2"
onMouseMove={trackHoverZoom}
onMouseLeave={() => setHoverZoom(null)}
>
<Image
src={selected}
alt={name}
fill
priority
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-cover transition-transform duration-150 ease-out"
style={{
transform: `scale(${hoverZoom ? HOVER_ZOOM_SCALE : 1})`,
transformOrigin: hoverZoom ? `${hoverZoom.x}% ${hoverZoom.y}%` : "center",
}}
/>
<span className="absolute bottom-5 right-5 inline-flex items-center gap-2 rounded-full bg-white px-4 py-2 text-xs font-black shadow-xl"><ZoomIn className="size-4" />Zoom interactivo</span>
</button>
</DialogTrigger>