import { createContext, useCallback, useContext, useEffect, useRef, useState, type PropsWithChildren, } from "react"; import { createPortal } from "react-dom"; import { ChevronLeft, ChevronRight, Download, X } from "lucide-react"; import type { Attachment } from "../../../types/post"; type LightboxState = { images: Attachment[]; index: number; } | null; type Ctx = { openLightbox: (images: Attachment[], startIndex?: number) => void; closeLightbox: () => void; }; const LightboxContext = createContext(null); export function useLightbox(): Ctx { const ctx = useContext(LightboxContext); if (!ctx) throw new Error("useLightbox must be used inside ImageLightboxProvider"); return ctx; } export function ImageLightboxProvider({ children }: PropsWithChildren) { const [state, setState] = useState(null); const openLightbox = useCallback((images: Attachment[], startIndex = 0) => { if (!images.length) return; const i = Math.min(Math.max(0, startIndex), images.length - 1); setState({ images, index: i }); }, []); const closeLightbox = useCallback(() => setState(null), []); return ( {children} {state ? ( ) : null} ); } function LightboxView({ images, startIndex, onClose, }: { images: Attachment[]; startIndex: number; onClose: () => void; }) { const [index, setIndex] = useState(startIndex); const touchStartX = useRef(null); const goPrev = useCallback( () => setIndex((i) => (i - 1 + images.length) % images.length), [images.length], ); const goNext = useCallback( () => setIndex((i) => (i + 1) % images.length), [images.length], ); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); if (e.key === "ArrowLeft") goPrev(); if (e.key === "ArrowRight") goNext(); }; window.addEventListener("keydown", onKey); const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = prevOverflow; }; }, [goPrev, goNext, onClose]); const current = images[index]; if (!current) return null; return createPortal(
e.stopPropagation()} className="absolute right-16 top-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20" aria-label="Download" > {images.length > 1 ? ( <>
{index + 1} / {images.length}
) : null} {current.filename} e.stopPropagation()} onTouchStart={(e) => { touchStartX.current = e.touches[0].clientX; }} onTouchEnd={(e) => { if (touchStartX.current == null) return; const dx = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(dx) > 40) { if (dx > 0) goPrev(); else goNext(); } touchStartX.current = null; }} />
, document.body, ); }