Mail: a smartphone layout

Below 768px (or a touch screen under 500px tall) the mail view becomes one
column at a time: the list full screen, with a menu button beside the search
box that slides the sidebar in as a 70%-wide drawer (tap outside, pick a
folder or press Esc to close), and a floating compose button. Opening a
conversation or a draft shows the reading pane full screen with a back
arrow; composers, including reply/reply-all/forward, open full screen.

List rows stack sender/date, subject and snippet; tapping an avatar selects
a row and the bulk actions sit above the list. The app switcher, Settings
link, select-all box, resizers and right panel are hidden on a phone, and
the composer header drops the Attach label and pop-out at phone width.

Everything is gated on useIsMobile(); the desktop layout renders as before
(checked by screenshot comparison at 1440x900).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 08:09:09 +02:00
co-authored by Claude Opus 5.5
parent cfc89e78b3
commit fe3fd94165
7 changed files with 299 additions and 63 deletions
+16 -5
View File
@@ -4,6 +4,7 @@ import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { useT } from "../../i18n";
import { api } from "../../lib/api";
import { printMessages } from "../../lib/print";
import { useIsMobile } from "../../lib/useIsMobile";
import { getDateLocale, getHour12 } from "../../lib/timeFormat";
import type { AccountOut, MailboxOut, MessageEnvelope } from "../../lib/types";
import { PluginSlot } from "../../plugins/PluginSlot";
@@ -684,16 +685,26 @@ export function ThreadView({
);
const composerRef = useRef<HTMLDivElement | null>(null);
// On a phone, reply/reply-all/forward open full screen rather than inline.
const mobile = useIsMobile();
// Bring the inline composer into view when it opens (reply/reply-all/forward).
useEffect(() => {
if (composing) composerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
}, [composing]);
if (composing && !mobile) {
composerRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
}
}, [composing, mobile]);
const composerFor = (id: string) =>
composing?.anchorId === id ? (
<div ref={composerRef} className="px-4 py-3">
<Composer init={composing.init} onClose={() => setComposing(null)} />
</div>
mobile ? (
<div className="fixed inset-0 z-50 bg-app-elevated pb-[env(safe-area-inset-bottom)] pt-[env(safe-area-inset-top)]">
<Composer init={composing.init} onClose={() => setComposing(null)} fullWindow />
</div>
) : (
<div ref={composerRef} className="px-4 py-3">
<Composer init={composing.init} onClose={() => setComposing(null)} />
</div>
)
) : null;
return (