import React, { useCallback, useEffect, useId, useRef, useState } from "react";
import { createPortal, flushSync } from "react-dom";
import { X } from "lucide-react";
interface LightboxProps {
onClose: () => void;
children: React.ReactNode;
}
export function Lightbox({ onClose, children }: LightboxProps) {
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}, [onClose]);
if (typeof document === "undefined") return null;
return createPortal(
e.stopPropagation()}>{children}
,
document.body,
);
}
type DocWithVT = Document & {
startViewTransition?: (cb: () => void) => { finished: Promise };
};
/**
* Hook that manages lightbox open/close and uses the View Transition API to
* morph the thumbnail into fullscreen content.
*
* The trick: `view-transition-name` must live on exactly ONE element at a time.
* - Old state (thumbnail visible): name is on the thumbnail.
* - New state (lightbox visible): name moves to the lightbox content.
* `flushSync` ensures React commits the DOM change synchronously inside the
* `startViewTransition` callback so the API can snapshot old → new correctly.
*/
export function useLightbox() {
const thumbnailRef = useRef(null);
const [open, setOpen] = useState(false);
const vtName = useId();
const openLightbox = useCallback(() => {
const thumb = thumbnailRef.current;
const doc = document as DocWithVT;
if (doc.startViewTransition && thumb) {
thumb.style.viewTransitionName = vtName;
doc.startViewTransition(() => {
thumb.style.viewTransitionName = "";
flushSync(() => setOpen(true));
});
} else {
setOpen(true);
}
}, [vtName]);
const closeLightbox = useCallback(() => {
const thumb = thumbnailRef.current;
const doc = document as DocWithVT;
if (doc.startViewTransition && thumb) {
const transition = doc.startViewTransition(() => {
flushSync(() => setOpen(false));
thumb.style.viewTransitionName = vtName;
});
transition.finished
.then(() => {
thumb.style.viewTransitionName = "";
})
.catch(() => {
thumb.style.viewTransitionName = "";
});
} else {
setOpen(false);
}
}, [vtName]);
return {
thumbnailRef,
vtName,
open,
openLightbox,
closeLightbox,
};
}