This commit is contained in:
106
components/ZoomableImage.tsx
Normal file
106
components/ZoomableImage.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useState } from 'react';
|
||||
import { GlassMagnifier } from 'react-image-magnifiers';
|
||||
|
||||
interface ZoomableImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
magnifierSize?: string;
|
||||
zoomLevel?: number;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean; // New prop to prevent zoom on click
|
||||
}
|
||||
|
||||
export const ZoomableImage: React.FC<ZoomableImageProps> = ({
|
||||
src,
|
||||
alt,
|
||||
className = "",
|
||||
magnifierSize = "30%",
|
||||
zoomLevel = 2.5,
|
||||
children,
|
||||
disabled = false
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleModal = (e: React.MouseEvent) => {
|
||||
if (disabled) return; // Don't open if disabled
|
||||
e.stopPropagation();
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Thumbnail trigger */}
|
||||
<div
|
||||
className={`cursor-zoom-in relative group ${className}`}
|
||||
onClick={toggleModal}
|
||||
title="Click to zoom"
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="w-full h-full object-cover" // Changed to object-cover to match Home usage expectation or passed className? Actually lets keep original but fix usage if needed. Wait, original was w-full h-auto object-contain.
|
||||
/>
|
||||
{children}
|
||||
{!children && (
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100">
|
||||
<span className="bg-white/80 text-stone-900 p-2 rounded-full shadow-lg backdrop-blur-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607ZM10.5 7.5v6m3-3h-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-[9999] bg-stone-900/95 backdrop-blur-md flex items-center justify-center p-8 animate-in fade-in duration-200"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<div className="relative w-full h-full max-w-7xl flex items-center justify-center" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="absolute -top-6 -right-6 md:top-0 md:-right-12 text-white/50 hover:text-white transition-colors p-2 z-50"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-8 h-8">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="w-full h-full flex items-center justify-center rounded-lg overflow-hidden">
|
||||
<GlassMagnifier
|
||||
imageSrc={src}
|
||||
imageAlt={alt}
|
||||
magnifierSize={magnifierSize}
|
||||
magnifierBorderSize={2}
|
||||
magnifierBorderColor="rgba(255, 255, 255, 0.5)"
|
||||
square={false}
|
||||
allowOverflow={true}
|
||||
style={{
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '90vh',
|
||||
display: 'block'
|
||||
}}
|
||||
imageStyle={{
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '90vh',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 bg-black/50 text-white px-4 py-2 rounded-full text-xs font-medium backdrop-blur-md pointer-events-none z-50">
|
||||
Hover to magnify • Click outside to close
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user