diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 5a10f90..533f100 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import { @@ -37,6 +37,7 @@ export default function NavSidebar({ ); const [acctOpen, setAcctOpen] = useState(false); const acctBtnRef = useRef(null); + const acctPanelRef = useRef(null); // Popover position (fixed, viewport coords). It's portaled to so it escapes the // nav's backdrop-filter, which would otherwise trap fixed positioning + stacking and let // clicks fall through to the controls behind it. @@ -50,6 +51,27 @@ export default function NavSidebar({ setAcctOpen((o) => !o); } + // Dismiss on outside click / Escape via document listeners — NOT a full-screen backdrop + // div, which (sitting between the popover and the page) would break the popover's + // backdrop-filter and make it look solid. + useEffect(() => { + if (!acctOpen) return; + function onDoc(e: MouseEvent) { + const t = e.target as Node; + if (acctPanelRef.current?.contains(t) || acctBtnRef.current?.contains(t)) return; + setAcctOpen(false); + } + function onKey(e: KeyboardEvent) { + if (e.key === "Escape") setAcctOpen(false); + } + document.addEventListener("mousedown", onDoc); + document.addEventListener("keydown", onKey); + return () => { + document.removeEventListener("mousedown", onDoc); + document.removeEventListener("keydown", onKey); + }; + }, [acctOpen]); + function toggleCollapsed() { setCollapsed((c) => { const next = !c; @@ -157,12 +179,11 @@ export default function NavSidebar({ {acctOpen && createPortal( - <> -
setAcctOpen(false)} /> -
+
-
- , +
, document.body )}