-
Notifications
You must be signed in to change notification settings - Fork 170
/
portal.ts
69 lines (64 loc) · 1.91 KB
/
portal.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import type { ReactNode, ReactPortal } from "react";
import { useCallback, useContext, useEffect, useState } from "react";
import { RouterContext } from "./context.ts";
/**
* The `usePortal` hook to create a portal helper.
* Please ensure to pass the `React.createPortal` in `Router` props.
*
* ```jsx
* function Modal() {
* const portal = usePortal({ preventScroll: true });
* return portal(<p>Hello portal!</p>);
* }
* ```
*/
export function usePortal(
props?: { key?: string | null; className?: string; lockScroll?: boolean; isDialog?: boolean },
): (children: ReactNode) => ReactPortal | null {
const { className, lockScroll, isDialog, key } = props || {};
const { createPortal } = useContext(RouterContext);
const [portalRoot, setPortalRoot] = useState<HTMLElement | null>(null);
useEffect(() => {
const { body } = document;
const portalRoot = document.createElement(isDialog ? "dialog" : "div");
if (key) {
portalRoot.id = key;
}
if (className) {
portalRoot.className = className;
}
if (lockScroll) {
body.style.overflow = "hidden";
}
body.appendChild(portalRoot);
setPortalRoot(portalRoot);
if (isDialog) {
Object.assign(portalRoot.style, {
width: "100vw",
height: "100vh",
backgroundColor: "transparent",
});
/* @ts-ignore */
portalRoot.showModal?.();
}
return () => {
setPortalRoot(null);
body.removeChild(portalRoot);
if (lockScroll) {
body.style.overflow = "";
}
};
}, [key, className, lockScroll, isDialog]);
return useCallback(
(chlidren: ReactNode) => {
if (!portalRoot) {
return null;
}
if (!createPortal) {
throw new Error("Please ensure to pass the `React.createPortal` in `Router` props");
}
return createPortal(chlidren, portalRoot, key);
},
[portalRoot, createPortal, key],
);
}