manus-clone-cn / src /components /ImagePreviewModal.tsx
Trae Assistant
Initial commit
69a3dd3
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>
);
}