aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/mui/Portal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/mui/Portal.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/mui/Portal.tsx113
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;
+ }
+}