Spaces:
Running
Running
| 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> | |
| ); | |
| } | |