diff --git a/packages/dashboard-v2/gatsby-browser.js b/packages/dashboard-v2/gatsby-browser.js
index a71e49c3..a39bdb48 100644
--- a/packages/dashboard-v2/gatsby-browser.js
+++ b/packages/dashboard-v2/gatsby-browser.js
@@ -6,8 +6,14 @@ import "@fontsource/sora/600.css"; // semibold
import "@fontsource/source-sans-pro/400.css"; // normal
import "@fontsource/source-sans-pro/600.css"; // semibold
import "./src/styles/global.css";
+import { MODAL_ROOT_ID } from "./src/components/Modal";
export function wrapPageElement({ element, props }) {
const Layout = element.type.Layout ?? React.Fragment;
- return {element};
+ return (
+
+ {element}
+
+
+ );
}
diff --git a/packages/dashboard-v2/gatsby-ssr.js b/packages/dashboard-v2/gatsby-ssr.js
index a71e49c3..a39bdb48 100644
--- a/packages/dashboard-v2/gatsby-ssr.js
+++ b/packages/dashboard-v2/gatsby-ssr.js
@@ -6,8 +6,14 @@ import "@fontsource/sora/600.css"; // semibold
import "@fontsource/source-sans-pro/400.css"; // normal
import "@fontsource/source-sans-pro/600.css"; // semibold
import "./src/styles/global.css";
+import { MODAL_ROOT_ID } from "./src/components/Modal";
export function wrapPageElement({ element, props }) {
const Layout = element.type.Layout ?? React.Fragment;
- return {element};
+ return (
+
+ {element}
+
+
+ );
}
diff --git a/packages/dashboard-v2/src/components/Modal/Modal.js b/packages/dashboard-v2/src/components/Modal/Modal.js
new file mode 100644
index 00000000..6fd8e00a
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Modal/Modal.js
@@ -0,0 +1,37 @@
+import cn from "classnames";
+import PropTypes from "prop-types";
+
+import { PlusIcon } from "../Icons";
+import { Panel } from "../Panel";
+
+import { ModalPortal } from "./ModalPortal";
+import { Overlay } from "./Overlay";
+
+export const Modal = ({ children, className, onClose }) => (
+
+
+
+
+
+);
+
+Modal.propTypes = {
+ /**
+ * Modal's body.
+ */
+ children: PropTypes.node.isRequired,
+ /**
+ * Handler function to be called when user clicks on the "X" icon,
+ * or outside of the modal.
+ */
+ onClose: PropTypes.func.isRequired,
+ /**
+ * Additional CSS classes to be applied to modal's body.
+ */
+ className: PropTypes.string,
+};
diff --git a/packages/dashboard-v2/src/components/Modal/ModalPortal.js b/packages/dashboard-v2/src/components/Modal/ModalPortal.js
new file mode 100644
index 00000000..ef18e612
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Modal/ModalPortal.js
@@ -0,0 +1,16 @@
+import { useEffect, useRef, useState } from "react";
+import { createPortal } from "react-dom";
+
+export const MODAL_ROOT_ID = "__modal-root";
+
+export const ModalPortal = ({ children }) => {
+ const ref = useRef();
+ const [isClientSide, setIsClientSide] = useState(false);
+
+ useEffect(() => {
+ ref.current = document.querySelector(MODAL_ROOT_ID) || document.body;
+ setIsClientSide(true);
+ }, []);
+
+ return isClientSide ? createPortal(children, ref.current) : null;
+};
diff --git a/packages/dashboard-v2/src/components/Modal/Overlay.js b/packages/dashboard-v2/src/components/Modal/Overlay.js
new file mode 100644
index 00000000..7f1cbb35
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Modal/Overlay.js
@@ -0,0 +1,42 @@
+import { useRef } from "react";
+import { useLockBodyScroll } from "react-use";
+import PropTypes from "prop-types";
+
+export const Overlay = ({ children, onClick }) => {
+ const overlayRef = useRef(null);
+
+ useLockBodyScroll(true);
+
+ const handleClick = (event) => {
+ if (event.target !== overlayRef.current) {
+ return;
+ }
+
+ event.nativeEvent.stopImmediatePropagation();
+
+ onClick?.(event);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+Overlay.propTypes = {
+ /**
+ * Overlay's body.
+ */
+ children: PropTypes.node.isRequired,
+ /**
+ * Handler function to be called when user clicks on the overlay
+ * (but not the overlay's content).
+ */
+ onClick: PropTypes.func,
+};
diff --git a/packages/dashboard-v2/src/components/Modal/index.js b/packages/dashboard-v2/src/components/Modal/index.js
new file mode 100644
index 00000000..28d34710
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Modal/index.js
@@ -0,0 +1 @@
+export * from "./ModalPortal";