From a5b0842e6a985288a7e1eaf9eb57d1a13392b0d3 Mon Sep 17 00:00:00 2001 From: Lukas Remis Date: Wed, 30 Sep 2026 20:35:29 +0200 Subject: [PATCH] Mail (phone): side panel as a drawer sliding in from the right The reading view's toolbar gains the side-panel toggle on a phone too. It opens Calendar/People/Attachments as a right-hand drawer over a dimmed backdrop, closed by its X, the backdrop or Escape, and put away when the conversation changes. It keeps its own open state so the desktop's remembered panel never opens on a phone, and it mounts on first use only. Co-Authored-By: Claude Opus 5.5 --- frontend/src/routes/MailLayout.tsx | 52 +++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/MailLayout.tsx b/frontend/src/routes/MailLayout.tsx index 9ce048c..bd305b9 100644 --- a/frontend/src/routes/MailLayout.tsx +++ b/frontend/src/routes/MailLayout.tsx @@ -162,6 +162,16 @@ export function MailLayout() { useEffect(() => localStorage.setItem("panel.rightTab", rightTab), [rightTab]); useEffect(() => localStorage.setItem("panel.rightW", String(rightW)), [rightW]); const openPanel = (tab: PanelTab) => { setRightTab(tab); setRightOpen(true); }; + // Phone: the same panel as a drawer sliding in from the right. Its own state, + // so the desktop's remembered "open" never springs it onto a phone screen. + // Mounted on first use only (the Calendar tab loads a whole plugin bundle), + // then kept so it can slide out again. + const [sheetOpen, setSheetOpen] = useState(false); + const [sheetUsed, setSheetUsed] = useState(false); + const openSheet = () => { + setSheetUsed(true); + setSheetOpen(true); + }; useEffect(() => localStorage.setItem("mail.listOrder", listOrder), [listOrder]); const isDrafts = view === "folder:drafts"; @@ -309,6 +319,14 @@ export function MailLayout() { window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [drawerOpen]); + // The side-panel drawer belongs to the conversation it was opened over. + useEffect(() => setSheetOpen(false), [view, mobile, threadId]); + useEffect(() => { + if (!sheetOpen) return; + const onKey = (e: KeyboardEvent) => e.key === "Escape" && setSheetOpen(false); + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [sheetOpen]); // Auto-mark an opened thread read after a short dwell (4s), not instantly — and // cancel it if you leave before then, so a quick glance doesn't mark it read. @@ -1230,7 +1248,20 @@ export function MailLayout() { {t("mail.messageCount", { count: detailQuery.data.messages.length })} - {!mobile && ( + {mobile ? ( + + ) : (