From 6559b34135a1ef35a2fec12bbf6aba271cafd4ca Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 28 Oct 2026 19:53:33 +0100 Subject: [PATCH] fix: keep Mountain View's row actions, checkbox and star on the subject line when attachment chips make the row taller --- .../email-hover-actions-placement.test.tsx | 39 +++++++++++++++++++ components/email/email-hover-actions.tsx | 8 +++- components/email/thread-list-item.tsx | 10 +++-- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 components/email/__tests__/email-hover-actions-placement.test.tsx diff --git a/components/email/__tests__/email-hover-actions-placement.test.tsx b/components/email/__tests__/email-hover-actions-placement.test.tsx new file mode 100644 index 000000000..6a7b0ba13 --- /dev/null +++ b/components/email/__tests__/email-hover-actions-placement.test.tsx @@ -0,0 +1,39 @@ +import { render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Email } from '@/lib/jmap/types'; +import { useSettingsStore } from '@/stores/settings-store'; +import { EmailHoverActions } from '../email-hover-actions'; + +vi.mock('@/components/plugins/plugin-actions', () => ({ + PluginActions: () => null, + usePluginActions: () => [], + useMessageActionTarget: () => ({ accountKind: 'personal', keywords: [] }), +})); + +// Gmail keeps a row's actions on its first line when attachment chips make the +// row taller; ours sat centred between the subject and the chips. + +const email = { id: 'e1', keywords: { $seen: true }, mailboxIds: { inbox: true } } as unknown as Email; + +function toolbar(pinToFirstLine?: boolean) { + render(); + return screen.getByTestId('hover-action-delete').closest('.absolute') as HTMLElement; +} + +beforeEach(() => { + useSettingsStore.setState({ hoverActions: ['delete'], interfaceLayout: 'mountain-view', hoverActionsMode: 'inline' } as never); +}); + +describe('row quick action placement', () => { + it('stays on the first line of a Mountain View row', () => { + const el = toolbar(true); + expect(el.className).toContain('h-10'); + expect(el.className).not.toContain('bottom-0'); + }); + + it('spans the whole row otherwise', () => { + const el = toolbar(); + expect(el.className).toContain('bottom-0'); + expect(el.className).not.toContain('h-10'); + }); +}); diff --git a/components/email/email-hover-actions.tsx b/components/email/email-hover-actions.tsx index 683b9143e..7aef1a4a4 100644 --- a/components/email/email-hover-actions.tsx +++ b/components/email/email-hover-actions.tsx @@ -32,6 +32,11 @@ interface EmailHoverActionsProps { spamApplicable?: boolean; /** Role of the folder the row is listed in; plugin buttons can be limited to some. */ mailboxRole?: string | null; + /** + * Keep the actions on the row's first 40px line rather than centred on the + * whole row, as Gmail does when attachment chips make the row taller. + */ + pinToFirstLine?: boolean; } const ACTION_CONFIG: Record state.hoverActions); @@ -337,7 +343,7 @@ export function EmailHoverActions({ return (
: undefined; // The sender column is w-32 (lg:w-40) and the row line has gap-3 after it. - const className = cn(variant === "mountain-view" ? "mt-2" : "mt-1.5", mvRow && "mt-1 pb-2.5 ps-[8.75rem] lg:ps-[10.75rem]"); + const className = cn(variant === "mountain-view" ? "mt-2" : "mt-1.5", mvRow && "-mt-0.5 pb-2.5 ps-[8.75rem] lg:ps-[10.75rem]"); // Most rows have no attachment: no loader for them, nor an empty chip row. const mayHaveAttachments = email.hasAttachment || (email.attachments?.length ?? 0) > 0; if (onOpenAttachment && mayHaveAttachments) { @@ -155,7 +155,7 @@ function MvRowLead({ starLabel: string; }) { return ( -
+