import { useCallback, useEffect, useRef, useState } from "react"; import { Dialog as DialogPrimitive } from "radix-ui"; import { ChevronLeft, ChevronRight, Download, X } from "lucide-react"; import { attachmentDownloadPath, attachmentFilename } from "@/lib/issue-attachments"; import { isVideoLikeOutput } from "@/lib/issue-output"; export interface GalleryMediaItem { id: string; contentPath: string; openPath?: string; downloadPath?: string; contentType: string; originalFilename: string | null; } interface ImageGalleryModalProps { items: GalleryMediaItem[]; initialIndex: number; open: boolean; onOpenChange: (open: boolean) => void; } export function ImageGalleryModal({ items, initialIndex, open, onOpenChange, }: ImageGalleryModalProps) { const [currentIndex, setCurrentIndex] = useState(initialIndex); const mediaRef = useRef(null); const setMediaRef = useCallback((node: HTMLImageElement | HTMLVideoElement | null) => { mediaRef.current = node; }, []); useEffect(() => { if (open) setCurrentIndex(initialIndex); }, [open, initialIndex]); const goNext = useCallback(() => { setCurrentIndex((i) => (i + 1) % items.length); }, [items.length]); const goPrev = useCallback(() => { setCurrentIndex((i) => (i - 1 + items.length) % items.length); }, [items.length]); useEffect(() => { if (currentIndex < items.length) return; setCurrentIndex(0); }, [currentIndex, items.length]); useEffect(() => { if (!open) return; const handler = (e: KeyboardEvent) => { if (e.key === "ArrowRight") goNext(); else if (e.key === "ArrowLeft") goPrev(); else if (e.key === "Escape") onOpenChange(false); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [open, goNext, goPrev, onOpenChange]); /** Close when clicking empty curtain space (not interactive elements or the image) */ const handleBackdropClick = useCallback( (e: React.MouseEvent) => { const target = e.target as HTMLElement; if ( target.closest("button") || target.closest("a") || target === mediaRef.current ) return; onOpenChange(false); }, [onOpenChange], ); if (items.length === 0) return null; const current = items[currentIndex]; if (!current) return null; const filename = attachmentFilename(current); const isVideo = isVideoLikeOutput(current.contentType, current.originalFilename); return ( {/* Full-screen curtain */} {/* Top bar */}
{filename}
{currentIndex + 1} / {items.length} e.stopPropagation()} >
{/* Main area: nav buttons outside image */}
{/* Left nav zone */}
{items.length > 1 && ( )}
{/* Media */}
{isVideo ? (
{/* Right nav zone */}
{items.length > 1 && ( )}
{/* Bottom padding for balance */}
); }