kennzeichen/frontend/src/app/components/ImageZoomModal.tsx
2025-10-23 12:11:41 +02:00

117 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useEffect, useState } from 'react';
import { Button } from './Button';
import Image from 'next/image';
import Toast from './Toast';
interface ImageZoomModalProps {
src: string;
alt?: string;
onClose: () => void;
}
export default function ImageZoomModal({ src, alt = 'Bild', onClose }: ImageZoomModalProps) {
const [zoom, setZoom] = useState(1);
const [translate, setTranslate] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [onClose]);
return (
<div
onClick={(e) => {
if (e.target === e.currentTarget) {
setZoom(1);
setTranslate({ x: 0, y: 0 });
onClose();
}
}}
className="fixed inset-0 z-50 bg-black bg-opacity-80 flex items-center justify-center"
>
<div
onClick={(e) => e.stopPropagation()}
className="relative w-full h-full flex items-center justify-center overflow-hidden"
>
{/* Toast (oben links) dauerhaft sichtbar */}
<div className="absolute top-4 left-4 z-50 cursor-default">
<Toast>
<div className="grid grid-cols-[1fr_auto] grid-rows-3 gap-x-4 gap-y-2 text-sm text-black dark:text-white">
<div><kbd className="px-2 py-1 bg-gray-200 text-black rounded">ESC</kbd></div>
<div>Schließen</div>
<div><kbd className="px-2 py-1 bg-gray-200 text-black rounded">Mausrad</kbd></div>
<div>Bild zoomen</div>
<div><kbd className="px-2 py-1 bg-gray-200 text-black rounded">Linke Maustaste</kbd></div>
<div>Bild verschieben</div>
</div>
</Toast>
</div>
{/* Close Button oben rechts */}
<div className="absolute top-4 right-4 z-50">
<Button
onClick={() => {
setZoom(1);
setTranslate({ x: 0, y: 0 });
onClose();
}}
size="default"
variant="solid"
color="red"
>
</Button>
</div>
{/* Bild */}
<div className="relative w-full max-w-4xl aspect-[4/3]">
<Image
src={src}
alt={alt}
fill
unoptimized
className="object-contain select-none cursor-grab active:cursor-grabbing"
style={{
transform: `scale(${zoom}) translate(${translate.x}px, ${translate.y}px)`,
transition: 'transform 0.2s',
}}
onWheel={(e) => {
e.preventDefault();
const delta = e.deltaY > 0 ? -0.1 : 0.1;
setZoom((z) => Math.min(Math.max(z + delta, 1), 5));
}}
onMouseDown={(e) => {
e.preventDefault();
const startX = e.clientX;
const startY = e.clientY;
const startTranslate = { ...translate };
const handleMove = (moveEvent: MouseEvent) => {
const dx = moveEvent.clientX - startX;
const dy = moveEvent.clientY - startY;
setTranslate({
x: startTranslate.x + dx,
y: startTranslate.y + dy,
});
};
const handleUp = () => {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('mouseup', handleUp);
};
window.addEventListener('mousemove', handleMove);
window.addEventListener('mouseup', handleUp);
}}
/>
</div>
</div>
</div>
);
}