fix: use the same icons for the same actions in Mail: Refresh, Back, Edit, Compose, More and the amber star

This commit is contained in:
Linus Rath
2026-10-28 05:48:50 +01:00
parent c736ab1f3c
commit e5ef5ee9bf
6 changed files with 44 additions and 26 deletions
@@ -36,3 +36,20 @@ describe('Mail in right-to-left languages', () => {
expect(source.match(/<bdi>\{subjectSnippet/g)).toHaveLength(4);
});
});
// R2: one icon per action. Edit is Pencil, Compose PenSquare, Refresh
// RefreshCw (RotateCcw means reset or undo), the star amber.
describe('Mail icons', () => {
const iconFiles = [
'components/mail/mail-app.tsx',
'components/email/email-viewer.tsx',
'components/email/email-context-menu.tsx',
'components/email/mv-list-toolbar.tsx',
'components/email/reader-plugin-surfaces.tsx',
];
it.each(iconFiles)('%s uses the agreed icons', (file) => {
const code = read(file);
expect(code).not.toMatch(/\bEditIcon\b|\bPenLine\b|yellow-400|icon=\{RotateCcw\}|<MoreHorizontal/);
expect(code).not.toMatch(/<RotateCcw className=\{cn\("w-4 h-4", isManualRefreshing/);
});
});
+3 -3
View File
@@ -30,7 +30,6 @@ import {
Tag,
ShieldAlert,
ShieldCheck,
EditIcon,
CalendarClock,
CalendarX,
Paperclip,
@@ -38,6 +37,7 @@ import {
MessagesSquare,
Copy,
Search,
Pencil,
} from "@/components/icons";
import { buildMailPath } from "@/lib/deep-links";
import { useCopyLink } from "@/hooks/use-copy-link";
@@ -552,7 +552,7 @@ export function EmailContextMenu({
disabled={!onRescheduleScheduled}
/>
<ContextMenuItem
icon={EditIcon}
icon={Pencil}
label={email.isSmimeScheduled ? t("cancel_and_compose_again") : t("cancel_and_edit")}
onClick={() => handleAction(onCancelScheduledForEdit!)}
disabled={!onCancelScheduledForEdit}
@@ -595,7 +595,7 @@ export function EmailContextMenu({
{!isScheduled && !showBatchActions && isDraft && onEditDraft && (
<>
<ContextMenuItem
icon={EditIcon}
icon={Pencil}
label={t("edit_draft")}
onClick={() => handleAction(onEditDraft)}
/>
+16 -15
View File
@@ -91,7 +91,6 @@ import {
Sun,
Upload,
Moon,
EditIcon,
PlayCircle,
PenSquare,
CalendarClock,
@@ -100,6 +99,8 @@ import {
Maximize2,
Minimize2,
Filter,
ArrowLeft,
Pencil,
} from "@/components/icons";
import { useTranslations } from "next-intl";
import { useRouter } from "@/i18n/navigation";
@@ -547,7 +548,7 @@ export function ContactSidebarPanel({
className="flex-1 min-w-0 gap-2"
title={t('contact_sidebar.action_edit_title')}
>
<EditIcon className="w-4 h-4 shrink-0" />
<Pencil className="w-4 h-4 shrink-0" />
<span className="truncate">{tCommon('edit')}</span>
</Button>
)}
@@ -3807,7 +3808,7 @@ export function EmailViewer({
className="h-9 w-9 flex-shrink-0 -ms-1"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
<ArrowLeft className="w-5 h-5" />
</Button>
)}
{showBackButton && mvMobileViewer && (
@@ -3861,7 +3862,7 @@ export function EmailViewer({
{showToolbarLabels && <span className="hidden sm:inline text-sm">{t('reschedule_send')}</span>}
</Button>
<Button variant="ghost" size="sm" onClick={onCancelScheduledForEdit} className="hidden sm:flex sm:h-8 sm:gap-1.5 sm:py-0" title={email.isSmimeScheduled ? t('cancel_and_compose_again') : t('cancel_and_edit')}>
<EditIcon className="w-4 h-4" />
<Pencil className="w-4 h-4" />
{showToolbarLabels && <span className="hidden sm:inline text-sm">{email.isSmimeScheduled ? t('cancel_and_compose_again') : t('cancel_and_edit')}</span>}
</Button>
{onCancelScheduledSend && !email.isSmimeScheduled && (
@@ -3881,7 +3882,7 @@ export function EmailViewer({
className="sm:flex sm:flex-row sm:h-8 sm:gap-1.5 sm:py-0"
title={t('tooltips.edit_draft')}
>
<EditIcon className="w-4 h-4" />
<Pencil className="w-4 h-4" />
<span className="text-sm">{t('edit_draft')}</span>
</Button>
)}
@@ -4036,12 +4037,12 @@ export function EmailViewer({
"flex-col items-center gap-0.5 h-auto py-1.5 px-2 sm:flex-row sm:h-8 sm:gap-1.5 sm:py-0",
mvViewer
? "order-2"
: isInJunkFolder ? "hover:bg-green-50 dark:hover:bg-green-950/30" : "hover:bg-red-50 dark:hover:bg-red-950/30"
: isInJunkFolder ? "hover:bg-emerald-50 dark:hover:bg-emerald-950/30" : "hover:bg-red-50 dark:hover:bg-red-950/30"
)}
title={isInJunkFolder ? t('spam.not_spam_title') : t('spam.button_title')}
>
{isInJunkFolder ? (
<ShieldCheck className={cn("h-4 w-4", !mvViewer && "text-green-600 dark:text-green-400")} />
<ShieldCheck className={cn("h-4 w-4", !mvViewer && "text-emerald-600 dark:text-emerald-400")} />
) : (
<ShieldAlert className={cn("h-4 w-4", !mvViewer && "text-red-600 dark:text-red-400")} />
)}
@@ -4170,7 +4171,7 @@ export function EmailViewer({
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Star className={cn("w-4 h-4", isStarred && "fill-yellow-400 text-yellow-400")} />
<Star className={cn("w-4 h-4", isStarred && "fill-amber-400 text-amber-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
</button>
{/* Overflow: reply */}
@@ -4328,7 +4329,7 @@ export function EmailViewer({
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
>
{isInJunkFolder ? (
<ShieldCheck className="h-4 w-4 text-green-600 dark:text-green-400" />
<ShieldCheck className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
) : (
<ShieldAlert className="h-4 w-4 text-red-600 dark:text-red-400" />
)}
@@ -4538,7 +4539,7 @@ export function EmailViewer({
onClick={leaveMoreMenuSub}
className="flex items-center gap-1 -ms-2 px-2 py-1 rounded hover:bg-muted text-sm font-semibold text-foreground"
>
<ChevronLeft className="w-5 h-5" />
<ArrowLeft className="w-5 h-5" />
{moreMenuSub === 'move' ? t('move_to') : moreMenuSub === 'rules' ? tContextMenu('rules.title') : t('tag')}
</button>
) : (
@@ -4564,7 +4565,7 @@ export function EmailViewer({
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Star className={cn("w-5 h-5", isStarred && "fill-yellow-400 text-yellow-400")} />
<Star className={cn("w-5 h-5", isStarred && "fill-amber-400 text-amber-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
</button>
{/* Move to folder (opens sub-view). The toolbar's own Move button
@@ -4752,7 +4753,7 @@ export function EmailViewer({
className="h-11 w-11 lg:h-10 lg:w-10 flex-shrink-0 -ms-2"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
<ArrowLeft className="w-5 h-5" />
</Button>
)}
<div className="flex-1 min-w-0">
@@ -4779,7 +4780,7 @@ export function EmailViewer({
>
<Star className={cn(
"w-4 h-4 lg:w-5 lg:h-5 transition-colors",
isStarred ? "fill-yellow-400 text-yellow-400" : "text-muted-foreground/40"
isStarred ? "fill-amber-400 text-amber-400" : "text-muted-foreground/40"
)} />
</button>
)}
@@ -5697,7 +5698,7 @@ export function EmailViewer({
{t('reschedule_send')}
</Button>
<Button size="sm" variant="outline" onClick={onCancelScheduledForEdit} className="gap-1.5">
<EditIcon className="w-3.5 h-3.5" />
<Pencil className="w-3.5 h-3.5" />
{email.isSmimeScheduled ? t('cancel_and_compose_again') : t('cancel_and_edit')}
</Button>
{onCancelScheduledSend && !email.isSmimeScheduled && (
@@ -6438,7 +6439,7 @@ export function EmailViewer({
className="flex flex-col items-center justify-center gap-1 py-2 px-1 min-h-[44px] grow shrink-0 basis-[64px] text-primary active:text-primary/80 transition-colors duration-150"
aria-label={t('tooltips.edit_draft')}
>
<EditIcon className="w-5 h-5" />
<Pencil className="w-5 h-5" />
<span className="text-[10px] font-medium leading-tight truncate max-w-full">{t('edit_draft')}</span>
</button>
) : (
+2 -2
View File
@@ -11,13 +11,13 @@ import {
Mail,
MailOpen,
MoreVertical,
RotateCcw,
Search,
ShieldAlert,
ShieldCheck,
Square,
Tag,
Trash2,
RefreshCw,
} from "@/components/icons";
import { useTranslations } from "next-intl";
import { useAuthStore } from "@/stores/auth-store";
@@ -425,7 +425,7 @@ export function MvListToolbar({
</button>
<ToolbarButton
icon={RotateCcw}
icon={RefreshCw}
label={tCommon("refresh")}
onClick={onRefresh}
disabled={!client || isRefreshing}
+2 -2
View File
@@ -7,7 +7,7 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useTranslations } from "next-intl";
import { iconForName, MoreHorizontal, Puzzle } from "@/components/icons";
import { iconForName, MoreVertical, Puzzle } from "@/components/icons";
import { ContextMenu } from "@/components/ui/context-menu";
import { PluginActions, usePluginActions } from "@/components/plugins/plugin-actions";
import { PluginSlot } from "@/components/plugins/plugin-slot";
@@ -68,7 +68,7 @@ export function AttachmentPluginMenu({
data-attachment-plugin-menu=""
onClick={(e) => openContextMenu(e, true)}
>
<MoreHorizontal className={cn(iconClassName, "text-foreground")} />
<MoreVertical className={cn(iconClassName, "text-foreground")} />
</button>
<ContextMenu ref={menuRef} isOpen={contextMenu.isOpen} position={contextMenu.position} onClose={closeContextMenu}>
<PluginActions
+4 -4
View File
@@ -120,7 +120,7 @@ import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import";
import { findDraftIdentityId, findReplyIdentityId, resolveComposeAccountEmail } from "@/lib/reply-identity";
import { buildReplyRecipients, isSelfSent } from "@/lib/reply-recipients";
import { useProMultiAccountIdentities } from "@/hooks/use-pro-multi-account-identities";
import { Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, PenLine, CheckSquare, Square, AlertTriangle, ArrowLeft, RefreshCw, Reply, ReplyAll, Forward, Archive, Trash2, FolderInput, ShieldAlert, ShieldCheck, Folder } from "@/components/icons";
import { Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, CheckSquare, Square, AlertTriangle, ArrowLeft, RefreshCw, Reply, ReplyAll, Forward, Archive, Trash2, FolderInput, ShieldAlert, ShieldCheck, Folder } from "@/components/icons";
import { useRegisterCommands } from "@/lib/command-registry";
import { MoveToDialog } from "@/components/email/move-to-dialog";
import { ResizeHandle } from "@/components/layout/resize-handle";
@@ -4692,7 +4692,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
)}
{connectionLost && (
<div className="flex items-center justify-center gap-2 bg-destructive/10 border-b border-destructive/30 text-destructive text-sm py-1.5 px-4 flex-shrink-0">
<RotateCcw className="h-3.5 w-3.5 animate-spin" />
<RefreshCw className="h-3.5 w-3.5 animate-spin" />
<span>{tCommon('reconnecting')}</span>
</div>
)}
@@ -5036,7 +5036,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
title={tCommon("refresh")}
aria-label={tCommon("refresh")}
>
<RotateCcw className={cn("w-4 h-4", isManualRefreshing && "animate-spin")} />
<RefreshCw className={cn("w-4 h-4", isManualRefreshing && "animate-spin")} />
</button>
{/* The folder's actions, as the Mountain View list ⋮ has them. */}
<MailFolderMenu
@@ -5460,7 +5460,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
}}
className="flex items-center gap-3 px-4 py-2.5 bg-primary/10 border-b border-primary/20 hover:bg-primary/15 transition-colors cursor-pointer w-full text-start"
>
<PenLine className="w-4 h-4 text-primary shrink-0" />
<PenSquare className="w-4 h-4 text-primary shrink-0" />
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-primary">{t('email_composer.continue_draft')}</span>
{pendingDraft.subject && (