perf(ui): migrate icons to SVG sprite system

Replace @remixicon/react with a shared Icon component that renders
via <use href> references to a single hidden SVG sprite. This reduces
DOM node count by replacing inline SVGs with lightweight references.

- Create Icon component with sprite injection (packages/ui/src/components/icon/)
- Migrate all 164 files from @remixicon/react to Icon component
- Auto-generate sprite data from remixicon bundle (scripts/generate-icon-sprite.mjs)
- Add bun run icons:generate to package.json
- Move @remixicon/react to devDependencies
- Add icon usage instructions to theme-system skill
This commit is contained in:
Bohdan Triapitsyn
2026-05-13 13:26:35 +03:00
parent b4cbd7f0d6
commit 14357257ae
173 changed files with 2342 additions and 2227 deletions
@@ -10,22 +10,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
RiAddLine,
RiArrowDownSLine,
RiArrowRightLine,
RiComputerLine,
RiExternalLinkLine,
RiFileCopyLine,
RiInformationLine,
RiPlug2Line,
RiRefreshLine,
RiServerLine,
RiShuffleLine,
RiTerminalWindowLine,
RiDeleteBinLine,
RiStopLine,
} from '@remixicon/react';
import {
Dialog,
DialogContent,
@@ -39,6 +23,7 @@ import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLay
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import { copyTextToClipboard } from '@/lib/clipboard';
import { openExternalUrl } from '@/lib/url';
import { useI18n, type I18nKey } from '@/lib/i18n';
@@ -152,7 +137,7 @@ const HintLabel: React.FC<{ label: string; hint: React.ReactNode }> = ({ label,
<span>{label}</span>
<Tooltip>
<TooltipTrigger asChild>
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
</TooltipTrigger>
<TooltipContent sideOffset={8} className="max-w-xs">
<div className="typography-meta text-foreground">{hint}</div>
@@ -808,7 +793,7 @@ export const RemoteInstancesPage: React.FC = () => {
onClick={handlePrimaryConnectionAction}
disabled={isPrimaryActionPending || isRetryPending}
>
{canDisconnect ? <RiStopLine className="h-3.5 w-3.5" /> : <RiPlug2Line className="h-3.5 w-3.5" />}
{canDisconnect ? <Icon name="stop" className="h-3.5 w-3.5" /> : <Icon name="plug-2" className="h-3.5 w-3.5" />}
{primaryButtonLabel}
</Button>
<Button
@@ -819,7 +804,7 @@ export const RemoteInstancesPage: React.FC = () => {
onClick={handleRetryAction}
disabled={!canRetry}
>
<RiRefreshLine className={`h-3.5 w-3.5 ${isConnecting || (isReconnecting && !reconnectAppearsStuck) ? 'animate-spin' : ''}`} />
<Icon name="refresh" className={`h-3.5 w-3.5 ${isConnecting || (isReconnecting && !reconnectAppearsStuck) ? 'animate-spin' : ''}`} />
{retryButtonLabel}
</Button>
<Button
@@ -831,7 +816,7 @@ export const RemoteInstancesPage: React.FC = () => {
void handleOpenLogs();
}}
>
<RiTerminalWindowLine className="h-3.5 w-3.5" />
<Icon name="terminal-window" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.logs')}
</Button>
<Button
@@ -854,7 +839,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.sidebar.actions.remove')}
</Button>
</div>
@@ -1148,7 +1133,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
<RiShuffleLine className="h-3.5 w-3.5" />
<Icon name="shuffle" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1265,7 +1250,7 @@ export const RemoteInstancesPage: React.FC = () => {
<div className="flex items-center justify-between gap-2">
<div className="min-w-0 flex items-center gap-2">
<CollapsibleTrigger className="flex items-center gap-2 group">
<RiArrowDownSLine className={`h-4 w-4 text-muted-foreground transition-transform ${isForwardOpen ? 'rotate-180' : ''}`} />
<Icon name="arrow-down-s" className={`h-4 w-4 text-muted-foreground transition-transform ${isForwardOpen ? 'rotate-180' : ''}`} />
<span className="typography-ui-label text-foreground truncate">{buildForwardLabel(forward)}</span>
<span className="typography-micro text-muted-foreground/70 shrink-0">{typeLabel}</span>
</CollapsibleTrigger>
@@ -1284,7 +1269,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
</Button>
</div>
</div>
@@ -1406,27 +1391,27 @@ export const RemoteInstancesPage: React.FC = () => {
<div className="flex flex-wrap items-center gap-1 typography-micro text-muted-foreground/80">
{forward.type === 'dynamic' ? (
<>
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.localSocks5')}</span>
</>
) : forward.type === 'remote' ? (
<>
<RiServerLine className="h-3.5 w-3.5" />
<Icon name="server" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{remoteEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.remote')}</span>
<RiArrowRightLine className="h-3.5 w-3.5" />
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="arrow-right" className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.local')}</span>
</>
) : (
<>
<RiComputerLine className="h-3.5 w-3.5" />
<Icon name="computer" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{localEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.local')}</span>
<RiArrowRightLine className="h-3.5 w-3.5" />
<RiServerLine className="h-3.5 w-3.5" />
<Icon name="arrow-right" className="h-3.5 w-3.5" />
<Icon name="server" className="h-3.5 w-3.5" />
<span className="font-mono text-foreground">{remoteEndpoint}</span>
<span>{t('settings.remoteInstances.page.preview.remote')}</span>
</>
@@ -1447,7 +1432,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.openLocal')}
</Button>
) : null}
@@ -1475,7 +1460,7 @@ export const RemoteInstancesPage: React.FC = () => {
}));
}}
>
<RiAddLine className="h-3.5 w-3.5" />
<Icon name="add" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.addForward')}
</Button>
</section>
@@ -1540,7 +1525,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
<RiFileCopyLine className="h-3.5 w-3.5" />
<Icon name="file-copy" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.copyLocalUrl')}
</Button>
<Button
@@ -1552,7 +1537,7 @@ export const RemoteInstancesPage: React.FC = () => {
void handleOpenCurrentInstance();
}}
>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
{t('settings.remoteInstances.page.actions.open')}
</Button>
</>
@@ -1571,11 +1556,11 @@ export const RemoteInstancesPage: React.FC = () => {
</DialogHeader>
<div className="flex items-center justify-end gap-2">
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={handleCopyAllLogs} disabled={logDialogLoading || !logLinesText.trim()}>
<RiFileCopyLine className="h-3.5 w-3.5" />
<Icon name="file-copy" className="h-3.5 w-3.5" />
{t('settings.common.actions.copyAll')}
</Button>
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={() => void handleClearLogs()} disabled={logDialogLoading}>
<RiDeleteBinLine className="h-3.5 w-3.5" />
<Icon name="delete-bin" className="h-3.5 w-3.5" />
{t('settings.common.actions.clear')}
</Button>
</div>
@@ -1,11 +1,11 @@
import React from 'react';
import { RiAddLine, RiDeleteBinLine, RiPlug2Line, RiRefreshLine, RiStopLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout';
import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem';
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import type { DesktopSshInstance } from '@/lib/desktopSsh';
import { useI18n } from '@/lib/i18n';
@@ -145,7 +145,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
onClick={() => void handleAdd()}
aria-label={t('settings.remoteInstances.sidebar.actions.addSshInstance')}
>
<RiAddLine className="size-4" />
<Icon name="add" className="size-4" />
</Button>
</div>
</div>
@@ -172,7 +172,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
actions={[
{
label: isReady ? t('settings.remoteInstances.sidebar.actions.disconnect') : t('settings.remoteInstances.sidebar.actions.connect'),
icon: isReady ? RiStopLine : RiPlug2Line,
icon: isReady ? 'stop' : 'plug-2',
onClick: () => {
const op = isReady ? disconnect(instance.id) : connectWithPortRecovery(instance);
void op.catch((error) => {
@@ -189,7 +189,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.retry'),
icon: RiRefreshLine,
icon: "refresh",
onClick: () => {
if (!canRetry) return;
void retry(instance.id).catch((error) => {
@@ -201,7 +201,7 @@ export const RemoteInstancesSidebar: React.FC<RemoteInstancesSidebarProps> = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.remove'),
icon: RiDeleteBinLine,
icon: "delete-bin",
destructive: true,
onClick: () => {
void removeInstance(instance.id).then(() => {