Merge pull request #184 from Coffey-Labs/fix/compose-as-new-on-mobile

Put "compose as new" where a thumb can find it
This commit is contained in:
Coffey Labs
2026-09-01 10:56:32 -07:00
committed by GitHub
2 changed files with 23 additions and 2 deletions
+7 -1
View File
@@ -642,7 +642,10 @@ a.menu-item:hover { color: var(--fg); }
.vcard-card { margin: 0 16px 12px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-sunken); display: flex; align-items: center; gap: 12px; } .vcard-card { margin: 0 16px 12px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-sunken); display: flex; align-items: center; gap: 12px; }
.unsubscribe-row { margin: 0 16px 8px; font-size: .88em; color: var(--fg-muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .unsubscribe-row { margin: 0 16px 8px; font-size: .88em; color: var(--fg-muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.reply-box { margin: 8px 16px 24px; } .reply-box { margin: 8px 16px 24px; }
.reply-box .reply-prompt { display: flex; gap: 8px; align-items: center; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--fg-muted); } /* Wraps, because it does not fit. Three labelled buttons and an overflow need
about 390px of it, which a 430px phone has and a 360px one does not -- and it
was already over the line on the smaller ones before the overflow was added. */
.reply-box .reply-prompt { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--fg-muted); }
.reply-box .reply-prompt button { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border-strong); color: var(--fg); font-weight: 500; } .reply-box .reply-prompt button { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border-strong); color: var(--fg); font-weight: 500; }
.reply-box .reply-prompt button:hover { background: var(--bg-hover); } .reply-box .reply-prompt button:hover { background: var(--bg-hover); }
.no-thread { height: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: var(--fg-muted); } .no-thread { height: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: var(--fg-muted); }
@@ -1214,6 +1217,9 @@ button.dp-open:disabled { cursor: default; opacity: .5; }
.dp-time:hover { background: var(--bg-hover); } .dp-time:hover { background: var(--bg-hover); }
.dp-time.selected { background: var(--accent); color: var(--accent-fg); } .dp-time.selected { background: var(--accent); color: var(--accent-fg); }
@media (max-width: 480px) { @media (max-width: 480px) {
/* The spacer would take the whole of the first line and push the overflow
onto a line of its own; packed together they wrap as a group instead. */
.reply-box .reply-prompt .spacer { display: none; }
.dp-datetime { flex-wrap: wrap; } .dp-datetime { flex-wrap: wrap; }
.dp-datetime .dp-time-field { flex: 1 1 100%; } .dp-datetime .dp-time-field { flex: 1 1 100%; }
.dp-split { flex-direction: column; } .dp-split { flex-direction: column; }
+16 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { AlertOctagon, Archive, ArrowLeft, ChevronDown, ChevronUp, FolderInput, Forward, Mail, MailOpen, MoreVertical, Printer, Reply, ReplyAll, ShieldCheck, Star, Tag, Trash2, Download } from "lucide-react"; import { AlertOctagon, Archive, ArrowLeft, ChevronDown, ChevronUp, FolderInput, Forward, Mail, MailOpen, MailPlus, MoreVertical, Printer, Reply, ReplyAll, ShieldCheck, Star, Tag, Trash2, Download } from "lucide-react";
import { useMail } from "@/store/mail"; import { useMail } from "@/store/mail";
import { useSettings } from "@/store/settings"; import { useSettings } from "@/store/settings";
import { useCompose } from "@/store/compose"; import { useCompose } from "@/store/compose";
@@ -42,6 +42,8 @@ export function ThreadView({ threadId, mailboxId, onBack, actions, onNavigate, h
const [allExpanded, setAllExpanded] = useState(false); const [allExpanded, setAllExpanded] = useState(false);
const [labelAnchor, setLabelAnchor] = useState<{ x: number; y: number } | null>(null); const [labelAnchor, setLabelAnchor] = useState<{ x: number; y: number } | null>(null);
const moreMenu = useMenu(); const moreMenu = useMenu();
/** The overflow on the reply strip, which is the only per-message menu a phone offers easily. */
const replyMore = useMenu();
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const markTimer = useRef<number | null>(null); const markTimer = useRef<number | null>(null);
const isTouch = useIsTouch(); const isTouch = useIsTouch();
@@ -289,6 +291,19 @@ export function ThreadView({ threadId, mailboxId, onBack, actions, onNavigate, h
<button onClick={() => void reply(last, "reply")}><Reply size={16} /> {t("Reply")}</button> <button onClick={() => void reply(last, "reply")}><Reply size={16} /> {t("Reply")}</button>
<button onClick={() => void reply(last, "replyAll")}><ReplyAll size={16} /> {t("Reply all")}</button> <button onClick={() => void reply(last, "replyAll")}><ReplyAll size={16} /> {t("Reply all")}</button>
<button onClick={() => void reply(last, "forward")}><Forward size={16} /> {t("Forward")}</button> <button onClick={() => void reply(last, "forward")}><Forward size={16} /> {t("Forward")}</button>
{/*
On a phone this strip is where a thumb goes, and the per-message
menu at the top of a card is not somewhere anybody looks for
"send this again" -- which is how compose-as-new came to be
reported missing on mobile when it was there all along (#181).
A fourth full button does not fit at 500px; this does, and it
spells the action out once opened.
*/}
<span className="spacer" />
<button className="icon-btn" onClick={replyMore.open} aria-label={t("More ways to send this")}><MoreVertical size={18} /></button>
<Popover anchor={replyMore.anchor} onClose={replyMore.close} align="end" width={220}>
<MenuItem icon={<MailPlus size={16} />} label={t("Compose as new")} onClick={() => void useCompose.getState().composeAsNew(last)} />
</Popover>
</div> </div>
</div> </div>
)} )}