Expand the gallery with prev/next browsing and fullscreen detail view, golden influence lamps and chronological wall layout in 3D halls, and scripts/docs for catalog expansion, influence edges, and multi-source image fetching. Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
1.6 KiB
TypeScript
71 lines
1.6 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import './PaintingLightbox.css';
|
|
|
|
interface Props {
|
|
src: string;
|
|
alt: string;
|
|
title?: string;
|
|
subtitle?: string;
|
|
closeHint?: string;
|
|
onClose: () => void;
|
|
onDetails?: () => void;
|
|
}
|
|
|
|
export default function PaintingLightbox({
|
|
src,
|
|
alt,
|
|
title,
|
|
subtitle,
|
|
closeHint = 'Click anywhere to close',
|
|
onClose,
|
|
onDetails,
|
|
}: Props) {
|
|
useEffect(() => {
|
|
const prevOverflow = document.body.style.overflow;
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
window.addEventListener('keydown', onKey);
|
|
|
|
return () => {
|
|
document.body.style.overflow = prevOverflow;
|
|
window.removeEventListener('keydown', onKey);
|
|
};
|
|
}, [onClose]);
|
|
|
|
return createPortal(
|
|
<div
|
|
className="painting-lightbox"
|
|
onClick={onClose}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={title || alt}
|
|
>
|
|
{(title || subtitle || onDetails) && (
|
|
<div className="painting-lightbox-caption">
|
|
{title && <strong>{title}</strong>}
|
|
{subtitle && <span>{subtitle}</span>}
|
|
{onDetails && (
|
|
<button
|
|
type="button"
|
|
className="painting-lightbox-details"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onDetails();
|
|
}}
|
|
>
|
|
View details →
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
<img src={src} alt={alt} />
|
|
<span className="painting-lightbox-hint">{closeHint}</span>
|
|
</div>,
|
|
document.body
|
|
);
|
|
}
|