// A sheet's pinned footer that forms in its scrolling body hand their actions to; outside a sheet they stay inline. import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; type FooterSlot = { target: HTMLDivElement | null; setTarget: (element: HTMLDivElement | null) => void; claims: number; claim: () => () => void; }; const Slot = createContext(null); export const OverlayFooterHost = ({ children }: { children: ReactNode }) => { const [target, setTarget] = useState(null); const [claims, setClaims] = useState(0); const claim = useCallback(() => { setClaims((count) => count + 1); return () => setClaims((count) => count - 1); }, []); const slot = useMemo(() => ({ target, setTarget, claims, claim }), [target, claims, claim]); return {children}; }; export const OverlayFooter = () => { const slot = useContext(Slot); if (!slot || slot.claims === 0) return null; return
; }; // Submit buttons rendered here sit outside their
in the DOM, so they must name it with `form=`. export const OverlayActions = ({ className, children }: { className?: string; children: ReactNode }) => { const slot = useContext(Slot); const claim = slot?.claim; useEffect(() => claim?.(), [claim]); if (!slot) return
{children}
; return slot.target ? createPortal(children, slot.target) : null; };