Name every icon by the pixel grid it is drawn for

Groundwork for redrawing the icons on their pixel grids. A name is now
<icon><px> (server14, server16, server24) and must match the size token
where it renders: 14 at --icon-size-sm, 16 at -md, 24 at -xl.
tdesignLoading is the one exception, since it spins. Every name not yet
redrawn aliases its TDesign glyph, so nothing changes on screen except
the manager-open overlay icon, which moves from lg to xl ("a status
mark on a card").

- pixel-icons.ts holds the redrawn glyphs, so far add14 (the + from
  3f7145d, renamed from pixelAdd).
- TS code that picks icons takes Icon14 / Icon16 (menu items, file
  glyphs, the copy arrow, toasts, transfer states), so build:ui
  rejects a wrong name.
- icons.spec.ts checks every <ng-icon> in the skins: it has a size
  token, and each literal name, both sides of a [name] ternary
  included, is registered and drawn for that token's grid.
- TDesign's file1 is now fileText, since file1 plus a suffix would
  read file114.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-09 13:24:38 +02:00
co-authored by Claude
parent 3f7145d69a
commit 5d23809d48
21 changed files with 208 additions and 138 deletions
+13 -12
View File
@@ -3,6 +3,7 @@
// Only the icon glyph is colored; row text stays neutral. Docs and unknown
// files stay muted on purpose — sparing color is what keeps it scannable.
import type { FileEntry } from '@shared/types';
import type { Icon16 } from './icons';
type Category = 'image' | 'audio' | 'video' | 'archive' | 'code' | 'doc';
@@ -11,29 +12,29 @@ const MUTED = 'shrink-0 text-muted-foreground';
const FOLDER = 'shrink-0 text-icon-folder';
const LINK = 'shrink-0 text-icon-link';
const CATEGORIES: Record<Category, { glyph: string; class: string; extensions: string[] }> = {
const CATEGORIES: Record<Category, { glyph: Icon16; class: string; extensions: string[] }> = {
image: {
glyph: 'tdesignImage',
glyph: 'image16',
class: 'shrink-0 text-icon-image',
extensions: 'png jpg jpeg gif webp avif svg ico icns bmp tiff tif heic heif raw'.split(' '),
},
audio: {
glyph: 'tdesignMusic',
glyph: 'music16',
class: 'shrink-0 text-icon-media',
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
},
video: {
glyph: 'tdesignVideo',
glyph: 'video16',
class: 'shrink-0 text-icon-media',
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
},
archive: {
glyph: 'tdesignFileZip',
glyph: 'fileZip16',
class: 'shrink-0 text-icon-archive',
extensions: 'zip tar gz tgz bz2 xz zst 7z rar dmg iso jar deb rpm pkg apk'.split(' '),
},
code: {
glyph: 'tdesignCode',
glyph: 'code16',
class: 'shrink-0 text-icon-code',
// ".ts" is TypeScript here, not an MPEG transport stream (deliberate)
extensions: (
@@ -45,7 +46,7 @@ const CATEGORIES: Record<Category, { glyph: string; class: string; extensions: s
).split(' '),
},
doc: {
glyph: 'tdesignFile1',
glyph: 'fileText16',
class: MUTED, // docs keep the neutral color; only the glyph changes
extensions: 'md markdown txt rtf pdf doc docx xls xlsx ppt pptx csv tsv log odt ods odp epub'.split(' '),
},
@@ -84,12 +85,12 @@ export function fileKindKey(entry: FileEntry): string {
return category ? KIND_KEYS[category] : 'file';
}
export function fileGlyph(entry: FileEntry): string {
if (entry.name === '..') return 'tdesignArrowUp';
if (entry.type === 'dir') return 'tdesignFolder';
if (entry.type === 'symlink') return 'tdesignLink';
export function fileGlyph(entry: FileEntry): Icon16 {
if (entry.name === '..') return 'arrowUp16';
if (entry.type === 'dir') return 'folder16';
if (entry.type === 'symlink') return 'link16';
const category = categoryOf(entry.name);
return category ? CATEGORIES[category].glyph : 'tdesignFile';
return category ? CATEGORIES[category].glyph : 'file16';
}
export function fileIconClass(entry: FileEntry): string {