feat(dashboard-v2): add Modal component
This commit is contained in:
parent
340fe5f203
commit
d27ef442f4
|
@ -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 <Layout {...props}>{element}</Layout>;
|
||||
return (
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 <Layout {...props}>{element}</Layout>;
|
||||
return (
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 }) => (
|
||||
<ModalPortal>
|
||||
<Overlay onClick={onClose}>
|
||||
<div className="relative">
|
||||
<button onClick={onClose} className="absolute top-[20px] right-[20px]">
|
||||
<PlusIcon size={14} className="rotate-45" />
|
||||
</button>
|
||||
<Panel className={cn("px-8 py-6 sm:px-12 sm:py-10", className)}>{children}</Panel>
|
||||
</div>
|
||||
</Overlay>
|
||||
</ModalPortal>
|
||||
);
|
||||
|
||||
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,
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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 (
|
||||
<div
|
||||
ref={overlayRef}
|
||||
role="presentation"
|
||||
onClick={handleClick}
|
||||
className="fixed inset-0 z-50 bg-palette-100/80 flex items-center justify-center"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./ModalPortal";
|
Reference in New Issue