mirror of
https://github.com/hoornet/vega.git
synced 2026-06-08 06:01:57 -07:00
94f610c2d3
WebKitGTK collapses body <img> elements to 0 height during re-decode, shrinking scrollHeight and clamping scrollTop — articles with body images locked partway down. Give every .prose-article img a fixed 16:9 aspect-ratio + object-fit: contain so the layout box survives the collapse and scrollHeight stays constant. Body images now click-to-open the existing ImageLightbox (with cursor: zoom-in affordance), and the lightbox image itself now closes on click instead of swallowing it.
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import { useEffect, useCallback } from "react";
|
||
|
||
interface ImageLightboxProps {
|
||
images: string[];
|
||
index: number;
|
||
onClose: () => void;
|
||
onNavigate: (index: number) => void;
|
||
}
|
||
|
||
export function ImageLightbox({ images, index, onClose, onNavigate }: ImageLightboxProps) {
|
||
const hasPrev = index > 0;
|
||
const hasNext = index < images.length - 1;
|
||
|
||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||
if (e.key === "Escape") {
|
||
e.stopImmediatePropagation();
|
||
onClose();
|
||
}
|
||
if (e.key === "ArrowLeft" && hasPrev) onNavigate(index - 1);
|
||
if (e.key === "ArrowRight" && hasNext) onNavigate(index + 1);
|
||
}, [onClose, onNavigate, index, hasPrev, hasNext]);
|
||
|
||
useEffect(() => {
|
||
document.addEventListener("keydown", handleKeyDown);
|
||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||
}, [handleKeyDown]);
|
||
|
||
return (
|
||
<div
|
||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-label="Image viewer"
|
||
onClick={onClose}
|
||
>
|
||
{/* Close button */}
|
||
<button
|
||
onClick={onClose}
|
||
aria-label="Close image viewer"
|
||
className="absolute top-4 right-4 text-white/70 hover:text-white text-[20px] w-8 h-8 flex items-center justify-center transition-colors z-10"
|
||
>
|
||
✕
|
||
</button>
|
||
|
||
{/* Counter */}
|
||
{images.length > 1 && (
|
||
<div className="absolute top-4 left-4 text-white/50 text-[12px] z-10">
|
||
{index + 1} / {images.length}
|
||
</div>
|
||
)}
|
||
|
||
{/* Prev arrow */}
|
||
{hasPrev && (
|
||
<button
|
||
onClick={(e) => { e.stopPropagation(); onNavigate(index - 1); }}
|
||
aria-label="Previous image"
|
||
className="absolute left-4 top-1/2 -translate-y-1/2 text-white/50 hover:text-white text-[28px] w-10 h-10 flex items-center justify-center transition-colors z-10"
|
||
>
|
||
‹
|
||
</button>
|
||
)}
|
||
|
||
{/* Image — click to close (same gesture as clicking the backdrop) */}
|
||
<img
|
||
src={images[index]}
|
||
alt={`Image ${index + 1} of ${images.length}`}
|
||
className="max-w-[90vw] max-h-[90vh] object-contain select-none cursor-zoom-out"
|
||
onClick={onClose}
|
||
draggable={false}
|
||
/>
|
||
|
||
{/* Next arrow */}
|
||
{hasNext && (
|
||
<button
|
||
onClick={(e) => { e.stopPropagation(); onNavigate(index + 1); }}
|
||
aria-label="Next image"
|
||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/50 hover:text-white text-[28px] w-10 h-10 flex items-center justify-center transition-colors z-10"
|
||
>
|
||
›
|
||
</button>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|