import { useEffect, useId, useLayoutEffect, useRef, useState } from "react"; import type { ReactNode } from "react"; import { createPortal } from "react-dom"; import { useLift } from "./LiftContext"; type LiftableProps = { id?: string; always?: boolean; }; type Props = LiftableProps & { children: ReactNode; }; export function useLiftable = Record>( render: (props: { isLifted: boolean } & T) => ReactNode, options?: LiftableProps & T, ): { id: string; element: ReactNode } { const autoId = useId(); const { id: idProp, always, ...extraProps } = (options ?? {}) as LiftableProps & Record; const id = idProp ?? autoId; const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways } = useLift(); useEffect(() => { if (always) { registerAlways(id); return () => unregisterAlways(id); } }, [always, id, registerAlways, unregisterAlways]); const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id); const element = ( {render({ isLifted, ...extraProps } as { isLifted: boolean } & T)} ); return { id, element }; } export function Liftable({ id: idProp, always, children }: Props) { const autoId = useId(); const id = idProp ?? autoId; const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways, portalContainer } = useLift(); const wrapperRef = useRef(null); const [rect, setRect] = useState(null); useEffect(() => { if (always) { registerAlways(id); return () => unregisterAlways(id); } }, [always, id, registerAlways, unregisterAlways]); const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id); useLayoutEffect(() => { if (isLifted && wrapperRef.current) { setRect(wrapperRef.current.getBoundingClientRect()); } if (!isLifted) { setRect(null); } }, [isLifted]); // Re-measure on resize while lifted useEffect(() => { if (!isLifted || !wrapperRef.current) return; const measure = () => { if (wrapperRef.current) { setRect(wrapperRef.current.getBoundingClientRect()); } }; const observer = new ResizeObserver(measure); observer.observe(wrapperRef.current); window.addEventListener("resize", measure); return () => { observer.disconnect(); window.removeEventListener("resize", measure); }; }, [isLifted]); // When lifted and we have measurements + portal target, render in portal if (isLifted && rect && portalContainer) { return ( <> {/* Placeholder preserves layout space */}
{/* Portal children above blur */} {createPortal(
{children}
, portalContainer, )} ); } // Normal inline rendering return
{children}
; }