feat(dashboard-v2): add Modal component

This commit is contained in:
Michał Leszczyk 2022-03-24 10:39:18 +01:00
parent 340fe5f203
commit d27ef442f4
No known key found for this signature in database
GPG Key ID: FA123CA8BAA2FBF4
6 changed files with 110 additions and 2 deletions

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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,
};

View File

@ -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;
};

View File

@ -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,
};

View File

@ -0,0 +1 @@
export * from "./ModalPortal";