Put "compose as new" where a thumb can find it
It was never absent on a phone: it sits in the menu behind the ⋮ at the top of a message card. But that is not where anybody looks. On a phone you act on a thread from the strip at the bottom -- Reply, Reply all, Forward -- and what is not there is, for practical purposes, not there. The strip gets an overflow of its own, with compose-as-new in it. A fourth labelled button does not fit; this does, and it says what it is once opened. Measuring to place it turned up something else. Three labelled buttons want about 390px, and with the overflow rather more: enough for a 430px phone and not for a 390, 360 or 320 one. The strip was already over that line on the smaller ones before today, and simply overflowed. It now wraps, and the spacer that would push the overflow onto a line of its own is dropped on narrow screens so the buttons wrap as a group. Closes #181
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user