diff --git a/ui/src/Components/MainModal/index.test.js b/ui/src/Components/MainModal/index.test.js
index 3f007c55e..4c117bd09 100644
--- a/ui/src/Components/MainModal/index.test.js
+++ b/ui/src/Components/MainModal/index.test.js
@@ -34,6 +34,7 @@ beforeEach(() => {
afterEach(() => {
jest.restoreAllMocks();
fetchMock.reset();
+ document.body.className = "";
});
const MountedMainModal = () => {
@@ -111,8 +112,12 @@ describe("", () => {
it("'modal-open' class is removed from body node after modal is hidden", () => {
const tree = MountedMainModal();
const toggle = tree.find(".nav-link");
+
toggle.simulate("click");
+ expect(document.body.className.split(" ")).toContain("modal-open");
+
toggle.simulate("click");
+ act(() => jest.runOnlyPendingTimers());
expect(document.body.className.split(" ")).not.toContain("modal-open");
});
diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.test.js b/ui/src/Components/ManagedSilence/DeleteSilence.test.js
index 983ced60a..937b11a80 100644
--- a/ui/src/Components/ManagedSilence/DeleteSilence.test.js
+++ b/ui/src/Components/ManagedSilence/DeleteSilence.test.js
@@ -52,6 +52,7 @@ afterEach(() => {
useFetchGet.mockReset();
useFetchDelete.mockReset();
clear();
+ document.body.className = "";
});
const MockOnHide = jest.fn();
diff --git a/ui/src/Components/Modal/index.js b/ui/src/Components/Modal/index.js
index 0f9af1893..6e8ff5b71 100644
--- a/ui/src/Components/Modal/index.js
+++ b/ui/src/Components/Modal/index.js
@@ -1,10 +1,7 @@
-import React, { Component } from "react";
+import React, { useRef, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
-import { observer } from "mobx-react";
-import { observable } from "mobx";
-
import { disableBodyScroll, clearAllBodyScrollLocks } from "body-scroll-lock";
import { HotKeys } from "react-hotkeys";
@@ -14,105 +11,76 @@ import {
MountModalBackdrop,
} from "Components/Animations/MountModal";
-const Modal = observer(
- class Modal extends Component {
- static propTypes = {
- size: PropTypes.oneOf(["lg", "xl"]),
- isOpen: PropTypes.bool.isRequired,
- isUpper: PropTypes.bool,
- toggleOpen: PropTypes.func.isRequired,
- children: PropTypes.node.isRequired,
+const ModalInner = ({ size, isUpper, toggleOpen, children }) => {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ document.body.classList.add("modal-open");
+ disableBodyScroll(ref.current);
+ return () => {
+ document.body.classList.remove("modal-open");
+ clearAllBodyScrollLocks();
};
- static defaultProps = {
- size: "lg",
- isUpper: false,
+ }, []);
+
+ const onRemount = useCallback(() => {
+ document.body.classList.add("modal-open");
+ disableBodyScroll(ref.current);
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("remountModal", onRemount);
+ return () => {
+ window.removeEventListener("remountModal", onRemount);
};
+ }, [onRemount]);
- constructor(props) {
- super(props);
- this.modalRef = React.createRef();
- this.HotKeysRef = React.createRef();
- this.lastIsOpen = observable.box(false);
- }
+ return (
+ r && r.focus()}
+ keyMap={{ CLOSE: "Escape" }}
+ handlers={{ CLOSE: toggleOpen }}
+ className="modal-open"
+ >
+
+
+ );
+};
- toggleBodyClass = (isOpen) => {
- document.body.classList.toggle("modal-open", isOpen);
- if (isOpen) {
- this.HotKeysRef.current.focus();
- disableBodyScroll(this.modalRef.current);
- } else {
- clearAllBodyScrollLocks();
- }
- this.lastIsOpen.set(isOpen);
- };
-
- componentDidMount() {
- const { isOpen } = this.props;
- if (isOpen) {
- this.toggleBodyClass(isOpen);
- }
- }
-
- componentDidUpdate() {
- const { isOpen } = this.props;
- // we shouldn't update if modal is hidden and was hidden previously
- // which can happen when the button gets re-rendered
- if (this.lastIsOpen.get() === true || isOpen === true) {
- this.toggleBodyClass(isOpen);
- }
- }
-
- componentWillUnmount() {
- const { isOpen } = this.props;
-
- if (isOpen) {
- this.toggleBodyClass(false);
- }
- }
-
- render() {
- const {
- size,
- isOpen,
- isUpper,
- toggleOpen,
- children,
- ...props
- } = this.props;
-
- return ReactDOM.createPortal(
-
-
-
-
-
-
-
-
-
- ,
- document.body
- );
- }
- }
-);
+const Modal = ({ size, isOpen, isUpper, toggleOpen, children, ...props }) => {
+ return ReactDOM.createPortal(
+
+
+
+ {children}
+
+
+
+
+
+ ,
+ document.body
+ );
+};
+Modal.propTypes = {
+ size: PropTypes.oneOf(["lg", "xl"]),
+ isOpen: PropTypes.bool.isRequired,
+ isUpper: PropTypes.bool,
+ toggleOpen: PropTypes.func.isRequired,
+ children: PropTypes.node.isRequired,
+};
+Modal.defaultProps = {
+ size: "lg",
+ isUpper: false,
+};
export { Modal };
diff --git a/ui/src/Components/Modal/index.test.js b/ui/src/Components/Modal/index.test.js
index e7a560848..828e46285 100644
--- a/ui/src/Components/Modal/index.test.js
+++ b/ui/src/Components/Modal/index.test.js
@@ -16,15 +16,22 @@ const MountedModal = (isOpen) => {
afterEach(() => {
jest.resetAllMocks();
+ document.body.className = "";
});
describe("", () => {
+ it("'modal-open' class is appended to MountModal container", () => {
+ const tree = MountedModal(true);
+ expect(tree.find("div").at(0).hasClass("modal-open")).toBe(true);
+ });
+
it("'modal-open' class is appended to body node when modal is visible", () => {
MountedModal(true);
expect(document.body.className.split(" ")).toContain("modal-open");
});
it("'modal-open' class is not removed from body node after hidden modal is unmounted", () => {
+ document.body.classList.add("modal-open");
const tree = MountedModal(false);
tree.unmount();
expect(document.body.className.split(" ")).toContain("modal-open");
@@ -51,9 +58,7 @@ describe("", () => {
const tree = MountedModal(isOpen);
expect(document.body.className.split(" ")).toContain("modal-open");
- isOpen = false;
- // force update
- tree.setProps({ style: {} });
+ tree.setProps({ isOpen: false });
expect(document.body.className.split(" ")).toContain("modal-open");
});
@@ -70,7 +75,10 @@ describe("", () => {
it("toggleOpen is called after pressing 'esc'", () => {
const tree = MountedModal(true);
- tree.simulate("keyDown", { key: "Escape", keyCode: 27, which: 27 });
+ tree
+ .find("div")
+ .at(0)
+ .simulate("keyDown", { key: "Escape", keyCode: 27, which: 27 });
expect(fakeToggle).toHaveBeenCalled();
});
});
diff --git a/ui/src/Components/OverviewModal/index.test.js b/ui/src/Components/OverviewModal/index.test.js
index 18c250f8f..71ba77845 100644
--- a/ui/src/Components/OverviewModal/index.test.js
+++ b/ui/src/Components/OverviewModal/index.test.js
@@ -16,6 +16,10 @@ beforeEach(() => {
alertStore = new AlertStore([]);
});
+afterEach(() => {
+ document.body.className = "";
+});
+
const MountedOverviewModal = () => {
return mount();
};
@@ -80,9 +84,12 @@ describe("", () => {
it("'modal-open' class is removed from body node after modal is hidden", () => {
const tree = MountedOverviewModal();
- const toggle = tree.find("div.navbar-brand");
- toggle.simulate("click");
- toggle.simulate("click");
+
+ tree.find("div.navbar-brand").simulate("click");
+ expect(document.body.className.split(" ")).toContain("modal-open");
+
+ tree.find("div.navbar-brand").simulate("click");
+ act(() => jest.runOnlyPendingTimers());
expect(document.body.className.split(" ")).not.toContain("modal-open");
});
diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.js
index 05c581fd7..3129dc00b 100644
--- a/ui/src/Components/SilenceModal/index.js
+++ b/ui/src/Components/SilenceModal/index.js
@@ -1,4 +1,4 @@
-import React, { useRef } from "react";
+import React from "react";
import PropTypes from "prop-types";
import { observer } from "mobx-react";
@@ -22,10 +22,9 @@ const SilenceModalContent = React.lazy(() =>
const SilenceModal = observer(
({ alertStore, silenceFormStore, settingsStore }) => {
- const modalRef = useRef();
-
const onDeleteModalClose = React.useCallback(() => {
- modalRef.current.toggleBodyClass(true);
+ const event = new CustomEvent("remountModal");
+ window.dispatchEvent(event);
}, []);
return (
@@ -46,7 +45,6 @@ const SilenceModal = observer(
{
silenceFormStore = new SilenceFormStore();
});
+afterEach(() => {
+ document.body.className = "";
+});
+
const MountedSilenceModal = () => {
return mount(
", () => {
it("'modal-open' class is removed from body node after modal is hidden", () => {
const tree = MountedSilenceModal();
const toggle = tree.find(".nav-link");
+
toggle.simulate("click");
+ expect(document.body.className.split(" ")).toContain("modal-open");
+
toggle.simulate("click");
+ act(() => jest.runOnlyPendingTimers());
expect(document.body.className.split(" ")).not.toContain("modal-open");
});