import { css } from "@linaria/core"; import { createPortal, forwardRef } from "preact/compat"; import { h, JSX, VNode, ComponentChildren, RefObject, isValidElement, cloneElement, Fragment, } from "preact"; import { Ref, useEffect, useMemo, useState } from "preact/hooks"; // eslint-disable-next-line import/extensions import { alpha } from "./colors/manipulation"; // eslint-disable-next-line import/extensions import { theme } from "./style"; const baseStyle = css` position: fixed; z-index: ${theme.zIndex.modal}; right: 0px; bottom: 0px; top: 0px; left: 0px; `; interface Props { class: string; children: ComponentChildren; disablePortal?: boolean; container?: VNode; } export const Portal = forwardRef(function Portal( { container, disablePortal, children }: Props, ref: Ref, ): VNode { const [mountNode, setMountNode] = useState( undefined, ); const handleRef = useForkRef( isValidElement(children) ? children.ref : null, ref, ); useEffect(() => { if (!disablePortal) { setMountNode(getContainer(container) || document.body); } }, [container, disablePortal]); useEffect(() => { if (mountNode && !disablePortal) { setRef(ref, mountNode); return () => { setRef(ref, null); }; } return undefined; }, [ref, mountNode, disablePortal]); if (disablePortal) { if (isValidElement(children)) { return cloneElement(children, { ref: handleRef, }); } return {children}; } return mountNode ? ( createPortal({children}, mountNode) ) : ( ); }); function getContainer(container: any): any { return typeof container === "function" ? container() : container; } function useForkRef( refA: React.Ref | null | undefined, refB: React.Ref | null | undefined, ): React.Ref | null { /** * This will create a new function if the ref props change and are defined. * This means react will call the old forkRef with `null` and the new forkRef * with the ref. Cleanup naturally emerges from this behavior. */ return useMemo(() => { if (refA == null && refB == null) { return null; } return (refValue) => { setRef(refA, refValue); setRef(refB, refValue); }; }, [refA, refB]); } function setRef( ref: RefObject | ((instance: T | null) => void) | null | undefined, value: T | null, ): void { if (typeof ref === "function") { ref(value); } else if (ref) { ref.current = value; } }