From c28e2ec5cb8db4a0a7473ecfde463c292b7eb31b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:06:36 +0100 Subject: [PATCH] fix: keep the names of Reply, Archive, Delete and Move in the reader toolbar while they fit --- app/globals.css | 6 ++-- .../reader-toolbar-labels-first.test.tsx | 18 +++++++++++ components/email/email-viewer.tsx | 30 +++++++++++-------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/globals.css b/app/globals.css index 6ecc0f095..315f59981 100644 --- a/app/globals.css +++ b/app/globals.css @@ -593,8 +593,10 @@ optgroup { animation: toast-exit 0.28s cubic-bezier(0.06, 0.71, 0.55, 1) forwards; } -/* The reader toolbar with no room for its labels: icons only, the names stay in the tooltips. */ -[data-toolbar-compact] [data-toolbar-label] { +/* The reader toolbar with no room for its labels: icons only, the names stay in the tooltips. + "secondary" first keeps the names of Reply, Archive, Delete and Move (mail-p2-15). */ +[data-toolbar-compact="secondary"] [data-toolbar-label]:not([data-toolbar-label="primary"]), +[data-toolbar-compact="all"] [data-toolbar-label] { display: none !important; } diff --git a/components/email/__tests__/reader-toolbar-labels-first.test.tsx b/components/email/__tests__/reader-toolbar-labels-first.test.tsx index ea51036a2..47c8f6d50 100644 --- a/components/email/__tests__/reader-toolbar-labels-first.test.tsx +++ b/components/email/__tests__/reader-toolbar-labels-first.test.tsx @@ -96,6 +96,24 @@ describe('reader toolbar overflow', () => { expect(labels).toEqual(expect.arrayContaining(['reply', 'reply_all', 'forward', 'archive', 'delete', 'move'])); }); + it('keeps the names of Reply, Archive, Delete and Move while only those fit (mail-p2-15)', () => { + // 600 px: every label needs 400 per group, the main four's 280, none 250. + const spies = [ + vi.spyOn(HTMLElement.prototype, 'scrollWidth', 'get').mockImplementation(function (this: HTMLElement) { + const step = this.closest('[data-toolbar-compact]')?.getAttribute('data-toolbar-compact'); + return step === 'all' ? 250 : step === 'secondary' ? 280 : 400; + }), + vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(600), + vi.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(40), + ]; + restore = () => spies.forEach((spy) => spy.mockRestore()); + const { container } = viewer(); + expect(toolbar(container).getAttribute('data-toolbar-compact')).toBe('secondary'); + expect(hiddenPriorities(container)).toEqual([]); + const kept = Array.from(container.querySelectorAll('[data-toolbar-label="primary"]')).map((label) => label.textContent); + expect(kept.sort()).toEqual(['archive', 'delete', 'move', 'reply']); + }); + it('then hides the optional buttons, never Reply, Reply all, Forward, Archive or Move', () => { restore = layout(500, 400); const { container } = viewer(); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 80ffc1c4c..6c2ae86a0 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -231,6 +231,8 @@ const TOOLBAR_PRIORITY_ACTIONS: Record = { const REQUIRED_TOOLBAR_PRIORITIES = [5, 3, 2, 4, 1] as const; /** Set on the toolbar while it shows icons without their labels. */ const TOOLBAR_COMPACT_ATTRIBUTE = 'data-toolbar-compact'; +/** Which labels the reader toolbar has dropped: none, all but the main four, or all. */ +type ToolbarCompact = false | 'secondary' | 'all'; const NO_ACTION_KEYS: ReadonlySet = new Set(); const NO_PLUGIN_KEYS: ReadonlySet = new Set(); @@ -1854,7 +1856,7 @@ export function EmailViewer({ // looked like (see layoutKey), and the width it last had: an open that // brings back a toolbar already measured at this width needs no measuring. // A loading message and a loaded one can differ, so a few are kept. - const measuredToolbarRef = useRef(new Map; compact: boolean }>()); + const measuredToolbarRef = useRef(new Map; compact: ToolbarCompact }>()); const toolbarWidthRef = useRef(undefined); const currentTagIds = getEmailTagIds(email?.keywords); const sortedTagIds = sortTagIds(currentTagIds); @@ -2117,7 +2119,8 @@ export function EmailViewer({ const key = layoutKey(items); const known = toolbarWidthRef.current !== undefined ? measuredToolbarRef.current.get(key) : undefined; if (known) { - el.toggleAttribute(TOOLBAR_COMPACT_ATTRIBUTE, known.compact); + if (known.compact) el.setAttribute(TOOLBAR_COMPACT_ATTRIBUTE, known.compact); + else el.removeAttribute(TOOLBAR_COMPACT_ATTRIBUTE); for (const item of items) item.style.display = known.hidden.has(Number(item.dataset.overflowPriority)) ? 'none' : ''; applyHidden(known.hidden); return; @@ -2135,10 +2138,13 @@ export function EmailViewer({ const containerWidth = el.clientWidth; const overflowNow = () => leftGroup.scrollWidth + rightGroup.scrollWidth + mainGap - (containerWidth + 1); let overflow = overflowNow(); - // The labels go before any button does (icons keep their tooltips). - const compact = overflow > 0; - if (compact) { - el.setAttribute(TOOLBAR_COMPACT_ATTRIBUTE, ''); + // The labels go before any button does (icons keep their tooltips): + // first all but Reply, Archive, Delete and Move, then those too. + let compact: ToolbarCompact = false; + for (const step of ['secondary', 'all'] as const) { + if (overflow <= 0) break; + compact = step; + el.setAttribute(TOOLBAR_COMPACT_ATTRIBUTE, step); overflow = overflowNow(); } const hidden = new Set(); @@ -4477,7 +4483,7 @@ ${printWatermark ? printWatermark.html : ''} title={t('tooltips.reply')} > - {showToolbarLabels && {t('reply')}} + {showToolbarLabels && {t('reply')}} )} {isInTrashFolder && restoreTarget && onMoveToMailbox && ( @@ -4551,7 +4557,7 @@ ${printWatermark ? printWatermark.html : ''} title={tActions('restore_to', { folder: localizeMailboxName(restoreTarget.role, restoreTarget.name, (key) => tSidebarNames(`mailboxes.${key}`)) })} > - {showToolbarLabels && {tActions('restore')}} + {showToolbarLabels && {tActions('restore')}} )} {archiveActionEnabled && !isInJunkFolder && !isInTrashFolder && ( @@ -4565,7 +4571,7 @@ ${printWatermark ? printWatermark.html : ''} title={t('tooltips.archive')} > - {showToolbarLabels && {t('archive')}} + {showToolbarLabels && {t('archive')}} )} {/* Delete */} @@ -4578,7 +4584,7 @@ ${printWatermark ? printWatermark.html : ''} data-testid="reader-delete" > - {showToolbarLabels && {deleteIsPermanent ? tActions('delete_forever') : t('delete')}} + {showToolbarLabels && {deleteIsPermanent ? tActions('delete_forever') : t('delete')}} {/* Move to folder */} {canMoveAnywhere && onMoveToMailbox && ( @@ -4598,7 +4604,7 @@ ${printWatermark ? printWatermark.html : ''} aria-expanded={moveMenuOpen} > - {showToolbarLabels && {t('move')}} + {showToolbarLabels && {t('move')}}