"use client"; /** * PostImageLightbox — full-screen viewer for a post's attached photos. * * Opened by tapping any image on a PostCard. Controlled by the caller: `index` * is the photo being shown (null = closed), so the card owns the state and this * component stays a pure presentation layer. * * Dismiss: tap the backdrop ring around the photo, the ✕, or press Escape. * Navigate a multi-photo post with ← / → or the on-screen chevrons. * * Rendered through a portal onto
so it escapes the feed's virtualised, * transformed rows — an overlay nested inside one of those would be clipped by * the row's stacking context and positioned against the transform, not the * viewport. */ import { useCallback, useEffect } from "react"; import { createPortal } from "react-dom"; import Image from "next/image"; import { ChevronLeft, ChevronRight, X } from "lucide-react"; import type { PostImage } from "@/lib/social"; export function PostImageLightbox({ images, index, caption, onIndexChange, onClose, }: { images: PostImage[]; /** Photo currently shown; null closes the viewer. */ index: number | null; /** The post's caption, shown in a gradient scrim along the bottom. * Clamped to three lines with an ellipsis so a long passage can't cover * the photo. Omit (or pass empty) to hide the scrim entirely. */ caption?: string | null; onIndexChange: (i: number) => void; onClose: () => void; }) { const open = index !== null; const count = images.length; // Wrap around at both ends so a long press on → never dead-ends. const goPrev = useCallback(() => { if (index === null || count < 2) return; onIndexChange((index - 1 + count) % count); }, [index, count, onIndexChange]); const goNext = useCallback(() => { if (index === null || count < 2) return; onIndexChange((index + 1) % count); }, [index, count, onIndexChange]); useEffect(() => { if (!open) return; function onKey(e: KeyboardEvent) { if (e.key === "Escape") onClose(); else if (e.key === "ArrowLeft") goPrev(); else if (e.key === "ArrowRight") goNext(); } document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [open, onClose, goPrev, goNext]); // Lock body scroll while open so the feed underneath doesn't drift. useEffect(() => { if (!open) return; const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, [open]); if (!open || typeof document === "undefined") return null; const img = images[index]; if (!img) return null; return createPortal(“{caption.trim()}”