diff --git a/apps/app/src/components/qr-modal/index.test.tsx b/apps/app/src/components/qr-modal/index.test.tsx
new file mode 100644
index 0000000..0a43114
--- /dev/null
+++ b/apps/app/src/components/qr-modal/index.test.tsx
@@ -0,0 +1,32 @@
+import { afterEach, expect, it, vi } from "vitest";
+import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom/vitest";
+import { QrModal } from "./index";
+afterEach(() => { cleanup(); vi.unstubAllGlobals(); });
+function renderDialog() {
+ vi.stubGlobal("fetch", vi.fn(() => Promise.resolve(new Response("{}"))));
+ const onClose = vi.fn();
+ const view = render();
+ const heading = screen.getByText("Scan to Pick a Visualization");
+ const panel = heading.parentElement!.parentElement!;
+ const foreground = panel.parentElement!;
+ return { ...view, panel, foreground, onClose };
+}
+it("dismisses when clicking the foreground outside the panel", () => {
+ const { foreground, onClose } = renderDialog();
+ fireEvent.click(foreground);
+ expect(onClose).toHaveBeenCalledExactlyOnceWith();
+});
+it("keeps clicks inside the panel open", () => {
+ const { panel, onClose } = renderDialog();
+ fireEvent.click(panel);
+ expect(onClose).not.toHaveBeenCalled();
+});
+it("dismisses with Escape and removes the listener when closed", () => {
+ const { rerender, onClose } = renderDialog();
+ fireEvent.keyDown(document, { key: "Escape" });
+ expect(onClose).toHaveBeenCalledExactlyOnceWith();
+ rerender();
+ fireEvent.keyDown(document, { key: "Escape" });
+ expect(onClose).toHaveBeenCalledTimes(1);
+});
diff --git a/apps/app/src/components/qr-modal/index.tsx b/apps/app/src/components/qr-modal/index.tsx
index 756f037..37467a3 100644
--- a/apps/app/src/components/qr-modal/index.tsx
+++ b/apps/app/src/components/qr-modal/index.tsx
@@ -47,6 +47,15 @@ export function QrModal({
}) {
const pickUrl = usePickUrl(sessionId);
+ useEffect(() => {
+ if (!isOpen) return;
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") onClose();
+ };
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [isOpen, onClose]);
+
if (!isOpen) return null;
return (
@@ -65,6 +74,9 @@ export function QrModal({
{/* Modal */}
{
+ if (event.target === event.currentTarget) onClose();
+ }}
style={{
position: "fixed",
inset: 0,