diff --git a/src/renderer/components/modal/modal.tsx b/src/renderer/components/modal/modal.tsx index 35be1bf9..cc75db26 100644 --- a/src/renderer/components/modal/modal.tsx +++ b/src/renderer/components/modal/modal.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useId, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { XIcon } from "@primer/octicons-react"; @@ -23,6 +23,8 @@ export function Modal({ }: ModalProps) { const [isClosing, setIsClosing] = useState(false); const dispatch = useAppDispatch(); + const modalContentRef = useRef(null); + const componentId = useId(); const handleCloseClick = () => { setIsClosing(true); @@ -38,6 +40,42 @@ export function Modal({ }); }; + const isTopMostModal = () => { + const openModals = document.querySelectorAll("[role=modal]"); + return ( + openModals.length && + openModals[openModals.length - 1] === modalContentRef.current + ); + }; + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && isTopMostModal()) { + handleCloseClick(); + } + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, []); + + useEffect(() => { + const onMouseDown = (e: MouseEvent) => { + if (!isTopMostModal()) return; + + const clickedOutsideContent = !modalContentRef.current.contains( + e.target as Node + ); + + if (clickedOutsideContent) { + handleCloseClick(); + } + }; + + window.addEventListener("mousedown", onMouseDown); + return () => window.removeEventListener("mousedown", onMouseDown); + }, []); + useEffect(() => { dispatch(toggleDragging(visible)); }, [dispatch, visible]); @@ -46,7 +84,11 @@ export function Modal({ return createPortal(
-
+

{title}