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 ? ( + + ) : (