From 4849cf233ef192f70d9587625b0ae2dea195ad70 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 04:11:17 +0100 Subject: [PATCH] feat: show typed, coloured file icons on the reader's attachment chips --- .../__tests__/reader-attachment-icons.test.ts | 21 ++++++++++++++ src/components/email/AttachmentChips.tsx | 9 ++++-- src/components/email/ListAttachmentChips.tsx | 28 +++---------------- src/components/email/attachment-icons.ts | 28 +++++++++++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 src/components/__tests__/reader-attachment-icons.test.ts create mode 100644 src/components/email/attachment-icons.ts diff --git a/src/components/__tests__/reader-attachment-icons.test.ts b/src/components/__tests__/reader-attachment-icons.test.ts new file mode 100644 index 00000000..c4dacbc9 --- /dev/null +++ b/src/components/__tests__/reader-attachment-icons.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +// List chips showed a typed, coloured icon per file; the reader's chips a +// grey paperclip for everything (webmail a78d03a18 uses the list's icons in +// the reader too). + +const src = (p: string) => fs.readFileSync(path.resolve(__dirname, '..', p), 'utf8'); + +describe('reader attachment chip icons', () => { + it('draws the same typed icon and colour as the list chips', () => { + const reader = src('email/AttachmentChips.tsx'); + expect(reader).not.toMatch(//); + const list = src('email/ListAttachmentChips.tsx'); + expect(list).toMatch(/ATTACHMENT_ICONS\[kind\]/); + expect(list).toMatch(/attachmentIconColor\(kind, c\)/); + }); +}); diff --git a/src/components/email/AttachmentChips.tsx b/src/components/email/AttachmentChips.tsx index b194485f..88599955 100644 --- a/src/components/email/AttachmentChips.tsx +++ b/src/components/email/AttachmentChips.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Alert } from 'react-native'; -import { Paperclip, Download, Eye, ExternalLink, Save, Share2, FolderArchive, FolderOpen } from '../icons'; +import { Download, Eye, ExternalLink, Save, Share2, FolderArchive, FolderOpen } from '../icons'; import type { Attachment, Email } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; @@ -13,6 +13,8 @@ import { getAttachmentDisplayName, visibleAttachments, formatSize, } from '../../lib/attachment-display'; import { emailExportFilename } from '../../lib/download-filename'; +import { attachmentKind } from '../../lib/list-attachments'; +import { ATTACHMENT_ICONS, attachmentIconColor } from './attachment-icons'; import { ActionSheet, type ActionSheetItem } from './ActionSheet'; import { AttachmentPreviewModal } from './AttachmentPreviewModal'; import type { ExtractedAttachment } from './use-body-override'; @@ -232,6 +234,9 @@ export function AttachmentChips({ email, jmapAccountId, calendarBannerShown, tne {visible.map((item) => { const busy = busyKey === item.key; + // Typed and coloured like the list's chips (webmail a78d03a18). + const kind = attachmentKind(item.type, item.name); + const Icon = ATTACHMENT_ICONS[kind]; return ( - {busy ? : } + {busy ? : } {item.name} {formatSize(item.size)} diff --git a/src/components/email/ListAttachmentChips.tsx b/src/components/email/ListAttachmentChips.tsx index a457666f..51f68c9a 100644 --- a/src/components/email/ListAttachmentChips.tsx +++ b/src/components/email/ListAttachmentChips.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Alert, Pressable, StyleSheet, Text, View } from 'react-native'; -import { File as FileIcon, FileArchive, FileImage, FileSpreadsheet, FileText } from '../icons'; import type { Attachment, Email, Mailbox } from '../../api/types'; import { getEmailAttachments } from '../../api/email'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; @@ -13,8 +12,9 @@ import { accountIdOfRow } from '../../stores/email-store'; import { accountScopedId } from '../../lib/thread-utils'; import { attachmentKind, realAttachments, requestListAttachments, shortAttachmentName, - type AttachmentKind, type LoadListAttachments, + type LoadListAttachments, } from '../../lib/list-attachments'; +import { ATTACHMENT_ICONS, attachmentIconColor } from './attachment-icons'; import { getAttachmentDisplayName } from '../../lib/attachment-display'; import { attachmentSaveName, downloadAttachment } from '../../lib/email-export'; import { AttachmentPreviewModal } from './AttachmentPreviewModal'; @@ -23,26 +23,6 @@ import { useFilePreview } from './use-file-preview'; /** How many chips a row shows before collapsing the rest into a count. */ const MAX_CHIPS = 2; -const ICONS: Record = { - image: FileImage, - pdf: FileText, - sheet: FileSpreadsheet, - archive: FileArchive, - document: FileText, - other: FileIcon, -}; - -function iconColor(kind: AttachmentKind, c: ThemePalette): string { - switch (kind) { - case 'image': return c.tags.purple.dot; - case 'pdf': return c.tags.red.dot; - case 'sheet': return c.tags.green.dot; - case 'archive': return c.tags.amber.dot; - case 'document': return c.tags.blue.dot; - default: return c.textMuted; - } -} - /** * The row's attachment parts: the ones the email already carries (a message * opened before), else loaded lazily when it has a paperclip. @@ -94,7 +74,7 @@ export const ListAttachmentChips = React.memo(function ListAttachmentChips({ {shown.map((a, i) => { const kind = attachmentKind(a.type, a.name); - const Icon = ICONS[kind]; + const Icon = ATTACHMENT_ICONS[kind]; return ( - + {shortAttachmentName(a.name ?? '')} ); diff --git a/src/components/email/attachment-icons.ts b/src/components/email/attachment-icons.ts new file mode 100644 index 00000000..88920a0b --- /dev/null +++ b/src/components/email/attachment-icons.ts @@ -0,0 +1,28 @@ +import { File as FileIcon, FileArchive, FileImage, FileSpreadsheet, FileText } from '../icons'; +import type { ThemePalette } from '../../theme/tokens'; +import type { AttachmentKind } from '../../lib/list-attachments'; + +/** + * The icon of an attachment chip by file type, the same in the list and the + * reader (webmail components/email/attachment-chips.tsx, a78d03a18). + */ +export const ATTACHMENT_ICONS: Record = { + image: FileImage, + pdf: FileText, + sheet: FileSpreadsheet, + archive: FileArchive, + document: FileText, + other: FileIcon, +}; + +/** The icon's colour by file type. */ +export function attachmentIconColor(kind: AttachmentKind, c: ThemePalette): string { + switch (kind) { + case 'image': return c.tags.purple.dot; + case 'pdf': return c.tags.red.dot; + case 'sheet': return c.tags.green.dot; + case 'archive': return c.tags.amber.dot; + case 'document': return c.tags.blue.dot; + default: return c.textMuted; + } +}