<Modal />

Add dialogs to your web application for lightboxes, user notifications, or completely custom content.

Component

1import React, { Fragment, useEffect, useLayoutEffect, useState } from "react";
2import ReactDOM from "react-dom";
3import "./Styles/_modal.scss";
4
5export default function Modal({ children, ...props }) {
6 const { show, callback } = props;
7 useLockedBody(show);
8
9 return ReactDOM.createPortal(
10 <Fragment>
11 {show && <div className="Backdrop" onClick={callback} />}
12 <div className={`Modal ${show ? "active" : ""}`}>
13 <div className="Modal__Close" onClick={callback}>
14 Close
15 </div>
16 <div className="Modal__Content">{children}</div>
17 </div>
18 </Fragment>,
19 document.getElementById("portals")
20 );
21}
22
23Modal.defaultProps = {
24 show: false,
25 callback: () => { },
26};
27
28export { ModalBody } from "./Components/ModalBody";
29export { ModalHeader } from "./Components/ModalHeader";
30export { ModalFooter } from "./Components/ModalFooter";
31
32// Hooks For Adding Functionalities.
33const useLockedBody = (initialLocked = false) => {
34 const [locked, setLocked] = useState(initialLocked);
35
36 // Do the side effect before render
37 useLayoutEffect(() => {
38 if (!locked) {
39 return;
40 }
41
42 // Save initial body style
43 const originalOverflow = document.body.style.overflow;
44 const originalPaddingRight = document.body.style.paddingRight;
45
46 // Lock body scroll
47 document.body.style.overflow = "hidden";
48
49 // Get the scrollBar width
50 const root = document.getElementById("root"); // or root
51 const scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0;
52
53 // Avoid width reflow
54 if (scrollBarWidth) {
55 document.body.style.paddingRight = `${scrollBarWidth}px`;
56 }
57
58 return () => {
59 document.body.style.overflow = originalOverflow;
60
61 if (scrollBarWidth) {
62 document.body.style.paddingRight = originalPaddingRight;
63 }
64 };
65 }, [locked]);
66
67 // Update state if initialValue changes
68 useEffect(() => {
69 if (locked !== initialLocked) {
70 setLocked(initialLocked);
71 }
72 // eslint-disable-next-line react-hooks/exhaustive-deps
73 }, [initialLocked]);
74
75 return [locked, setLocked];
76};

Styles

1$properties: (
2 breakpoints: (
3 small: 40em,
4 ),
5 modal: (
6 height: 80vh,
7 maxHeight: 85vh,
8 ),
9 backdrop: (
10 bgclr: hsla(0, 0%, 96%, 0.502),
11 ),
12);
13
14.Modal {
15 position: fixed;
16 display: none;
17 background-color: white;
18 z-index: 600;
19 top: 50%;
20 left: 50%;
21 -webkit-transform: translate(-50%, -50%);
22 -ms-transform: translate(-50%, -50%);
23 transform: translate(-50%, -50%);
24 width: 350px;
25 -webkit-transition: all 0.3s ease-out;
26 -o-transition: all 0.3s ease-out;
27 transition: all 0.3s ease-out;
28 padding-top: 1.5rem;
29 -webkit-box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
30 rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
31 box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
32 rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
33 height: map-get($properties, modal, height);
34 max-height: map-get($properties, modal, maxHeight);
35
36 &.active {
37 display: block;
38 }
39
40 &__Content {
41 display: -ms-grid;
42 display: grid;
43 -ms-grid-rows: auto;
44 grid-template-rows: auto;
45 padding: 0 0.5rem;
46 height: 100%;
47 overflow: scroll;
48 }
49
50 &-Header {
51 }
52
53 &-Body {
54 }
55
56 &-Footer {
57 }
58
59 &__Close {
60 position: absolute;
61 top: 0;
62 right: 5px;
63 cursor: none;
64 color: red;
65
66 @media only screen and (min-width: map-get($properties, breakpoints, small)) {
67 cursor: pointer;
68 }
69 }
70
71 @media only screen and (min-width: map-get($properties, breakpoints, small)) {
72 width: auto;
73 }
74}
75
76.Backdrop {
77 position: fixed;
78 top: 0;
79 left: 0;
80 bottom: 0;
81 right: 0;
82 z-index: 500;
83 background-color: map-get($properties, backdrop, bgclr);
84}

Usage

1