mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 00:13:37 +00:00
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:
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user