65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect } from 'react';
|
|
import { X } from 'lucide-react';
|
|
|
|
interface PhotoModalProps {
|
|
photoUrl: string;
|
|
onClose: () => void;
|
|
title?: string;
|
|
}
|
|
|
|
export default function PhotoModal({
|
|
photoUrl,
|
|
onClose,
|
|
title = 'Photo',
|
|
}: PhotoModalProps) {
|
|
useEffect(() => {
|
|
const handleEscapeKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleEscapeKey);
|
|
return () => window.removeEventListener('keydown', handleEscapeKey);
|
|
}, [onClose]);
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
|
|
onClick={onClose}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={`Photo viewer for ${title}`}
|
|
>
|
|
<div
|
|
className="bg-surface border border-outline/30 max-w-2xl w-full max-h-[90vh] overflow-auto flex flex-col"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Header */}
|
|
<div className="sticky top-0 bg-surface border-b border-outline/30/50 p-4 md:p-6 flex items-center justify-between">
|
|
<h2 className="text-xl md:text-2xl font-normal text-foreground truncate">{title}</h2>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-surface-container text-secondary hover:text-foreground transition-colors"
|
|
aria-label="Close modal"
|
|
>
|
|
<X size={20} className="text-error" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Image Container */}
|
|
<div className="p-4 md:p-6 flex items-center justify-center flex-1">
|
|
<img
|
|
src={photoUrl}
|
|
alt={title}
|
|
className="max-w-full max-h-[calc(90vh-120px)] object-contain"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|