Merge pull request #55468 from NousResearch/bb/desktop-icon-size-token

refactor(desktop): add iconSize token, migrate ad-hoc icon sizes onto it
This commit is contained in:
brooklyn! 2026-06-30 02:19:28 -05:00 committed by GitHub
commit 8f8487b54f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 42 additions and 34 deletions

View file

@ -4,7 +4,7 @@ import { KbdCombo } from '@/components/ui/kbd'
import { Tip } from '@/components/ui/tooltip'
import { useI18n } from '@/i18n'
import { triggerHaptic } from '@/lib/haptics'
import { AudioLines, Layers3, Loader2, Square, SteeringWheel, Volume2, VolumeX } from '@/lib/icons'
import { AudioLines, iconSize, Layers3, Loader2, Square, SteeringWheel, Volume2, VolumeX } from '@/lib/icons'
import { formatCombo } from '@/lib/keybinds/combo'
import { cn } from '@/lib/utils'
@ -103,7 +103,7 @@ export function ComposerControls({
type="button"
variant="ghost"
>
<SteeringWheel size={14} />
<SteeringWheel className={iconSize.sm} />
</Button>
</Tip>
) : (
@ -123,7 +123,7 @@ export function ComposerControls({
size="icon"
type="button"
>
<AudioLines size={15} />
<AudioLines className={iconSize.sm} />
</Button>
</Tip>
) : (
@ -136,7 +136,7 @@ export function ComposerControls({
>
{busy ? (
busyAction === 'queue' ? (
<Layers3 size={14} />
<Layers3 className={iconSize.sm} />
) : (
<span className="block size-2.5 rounded-[0.1875rem] bg-current" />
)
@ -207,7 +207,7 @@ function ConversationPill({
type="button"
variant="ghost"
>
<Square className="fill-current" size={11} />
<Square className={cn('fill-current', iconSize.xs)} />
<span>{c.stopShort}</span>
</Button>
)}
@ -242,7 +242,7 @@ function ConversationIndicator({
speaking: boolean
}) {
if (speaking) {
return <Loader2 className="animate-spin" size={12} />
return <Loader2 className={cn('animate-spin', iconSize.xs)} />
}
const bars = [0.55, 0.85, 1, 0.85, 0.55]
@ -262,15 +262,7 @@ function ConversationIndicator({
// Pure-TTS toggle: type normally, but have every assistant reply read aloud —
// no dictation, no full conversation loop. Filled/accent when on, mirroring the
// muted-mic pressed state above. Driven by (and persisted to) `voice.auto_tts`.
function AutoSpeakButton({
active,
disabled,
onToggle
}: {
active: boolean
disabled: boolean
onToggle: () => void
}) {
function AutoSpeakButton({ active, disabled, onToggle }: { active: boolean; disabled: boolean; onToggle: () => void }) {
const { t } = useI18n()
const c = t.composer
const label = active ? c.stopSpeakingReplies : c.speakReplies
@ -294,7 +286,7 @@ function AutoSpeakButton({
type="button"
variant="ghost"
>
{active ? <Volume2 size={14} /> : <VolumeX size={14} />}
{active ? <Volume2 className={iconSize.sm} /> : <VolumeX className={iconSize.sm} />}
</Button>
</Tip>
)
@ -341,9 +333,9 @@ function DictationButton({
variant="ghost"
>
{status === 'recording' ? (
<Square className="fill-current" size={11} />
<Square className={cn('fill-current', iconSize.xs)} />
) : status === 'transcribing' ? (
<Loader2 className="animate-spin" size={14} />
<Loader2 className={cn('animate-spin', iconSize.sm)} />
) : (
<Codicon name="mic" size="0.875rem" />
)}

View file

@ -3,7 +3,7 @@ import { StatusSection } from '@/components/chat/status-section'
import { Button } from '@/components/ui/button'
import { Tip } from '@/components/ui/tooltip'
import { type Translations, useI18n } from '@/i18n'
import { ArrowUp, Pencil, Trash2 } from '@/lib/icons'
import { ArrowUp, iconSize, Pencil, Trash2 } from '@/lib/icons'
import { cn } from '@/lib/utils'
import type { QueuedPromptEntry } from '@/store/composer-queue'
@ -52,7 +52,7 @@ export function QueuePanel({ busy, editingId, entries, onDelete, onEdit, onSendN
type="button"
variant="ghost"
>
<Pencil size={11} />
<Pencil className={iconSize.xs} />
</Button>
</Tip>
<Tip label={busy ? c.queueSendNext : c.queueSend}>
@ -65,7 +65,7 @@ export function QueuePanel({ busy, editingId, entries, onDelete, onEdit, onSendN
type="button"
variant="ghost"
>
<ArrowUp size={11} />
<ArrowUp className={iconSize.xs} />
</Button>
</Tip>
<Tip label={c.queueDelete}>
@ -77,7 +77,7 @@ export function QueuePanel({ busy, editingId, entries, onDelete, onEdit, onSendN
type="button"
variant="ghost"
>
<Trash2 size={11} />
<Trash2 className={iconSize.xs} />
</Button>
</Tip>
</>

View file

@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'
import { Button } from '@/components/ui/button'
import { useI18n } from '@/i18n'
import { Loader2, Mic, Volume2, VolumeX } from '@/lib/icons'
import { iconSize, Loader2, Mic, Volume2, VolumeX } from '@/lib/icons'
import { cn } from '@/lib/utils'
import { stopVoicePlayback } from '@/lib/voice-playback'
import { $voicePlayback } from '@/store/voice-playback'
@ -188,7 +188,7 @@ export function VoiceActivity({ state }: { state: VoiceActivityState }) {
recording ? 'bg-primary/15 text-primary' : 'bg-primary/10 text-primary'
)}
>
{recording ? <Mic size={12} /> : <Loader2 className="animate-spin" size={12} />}
{recording ? <Mic className={iconSize.xs} /> : <Loader2 className={cn('animate-spin', iconSize.xs)} />}
</div>
<div className="flex min-w-0 flex-1 items-center gap-2">
@ -229,7 +229,7 @@ export function VoicePlaybackActivity() {
role="status"
>
<div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary">
{preparing ? <Loader2 className="animate-spin" size={12} /> : <Volume2 size={12} />}
{preparing ? <Loader2 className={cn('animate-spin', iconSize.xs)} /> : <Volume2 className={iconSize.xs} />}
</div>
<div className="flex min-w-0 flex-1 items-center gap-2">
@ -244,7 +244,7 @@ export function VoicePlaybackActivity() {
type="button"
variant="ghost"
>
<VolumeX size={12} />
<VolumeX className={iconSize.xs} />
Stop
</Button>
</div>

View file

@ -11,7 +11,7 @@ import type {
DesktopBootstrapState
} from '@/global'
import { useI18n } from '@/i18n'
import { AlertTriangle, Check, ChevronDown, ChevronRight, Loader2 } from '@/lib/icons'
import { AlertTriangle, Check, ChevronDown, ChevronRight, iconSize, Loader2 } from '@/lib/icons'
import { cn } from '@/lib/utils'
/**
@ -109,16 +109,16 @@ function StageRow({ descriptor, result, isCurrent, now }: StageRowProps) {
const icon = useMemo(() => {
switch (state) {
case 'running':
return <Loader2 className="h-4 w-4 animate-spin text-primary" />
return <Loader2 className={cn(iconSize.md, 'animate-spin text-primary')} />
case 'succeeded':
return <Check className="h-4 w-4 text-emerald-600" />
return <Check className={cn(iconSize.md, 'text-emerald-600')} />
case 'skipped':
return <Check className="h-4 w-4 text-muted-foreground" />
return <Check className={cn(iconSize.md, 'text-muted-foreground')} />
case 'failed':
return <AlertTriangle className="h-4 w-4 text-destructive" />
return <AlertTriangle className={cn(iconSize.md, 'text-destructive')} />
case 'pending':
@ -455,7 +455,7 @@ export function DesktopInstallOverlay({ enabled = true }: DesktopInstallOverlayP
{failed && state.error && (
<div className="mb-4 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-sm">
<div className="mb-1 flex items-center gap-1.5 font-medium text-destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTriangle className={iconSize.md} />
<span>{copy.error}</span>
</div>
<p className="whitespace-pre-wrap break-words text-foreground/90">{state.error}</p>
@ -484,7 +484,7 @@ export function DesktopInstallOverlay({ enabled = true }: DesktopInstallOverlayP
type="button"
variant="ghost"
>
{logOpen ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
{logOpen ? <ChevronDown className={iconSize.sm} /> : <ChevronRight className={iconSize.sm} />}
<span>{logOpen ? copy.hideOutput : copy.showOutput}</span>
<span className="ml-1 tabular-nums">({copy.lines(state.log.length)})</span>
</Button>
@ -527,7 +527,7 @@ export function DesktopInstallOverlay({ enabled = true }: DesktopInstallOverlayP
size="sm"
variant="ghost"
>
{cancelling ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
{cancelling ? <Loader2 className={cn(iconSize.md, 'animate-spin')} /> : null}
{cancelling ? copy.cancelling : copy.cancelInstall}
</Button>
</div>

View file

@ -225,3 +225,19 @@ export {
}
export type { Icon as IconComponent } from '@tabler/icons-react'
/**
* Named icon-size scale the single source of truth for SVG icon dimensions,
* replacing ad-hoc `h-N w-N` / `size={N}`. Use `<Icon className={iconSize.sm} />`
* (Tailwind `size-*` sets w+h and beats the icon's default 24px); compose with
* `cn()` for colour/animation classes.
*/
export const iconSize = {
xs: 'size-3', // 12px
sm: 'size-3.5', // 14px
md: 'size-4', // 16px
lg: 'size-5', // 20px
xl: 'size-6' // 24px
} as const
export type IconSize = keyof typeof iconSize