Spaces:
Sleeping
Sleeping
File size: 4,113 Bytes
69a3dd3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import React, { useEffect, useState } from 'react';
import { X, ZoomIn, ZoomOut, Download } from 'lucide-react';
interface ImagePreviewModalProps {
isOpen: boolean;
onClose: () => void;
src: string;
alt?: string;
}
export default function ImagePreviewModal({ isOpen, onClose, src, alt }: ImagePreviewModalProps) {
const [scale, setScale] = useState(1);
const [isDragging, setIsDragging] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [startPos, setStartPos] = useState({ x: 0, y: 0 });
// Reset state when modal opens
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
const handleZoomIn = (e: React.MouseEvent) => {
e.stopPropagation();
setScale(prev => Math.min(prev + 0.5, 4));
};
const handleZoomOut = (e: React.MouseEvent) => {
e.stopPropagation();
setScale(prev => Math.max(prev - 0.5, 1));
if (scale <= 1.5) {
setPosition({ x: 0, y: 0 }); // Reset position if zoomed out
}
};
const handleDownload = (e: React.MouseEvent) => {
e.stopPropagation();
const link = document.createElement('a');
link.href = src;
link.download = alt || 'image-download';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// Mouse event handlers for dragging
const handleMouseDown = (e: React.MouseEvent) => {
if (scale > 1) {
setIsDragging(true);
setStartPos({ x: e.clientX - position.x, y: e.clientY - position.y });
}
};
const handleMouseMove = (e: React.MouseEvent) => {
if (isDragging && scale > 1) {
setPosition({
x: e.clientX - startPos.x,
y: e.clientY - startPos.y
});
}
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-[200] flex items-center justify-center bg-black/80 backdrop-blur-sm animate-in fade-in duration-200"
onClick={onClose}
>
{/* Toolbar */}
<div className="absolute top-4 right-4 flex items-center gap-2 z-10" onClick={e => e.stopPropagation()}>
<button
onClick={handleZoomIn}
className="p-2 text-white/70 hover:text-white bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Zoom In"
>
<ZoomIn size={20} />
</button>
<button
onClick={handleZoomOut}
className="p-2 text-white/70 hover:text-white bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Zoom Out"
>
<ZoomOut size={20} />
</button>
<button
onClick={handleDownload}
className="p-2 text-white/70 hover:text-white bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Download"
>
<Download size={20} />
</button>
<button
onClick={onClose}
className="p-2 text-white/70 hover:text-white bg-black/50 hover:bg-black/70 rounded-full transition-colors ml-2"
title="Close"
>
<X size={24} />
</button>
</div>
{/* Image Container */}
<div
className="relative w-full h-full flex items-center justify-center p-4 overflow-hidden"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<img
src={src}
alt={alt || 'Preview'}
className={`max-w-full max-h-full object-contain transition-transform duration-200 ${isDragging ? 'cursor-grabbing' : scale > 1 ? 'cursor-grab' : 'cursor-default'}`}
style={{
transform: `scale(${scale}) translate(${position.x / scale}px, ${position.y / scale}px)`,
}}
onClick={(e) => e.stopPropagation()}
onMouseDown={handleMouseDown}
draggable={false}
/>
</div>
</div>
);
}
|