diff options
author | Sebastian <sebasjm@gmail.com> | 2022-08-15 21:36:53 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-08-15 21:36:53 -0300 |
commit | 0798aa5cedad2a599829f2b13d4573c64a081c29 (patch) | |
tree | 191ce51f0569c34e124dd20a4a23acecc5f9eae7 /packages/taler-wallet-webextension/src/mui/Portal.tsx | |
parent | cdc8e9afdfb93bd8a90d1e6cf0ea9aa20159e43a (diff) | |
download | wallet-core-0798aa5cedad2a599829f2b13d4573c64a081c29.tar.xz |
modal, popover and portal for select input
Diffstat (limited to 'packages/taler-wallet-webextension/src/mui/Portal.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/mui/Portal.tsx | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/mui/Portal.tsx b/packages/taler-wallet-webextension/src/mui/Portal.tsx new file mode 100644 index 000000000..828a574fd --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/Portal.tsx @@ -0,0 +1,113 @@ +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<any>, +): VNode { + const [mountNode, setMountNode] = useState<HTMLElement | undefined>( + 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 <Fragment>{children}</Fragment>; + } + + return mountNode ? ( + createPortal(<Fragment>{children}</Fragment>, mountNode) + ) : ( + <Fragment /> + ); +}); + +function getContainer(container: any): any { + return typeof container === "function" ? container() : container; +} + +function useForkRef<Instance>( + refA: React.Ref<Instance> | null | undefined, + refB: React.Ref<Instance> | null | undefined, +): React.Ref<Instance> | 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<T>( + ref: RefObject<T | null> | ((instance: T | null) => void) | null | undefined, + value: T | null, +): void { + if (typeof ref === "function") { + ref(value); + } else if (ref) { + ref.current = value; + } +} |