Size every icon on TDesign's scale: four rem tokens, whole pixels

TDesign draws its glyphs on a 24px grid with a 2px stroke and sizes a
component's icon 2px above its text, in whole pixels. The templates had
drifted to 12 one-off sizes, most of them fractional (0.9rem = 14.4px), so
strokes blurred and neighbours never matched: the tab bar's 14.4px cog
beside a 12.8px +, the shortcut rows' 12px + beside an 11.2px reset and
9.6px chip x.

Every <ng-icon> now takes one of --icon-size-sm (14px: beside 11-12px
text, small icon buttons), -md (16px: file rows, window titles, path-bar
buttons), -lg (20px) or -xl (24px: lone spinners and status marks).
--action-icon goes; architecture.md and gotchas.md carry the rule.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:19:58 +02:00
co-authored by Claude
parent e28314054f
commit fa2cec7136
14 changed files with 97 additions and 74 deletions
+6 -1
View File
@@ -507,7 +507,12 @@ none`, a drawn check / square dot), selects (a `currentColor` caret; use
`::-webkit-scrollbar`s (which make macOS scrollbars always visible).
`@theme inline` maps tokens to Tailwind utilities (`bg-primary`,
`text-icon-folder`, `bg-toolbar-2`, `bg-cursor-tint`). File-type icon colors
come from Oxide's hue scales (800 steps dark, 600 light). **Interface scale** (`settings.uiScale`,
come from Oxide's hue scales (800 steps dark, 600 light). **Icon sizes** follow
TDesign's rule (an icon is 2px larger than its text, in whole pixels) through
four rem tokens, and every `<ng-icon>` takes one: `--icon-size-sm` 14px (beside
11–12px text, small icon buttons), `-md` 16px (13–14px text: file rows, window
titles; the path-bar buttons), `-lg` 20px and `-xl` 24px (lone spinners and
status marks). **Interface scale** (`settings.uiScale`,
0.5–4 in 0.1 steps): one root font-size lever — `applyUiScale` sets
`html { font-size: 16px * scale }`, and since EVERY size in the app is rem
(panel tokens like `--row-h`, all Tailwind utilities, ng-icon sizes), the
+6
View File
@@ -76,6 +76,12 @@ touching the adjacent code.
moment the pseudo-element is styled, WKWebView draws classic always-visible
scrollbars that take layout width. Deliberate here (square design); don't
reach for it to "tint" scrollbars without accepting that.
- **An icon size is one of the four `--icon-size-*` tokens, never a one-off
rem.** TDesign glyphs are 24-unit SVGs with 2-unit strokes, so a fractional
size (0.9rem = 14.4px) lands every stroke between device pixels and blurs
it. Per-template picks had drifted to 12 sizes, and neighbours stopped
matching: a 14.4px cog beside a 12.8px +, and a 12px + beside 9.6px × in
the shortcut chips.
## The interface scale (root font-size lever)
+2 -2
View File
@@ -2,7 +2,7 @@
@if (!rpc.available) {
<div class="flex flex-1 items-center justify-center">
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
<ng-icon name="tdesignCloseCircle" size="1.5rem" class="text-destructive" />
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
<p class="mt-2 text-sm font-medium">{{ i18n.t('app.notConnectedTitle') }}</p>
<p class="mt-1 text-xs text-muted-foreground">
{{ i18n.t('app.notConnectedBody') }}
@@ -28,7 +28,7 @@
(mousedown)="focusManager()"
>
<div class="border border-border bg-card px-5 py-4 text-center shadow-xl">
<ng-icon name="tdesignServer" size="1.25rem" class="text-muted-foreground" />
<ng-icon name="tdesignServer" size="var(--icon-size-lg)" class="text-muted-foreground" />
<p class="mt-1.5 text-xs text-muted-foreground">{{ i18n.t('app.managerOpen') }}</p>
<button
(click)="focusManager()"
@@ -9,7 +9,7 @@
[class.h-9]="!isMacChrome"
[class.pl-3]="!isMacChrome"
>
<ng-icon name="tdesignServer" size="0.95rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignServer" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
<h1 class="text-sm font-semibold">{{ i18n.t('connMgr.title') }}</h1>
<div class="flex-1"></div>
<button
@@ -19,9 +19,9 @@
class="flex items-center gap-1.5 px-2 py-1 text-[0.6875rem] text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground disabled:opacity-50"
>
@if (importing()) {
<ng-icon name="tdesignLoading" size="0.85rem" class="animate-spin" />
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />
} @else {
<!-- <ng-icon name="tdesignFileZip" size="0.85rem" /> -->
<!-- <ng-icon name="tdesignFileZip" size="var(--icon-size-sm)" /> -->
}
{{ i18n.t('connMgr.importWinscp') }}
</button>
@@ -36,14 +36,14 @@
[title]="shortcuts.tip(i18n.t('connMgr.newConnectionHint'), 'conn-new')"
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignAdd" size="0.85rem" /> {{ i18n.t('connMgr.newConnection') }}
<ng-icon name="tdesignAdd" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newConnection') }}
</button>
<button
(click)="newStack()"
[title]="shortcuts.tip(i18n.t('connMgr.newStackHint'), 'conn-new-stack')"
class="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignFolderAdd" size="0.85rem" /> {{ i18n.t('connMgr.newStack') }}
<ng-icon name="tdesignFolderAdd" size="var(--icon-size-sm)" /> {{ i18n.t('connMgr.newStack') }}
</button>
</div>
@@ -69,7 +69,7 @@
>
<ng-icon
[name]="group.stack.collapsed ? 'tdesignChevronRight' : 'tdesignChevronDown'"
size="0.8rem"
size="var(--icon-size-sm)"
/>
</button>
<span class="truncate text-[0.6875rem] font-medium text-muted-foreground uppercase">
@@ -90,14 +90,14 @@
[title]="shortcuts.tip(i18n.t('connMgr.renameStack'), 'conn-rename-stack')"
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
>
<ng-icon name="tdesignEdit" size="0.75rem" />
<ng-icon name="tdesignEdit" size="var(--icon-size-sm)" />
</button>
<button
(click)="deleteStack(group.stack)"
[title]="shortcuts.tip(i18n.t('connMgr.deleteStack'), 'conn-delete')"
class="grid size-4 place-items-center text-muted-foreground hover:text-destructive"
>
<ng-icon name="tdesignDelete" size="0.75rem" />
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" />
</button>
@if (group.connections.length) {
<button
@@ -105,7 +105,7 @@
[title]="shortcuts.tip(i18n.t('connMgr.openStack'), 'conn-connect')"
class="grid size-4 place-items-center text-muted-foreground hover:text-primary"
>
<ng-icon name="tdesignArrowRight" size="0.8rem" />
<ng-icon name="tdesignArrowRight" size="var(--icon-size-sm)" />
</button>
}
</div>
@@ -123,7 +123,7 @@
[class.bg-accent]="draft()?.id === connection.id"
[class.opacity-50]="draggedId() === connection.id"
>
<ng-icon name="tdesignServer" size="0.85rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignServer" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
<span class="truncate">{{ connection.name }}</span>
</button>
} @empty {
@@ -161,7 +161,7 @@
[class.bg-accent]="draft()?.id === connection.id"
[class.opacity-50]="draggedId() === connection.id"
>
<ng-icon name="tdesignServer" size="0.85rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignServer" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
<span class="truncate">{{ connection.name }}</span>
</button>
}
@@ -342,7 +342,7 @@
[title]="shortcuts.tip(i18n.t('common.delete'), 'conn-delete')"
class="flex items-center gap-1.5 px-2 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-destructive"
>
<ng-icon name="tdesignDelete" size="0.85rem" /> {{ i18n.t('common.delete') }}
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" /> {{ i18n.t('common.delete') }}
</button>
<button
(click)="moreOpen.set(!moreOpen())"
@@ -366,7 +366,7 @@
class="flex items-center gap-1.5 bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
@if (busy()) {
<ng-icon name="tdesignLoading" size="0.85rem" class="animate-spin" />
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />
}
{{ i18n.t('common.connect') }}
</button>
@@ -374,7 +374,7 @@
} @else {
<div class="grid flex-1 place-items-center">
<div class="text-center">
<ng-icon name="tdesignServer" size="1.75rem" class="text-muted-foreground" />
<ng-icon name="tdesignServer" size="var(--icon-size-xl)" class="text-muted-foreground" />
<p class="mt-2 text-xs text-muted-foreground">
{{ i18n.t('connMgr.selectPrompt') }}
</p>
+2 -2
View File
@@ -77,11 +77,11 @@ const EDGE_GAP = 6; // px on purpose: a viewport-edge margin, not a UI size
<!-- the check column: a checkmark, else the item's icon -->
<span class="grid w-4 shrink-0 place-items-center">
@if (item.checked) {
<ng-icon name="tdesignCheck" size="0.85rem" class="text-primary" />
<ng-icon name="tdesignCheck" size="var(--icon-size-sm)" class="text-primary" />
} @else if (item.icon) {
<ng-icon
[name]="item.icon"
size="0.9rem"
size="var(--icon-size-sm)"
[class.text-muted-foreground]="$index !== activeIndex() || !!item.disabled"
/>
}
@@ -64,7 +64,7 @@ import { FocusService } from '@core/focus.service';
<div class="mt-1.5 flex items-center gap-1.5 text-xs">
<ng-icon
[name]="isDir() ? 'tdesignFolder' : 'tdesignFile'"
size="0.9rem"
size="var(--icon-size-sm)"
class="shrink-0 text-muted-foreground"
/>
<span class="font-mono tabular-nums">
@@ -2,7 +2,7 @@
@if (!ctx) {
<div class="flex flex-1 items-center justify-center">
<div class="max-w-sm border border-border bg-card p-6 text-center shadow-sm">
<ng-icon name="tdesignCloseCircle" size="1.5rem" class="text-destructive" />
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.noFileTitle') }}</p>
<p class="mt-1 text-xs text-muted-foreground">
{{ i18n.t('editor.noFileBody') }}
@@ -20,7 +20,7 @@
[class.h-9]="!isMacChrome"
[class.pl-3]="!isMacChrome"
>
<ng-icon name="tdesignFile1" size="0.95rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignFile1" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
<h1 class="min-w-0 truncate font-mono text-sm font-semibold">
{{ ctx.name }}{{ dirty() ? ' •' : '' }}
</h1>
@@ -41,13 +41,13 @@
[title]="shortcuts.tip(i18n.t('editor.findHint'), 'find')"
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignSearch" size="0.85rem" />
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" />
</button>
</header>
@if (findOpen()) {
<div class="flex shrink-0 items-center gap-2 border-b border-border bg-toolbar px-3 py-1.5">
<ng-icon name="tdesignSearch" size="0.85rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
<input
#findBox
[value]="findQuery()"
@@ -69,14 +69,14 @@
[title]="shortcuts.tipFixed(i18n.t('editor.prevMatch'), 'Shift+Enter')"
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignChevronUp" size="0.8rem" />
<ng-icon name="tdesignChevronUp" size="var(--icon-size-sm)" />
</button>
<button
(click)="findStep(1)"
[title]="shortcuts.tipFixed(i18n.t('editor.nextMatch'), 'Enter')"
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignChevronDown" size="0.8rem" />
<ng-icon name="tdesignChevronDown" size="var(--icon-size-sm)" />
</button>
<div class="flex-1"></div>
<button
@@ -84,7 +84,7 @@
[title]="i18n.t('editor.closeHint')"
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.8rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
</div>
}
@@ -93,19 +93,19 @@
<div
class="flex shrink-0 items-center gap-2 border-b border-border bg-destructive/10 px-3 py-1.5 text-[0.6875rem] text-destructive"
>
<ng-icon name="tdesignCloseCircle" size="0.85rem" class="shrink-0" />
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-sm)" class="shrink-0" />
{{ i18n.t('editor.disconnected', { name: ctx.sessionTitle }) }}
</div>
}
@if (loading()) {
<div class="flex flex-1 items-center justify-center">
<ng-icon name="tdesignLoading" size="1.25rem" class="animate-spin text-primary" />
<ng-icon name="tdesignLoading" size="var(--icon-size-lg)" class="animate-spin text-primary" />
</div>
} @else if (loadError(); as error) {
<div class="flex flex-1 items-center justify-center">
<div class="max-w-md border border-border bg-card p-6 text-center shadow-sm">
<ng-icon name="tdesignCloseCircle" size="1.5rem" class="text-destructive" />
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-xl)" class="text-destructive" />
<p class="mt-2 text-sm font-medium">{{ i18n.t('editor.cantOpen', { name: ctx.name }) }}</p>
<p class="mt-1 text-xs text-muted-foreground">{{ error }}</p>
<button
@@ -159,7 +159,7 @@
class="flex items-center gap-1.5 bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
@if (saving()) {
<ng-icon name="tdesignLoading" size="0.85rem" class="animate-spin" />
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />
}
{{ i18n.t('common.save') }}
</button>
@@ -10,19 +10,19 @@
[title]="shortcuts.tip(i18n.t('panel.choosePanel'), 'source-picker')"
>
@if (panel().connecting()) {
<ng-icon name="tdesignLoading" class="shrink-0 animate-spin text-primary" size="var(--action-icon)" />
<ng-icon name="tdesignLoading" class="shrink-0 animate-spin text-primary" size="var(--icon-size-sm)" />
} @else {
<ng-icon
[name]="isRemote() ? 'tdesignServer' : 'tdesignHardDrive'"
[class]="isRemote() ? 'shrink-0 text-primary' : 'shrink-0 text-muted-foreground'"
size="var(--action-icon)"
size="var(--icon-size-sm)"
/>
}
<span class="truncate">{{ isRemote() ? panel().title() : i18n.t('common.local') }}</span>
<ng-icon
name="tdesignChevronDown"
class="shrink-0 text-muted-foreground"
size="calc(var(--action-icon) - 0.1rem)"
size="var(--icon-size-sm)"
/>
</button>
<div class="flex-1"></div>
@@ -34,7 +34,7 @@
[title]="shortcuts.tip(side() === 'left' ? i18n.t('panel.copyToRight') : i18n.t('panel.copyToLeft'), 'send')"
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-primary"
>
<ng-icon [name]="copyIcon()" size="var(--action-icon)" />
<ng-icon [name]="copyIcon()" size="var(--icon-size-sm)" />
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('panel.copy') }}</span>
</button>
<button
@@ -42,7 +42,7 @@
[title]="shortcuts.tip(i18n.t('panel.editFile'), 'edit')"
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignFile1" size="var(--action-icon)" />
<ng-icon name="tdesignFile1" size="var(--icon-size-sm)" />
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('panel.edit') }}</span>
</button>
<button
@@ -50,7 +50,7 @@
[title]="shortcuts.tip(i18n.t('panel.rename'), 'rename')"
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignEdit" size="var(--action-icon)" />
<ng-icon name="tdesignEdit" size="var(--icon-size-sm)" />
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.rename') }}</span>
</button>
<button
@@ -58,14 +58,14 @@
[title]="shortcuts.tip(i18n.t('panel.newFolder'), 'new-folder')"
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignFolderAdd" size="var(--action-icon)" />
<ng-icon name="tdesignFolderAdd" size="var(--icon-size-sm)" />
</button>
<button
(click)="run('new-file')"
[title]="shortcuts.tip(i18n.t('panel.newFile'), 'new-file')"
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignFileAdd" size="var(--action-icon)" />
<ng-icon name="tdesignFileAdd" size="var(--icon-size-sm)" />
</button>
@if (chmodAvailable()) {
<button
@@ -73,7 +73,7 @@
[title]="shortcuts.tip(i18n.t('panel.permissions'), 'chmod')"
class="grid size-(--action-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignLockOn" size="var(--action-icon)" />
<ng-icon name="tdesignLockOn" size="var(--icon-size-sm)" />
</button>
}
<button
@@ -81,7 +81,7 @@
[title]="shortcuts.tip(i18n.t('common.delete'), 'delete')"
class="flex h-(--action-h) shrink-0 items-center px-1 text-muted-foreground hover:bg-accent hover:text-destructive"
>
<ng-icon name="tdesignDelete" size="var(--action-icon)" />
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" />
<span class="ml-1 hidden text-(length:--action-font) @[30rem]:inline">{{ i18n.t('common.delete') }}</span>
</button>
@@ -95,7 +95,7 @@
[title]="shortcuts.tip(i18n.t('panel.parentDir'), 'parent')"
class="grid size-(--path-ctl-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:hover:bg-transparent"
>
<ng-icon name="tdesignArrowUp" size="1rem" />
<ng-icon name="tdesignArrowUp" size="var(--icon-size-md)" />
</button>
<!-- no blanket stopPropagation: the window handler lets only ⌘/Ctrl chords
through from a text field, and its WebView2 reload guard must see F5 -->
@@ -117,9 +117,9 @@
class="grid size-(--path-ctl-h) shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:hover:bg-transparent"
>
@if (panel().loading()) {
<ng-icon name="tdesignLoading" size="1rem" class="animate-spin" />
<ng-icon name="tdesignLoading" size="var(--icon-size-md)" class="animate-spin" />
} @else {
<ng-icon name="tdesignRefresh" size="1rem" />
<ng-icon name="tdesignRefresh" size="var(--icon-size-md)" />
}
</button>
</div>
@@ -153,7 +153,7 @@
<!-- type-to-filter bar (appears while a query is active) -->
@if (panel().filter(); as query) {
<div class="flex items-center gap-1.5 border-b border-border bg-accent/50 px-2 py-1">
<ng-icon name="tdesignSearch" size="0.85rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignSearch" size="var(--icon-size-sm)" class="shrink-0 text-muted-foreground" />
<span class="min-w-0 flex-1 truncate font-mono text-xs">{{ query }}</span>
<span class="shrink-0 text-[0.6875rem] text-muted-foreground">
{{ i18n.t('panel.matches', { count: filterMatches() }) }}
@@ -163,7 +163,7 @@
[title]="i18n.t('panel.clearFilter')"
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.8rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
</div>
}
@@ -181,7 +181,7 @@
@if (panel().connecting()) {
<div class="panel-fade-in grid h-full place-items-center">
<div class="flex flex-col items-center gap-3">
<ng-icon name="tdesignLoading" size="1.4rem" class="animate-spin text-primary" />
<ng-icon name="tdesignLoading" size="var(--icon-size-xl)" class="animate-spin text-primary" />
<span class="text-xs text-muted-foreground">
{{ i18n.t('panel.connectingTo', { name: panel().connectingTo() ?? '' }) }}
</span>
@@ -210,7 +210,7 @@
[class.ring-ring]="i === panel().cursor() && isActive()"
>
<div class="flex min-w-0 items-center gap-1.5 px-2">
<ng-icon [name]="iconFor(entry)" size="0.95rem" [class]="iconClass(entry)" />
<ng-icon [name]="iconFor(entry)" size="var(--icon-size-md)" [class]="iconClass(entry)" />
<span class="truncate" [class.italic]="entry.type === 'symlink'">{{ entry.name }}</span>
</div>
@for (col of visibleColumns(); track col.key) {
@@ -260,7 +260,7 @@
</div>
@if (panel().slowLoad() && !panel().connecting()) {
<div class="panel-fade-in absolute inset-0 z-10 grid place-items-center bg-background/60">
<ng-icon name="tdesignLoading" size="1.4rem" class="animate-spin text-primary" />
<ng-icon name="tdesignLoading" size="var(--icon-size-xl)" class="animate-spin text-primary" />
</div>
}
</div>
@@ -9,7 +9,7 @@
[class.h-9]="!isMacChrome"
[class.pl-3]="!isMacChrome"
>
<ng-icon name="tdesignSetting" size="0.95rem" class="shrink-0 text-muted-foreground" />
<ng-icon name="tdesignSetting" size="var(--icon-size-md)" class="shrink-0 text-muted-foreground" />
<h1 class="text-sm font-semibold">{{ i18n.t('options.title') }}</h1>
</header>
@@ -250,7 +250,7 @@
[title]="i18n.t('options.resetDefault')"
class="grid size-5 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.7rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
}
</div>
@@ -306,7 +306,7 @@
<div class="relative min-w-0 flex-1">
<ng-icon
name="tdesignSearch"
size="0.8rem"
size="var(--icon-size-sm)"
class="pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-muted-foreground"
/>
<input
@@ -367,7 +367,7 @@
[title]="i18n.t('keys.ui.remove', { key: shortcuts.format(combo) })"
class="grid size-4 place-items-center text-muted-foreground hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.6rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
</span>
}
@@ -387,7 +387,7 @@
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground"
[class.text-primary]="capturing() === binding.id"
>
<ng-icon name="tdesignAdd" size="0.75rem" />
<ng-icon name="tdesignAdd" size="var(--icon-size-sm)" />
</button>
<button
(click)="resetRow(binding)"
@@ -396,7 +396,7 @@
[title]="i18n.t('keys.ui.reset')"
class="grid size-5 shrink-0 place-items-center text-muted-foreground hover:bg-accent hover:text-foreground disabled:invisible"
>
<ng-icon name="tdesignRefresh" size="0.7rem" />
<ng-icon name="tdesignRefresh" size="var(--icon-size-sm)" />
</button>
</div>
@if (note()?.id === binding.id) {
@@ -22,7 +22,7 @@ import { TransfersService } from '@core/transfers.service';
[title]="shortcuts.tip(i18n.t('transfer.showDetails'), 'toggle-transfers')"
>
@if (transfers.currentJob(); as job) {
<ng-icon name="tdesignLoading" size="0.8rem" class="shrink-0 animate-spin text-primary" />
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="shrink-0 animate-spin text-primary" />
<span class="truncate">
{{ i18n.t('transfer.copying', { name: job.name, percent: percent(job), speed: speedSuffix(job) }) }}
@if (transfers.queuedCount(); as queued) {
@@ -30,12 +30,12 @@ import { TransfersService } from '@core/transfers.service';
}
</span>
} @else if (transfers.failedJobs().length; as failed) {
<ng-icon name="tdesignCloseCircle" size="0.8rem" class="shrink-0 text-destructive" />
<ng-icon name="tdesignCloseCircle" size="var(--icon-size-sm)" class="shrink-0 text-destructive" />
<span class="text-destructive">
{{ i18n.t('transfer.failed', { count: failed }) }}
</span>
} @else if (transfers.hasJobs()) {
<ng-icon name="tdesignCheckCircle" size="0.8rem" class="shrink-0 text-success" />
<ng-icon name="tdesignCheckCircle" size="var(--icon-size-sm)" class="shrink-0 text-success" />
<span>{{ i18n.t('transfer.finished') }}</span>
} @else {
<span>{{ i18n.t('transfer.ready') }}</span>
@@ -46,7 +46,7 @@ import { TransfersService } from '@core/transfers.service';
<div class="flex shrink-0 items-center gap-1.5">
<ng-icon
[name]="activePanel().sessionId() === 'local' ? 'tdesignHardDrive' : 'tdesignServer'"
size="0.8rem"
size="var(--icon-size-sm)"
[class.text-primary]="activePanel().sessionId() !== 'local'"
/>
<span class="max-w-56 truncate">
+8 -8
View File
@@ -53,7 +53,7 @@ import { UpdaterService } from '@core/updater.service';
>
<ng-icon
[name]="panels.workspaceIsRemote(ws) ? 'tdesignServer' : 'tdesignHardDrive'"
size="0.85rem"
size="var(--icon-size-sm)"
class="shrink-0"
[class.text-primary]="panels.workspaceIsRemote(ws)"
/>
@@ -67,7 +67,7 @@ import { UpdaterService } from '@core/updater.service';
[title]="i18n.t('tabBar.closeTab')"
class="mr-1 grid size-4 shrink-0 place-items-center text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.7rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
} @else {
<span class="mr-1 size-4 shrink-0"></span>
@@ -83,7 +83,7 @@ import { UpdaterService } from '@core/updater.service';
[title]="shortcuts.tip(i18n.t('tabBar.newTab'), 'new-tab')"
class="ml-0.5 grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignAdd" size=".8rem" />
<ng-icon name="tdesignAdd" size="var(--icon-size-sm)" />
</button>
<div class="flex-1"></div>
@@ -106,13 +106,13 @@ import { UpdaterService } from '@core/updater.service';
<span class="relative flex items-center gap-1.5">
@switch (updater.state()) {
@case ('available') {
<ng-icon name="tdesignDownload" size=".8rem" />{{ i18n.t('tabBar.update') }}
<ng-icon name="tdesignDownload" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.update') }}
}
@case ('downloading') {
<ng-icon name="tdesignLoading" size=".8rem" class="animate-spin" />{{ i18n.t('tabBar.updating') }} {{ updater.progress() }}%
<ng-icon name="tdesignLoading" size="var(--icon-size-sm)" class="animate-spin" />{{ i18n.t('tabBar.updating') }} {{ updater.progress() }}%
}
@case ('ready') {
<ng-icon name="tdesignRefresh" size=".8rem" />{{ i18n.t('tabBar.restartToUpdate') }}
<ng-icon name="tdesignRefresh" size="var(--icon-size-sm)" />{{ i18n.t('tabBar.restartToUpdate') }}
}
}
</span>
@@ -125,7 +125,7 @@ import { UpdaterService } from '@core/updater.service';
[title]="shortcuts.tip(i18n.t('tabBar.manageConnections'), 'connection-manager')"
class="grid h-6 px-2 py-1 place-items-center text-[0.6875rem] text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
>
<!-- <ng-icon name="tdesignSetting" size="0.9rem" /> -->
<!-- <ng-icon name="tdesignSetting" size="var(--icon-size-sm)" /> -->
{{ i18n.t('tabBar.connections') }}
</button>
@@ -135,7 +135,7 @@ import { UpdaterService } from '@core/updater.service';
[title]="shortcuts.tip(i18n.t('tabBar.options'), 'open-settings')"
class="grid size-6 place-items-center text-muted-foreground electrobun-webkit-app-region-no-drag hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignSetting" size="0.9rem" />
<ng-icon name="tdesignSetting" size="var(--icon-size-sm)" />
</button>
</div>
`,
+2 -2
View File
@@ -17,7 +17,7 @@ import { ToastService } from '@core/toast.service';
>
<ng-icon
[name]="toastIcon(t.kind)"
size="1rem"
size="var(--icon-size-sm)"
[class]="t.kind === 'error' ? 'shrink-0 text-destructive' : 'shrink-0 text-success'"
/>
<span class="min-w-0 flex-1 wrap-break-word">{{ t.text }}</span>
@@ -26,7 +26,7 @@ import { ToastService } from '@core/toast.service';
[attr.aria-label]="i18n.t('transfer.dismiss')"
class="shrink-0 text-muted-foreground hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.9rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
<!-- auto-dismiss countdown; freezes with the timer while hovered -->
<div
@@ -8,13 +8,13 @@
[disabled]="!transfers.finishedJobs().length"
class="flex items-center gap-1 px-1.5 py-0.5 hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
>
<ng-icon name="tdesignDelete" size="0.75rem" /> {{ i18n.t('transfer.clearFinished') }}
<ng-icon name="tdesignDelete" size="var(--icon-size-sm)" /> {{ i18n.t('transfer.clearFinished') }}
</button>
</div>
<div class="max-h-40 overflow-y-auto border-t border-border bg-chrome">
@for (job of transfers.jobs(); track job.id) {
<div class="flex items-center gap-2 px-3 py-1.5 text-xs">
<ng-icon [name]="stateIcon(job)" size="0.95rem" [class]="stateClass(job)" />
<ng-icon [name]="stateIcon(job)" size="var(--icon-size-sm)" [class]="stateClass(job)" />
<span class="w-40 shrink-0 truncate font-medium" [title]="job.srcPath">{{ job.name }}</span>
<div class="min-w-0 flex-1">
<div class="truncate text-[0.6875rem] text-muted-foreground">{{ progressLabel(job) }}</div>
@@ -40,7 +40,7 @@
[title]="i18n.t('transfer.cancel')"
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-destructive"
>
<ng-icon name="tdesignClose" size="0.9rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
} @else {
<button
@@ -48,7 +48,7 @@
[title]="i18n.t('transfer.dismiss')"
class="shrink-0 p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="tdesignClose" size="0.9rem" />
<ng-icon name="tdesignClose" size="var(--icon-size-sm)" />
</button>
}
</div>
+13 -1
View File
@@ -197,7 +197,19 @@
--path-ctl-h: 1.75rem;
--action-h: 1.5rem;
--action-font: 0.6875rem;
--action-icon: 0.9rem;
}
/* Icon sizes, by TDesign's rule. Its glyphs are drawn on a 24px grid with a
2px stroke and ship for 16px; its components make an icon 2px larger than
the text beside it, in whole pixels (small button: 12px text, 14px icon).
Every <ng-icon> takes one of these four: a one-off size like 0.9rem (14.4px)
puts every stroke between pixels, and one-offs drift until neighbours never
match. Rem like the rest, so the interface scale moves them too. */
:root {
--icon-size-sm: 0.875rem; /* 14px: beside 11-12px text, and on small icon buttons */
--icon-size-md: 1rem; /* 16px: beside 13-14px text (file rows, window titles), path-bar buttons */
--icon-size-lg: 1.25rem; /* 20px: a spinner standing alone */
--icon-size-xl: 1.5rem; /* 24px: a status mark on a card or an empty pane */
}
/* Soft entrance for the panel busy states (connecting placeholder, load overlay). */