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
@@ -1,8 +1,8 @@
import React from 'react';
import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine, RiArrowDownSLine } from '@remixicon/react';
import { isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Icon } from "@/components/icon/Icon";
import { updateDesktopSettings } from '@/lib/persistence';
import { copyTextToClipboard } from '@/lib/clipboard';
import { restartDesktopApp } from '@/lib/desktop';
@@ -39,7 +39,7 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str
title={copyTitle}
aria-label={copyTitle}
>
<RiFileCopyLine className="h-4 w-4" />
<Icon name="file-copy" className="h-4 w-4" />
</button>
</div>
);
@@ -306,7 +306,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
<div className="app-region-no-drag rounded-lg border border-border bg-background/60 backdrop-blur-sm px-4 py-3 font-mono text-sm">
{copied ? (
<div className="flex items-center gap-2" style={{ color: 'var(--status-success)' }}>
<RiCheckLine className="h-4 w-4" />
<Icon name="check" className="h-4 w-4" />
{t('onboarding.common.status.copiedToClipboard')}
</div>
) : (
@@ -322,7 +322,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
className="text-xs text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1"
>
{platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')}
<RiExternalLinkLine className="h-3 w-3" />
<Icon name="external-link" className="h-3 w-3" />
</a>
<button
type="button"
@@ -377,7 +377,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
>
<summary className="flex items-center justify-between cursor-pointer py-2.5 text-sm text-muted-foreground hover:text-foreground transition-colors list-none [&::-webkit-details-marker]:hidden">
<span>{t('onboarding.localSetup.advanced.title')}</span>
<RiArrowDownSLine className="h-4 w-4 transition-transform group-open:rotate-180" />
<Icon name="arrow-down-s" className="h-4 w-4 transition-transform group-open:rotate-180" />
</summary>
<div className="pb-4 space-y-2">
<div className="flex gap-2">
@@ -419,7 +419,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
>
<summary className="flex items-center justify-between cursor-pointer py-2.5 text-sm text-muted-foreground hover:text-foreground transition-colors list-none [&::-webkit-details-marker]:hidden">
<span>{t('onboarding.localSetup.troubleshoot.title')}</span>
<RiArrowDownSLine className="h-4 w-4 transition-transform group-open:rotate-180" />
<Icon name="arrow-down-s" className="h-4 w-4 transition-transform group-open:rotate-180" />
</summary>
<ul className="pb-4 space-y-1.5 text-xs text-muted-foreground list-disc pl-4">
{platform === 'windows' ? (
@@ -1,6 +1,6 @@
import React from 'react';
import { RiRefreshLine, RiServerLine, RiMacbookLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { redactSensitiveUrl } from '@/lib/desktopHosts';
@@ -25,9 +25,9 @@ export type DesktopConnectionRecoveryProps = {
function getRecoveryIcon(iconKey: 'local' | 'remote'): React.ReactNode {
switch (iconKey) {
case 'local':
return <RiMacbookLine className="h-8 w-8" />;
return <Icon name="macbook" className="h-8 w-8" />;
case 'remote':
return <RiServerLine className="h-8 w-8" />;
return <Icon name="server" className="h-8 w-8" />;
}
}
@@ -99,7 +99,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="w-full"
>
<RiRefreshLine className={cn('h-4 w-4', isRetrying && 'animate-spin')} />
<Icon name="refresh" className={cn('h-4 w-4', isRetrying && 'animate-spin')} />
{isRetrying
? t('onboarding.desktopRecovery.actions.retrying')
: t(retryLabelKey)}
@@ -114,7 +114,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="flex-1"
>
<RiMacbookLine className="h-4 w-4" />
<Icon name="macbook" className="h-4 w-4" />
{t(config.useLocalLabelKey as Parameters<typeof t>[0])}
</Button>
)}
@@ -126,7 +126,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="flex-1"
>
<RiServerLine className="h-4 w-4" />
<Icon name="server" className="h-4 w-4" />
{t(config.useRemoteLabelKey as Parameters<typeof t>[0])}
</Button>
)}
@@ -1,8 +1,8 @@
import React from 'react';
import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine } from '@remixicon/react';
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Icon } from "@/components/icon/Icon";
import { updateDesktopSettings } from '@/lib/persistence';
import { copyTextToClipboard } from '@/lib/clipboard';
import { restartDesktopApp } from '@/lib/desktop';
@@ -40,7 +40,7 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str
className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors"
title={copyTitle}
>
<RiFileCopyLine className="h-4 w-4" />
<Icon name="file-copy" className="h-4 w-4" />
</button>
</div>
);
@@ -264,7 +264,7 @@ export function LocalSetupScreen({
<div className="bg-background/60 backdrop-blur-sm border border-border rounded-lg px-5 py-3 font-mono text-sm w-fit">
{copied ? (
<div className="flex items-center justify-center gap-2" style={{ color: 'var(--status-success)' }}>
<RiCheckLine className="h-4 w-4" />
<Icon name="check" className="h-4 w-4" />
{t('onboarding.common.status.copiedToClipboard')}
</div>
) : (
@@ -280,7 +280,7 @@ export function LocalSetupScreen({
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 justify-center"
>
{platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')}
<RiExternalLinkLine className="h-3 w-3" />
<Icon name="external-link" className="h-3 w-3" />
</a>
{checkError && (