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,10 +1,10 @@
import React from 'react';
import { RiAddLine, RiBrainAi3Line, RiCloseLine, RiSearchLine, RiStarFill, RiTimeLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { isIMECompositionEvent } from '@/lib/ime';
import { useConfigStore } from '@/stores/useConfigStore';
@@ -79,7 +79,7 @@ export const ModelChip: React.FC<{
onClick={onRemove}
className="text-muted-foreground hover:text-foreground ml-0.5"
>
<RiCloseLine className="h-3.5 w-3.5" />
<Icon name="close" className="h-3.5 w-3.5" />
</button>
</div>
);
@@ -349,7 +349,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
setIsOpen(!isOpen);
}}
>
<RiAddLine className="h-3.5 w-3.5 mr-1" />
<Icon name="add" className="h-3.5 w-3.5 mr-1" />
{addButtonLabel ?? t('multirun.modelMultiSelect.actions.addModel')}
</Button>
@@ -426,7 +426,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{/* Search input */}
<div className="p-2 border-b border-border/40">
<div className="relative">
<RiSearchLine className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Input
ref={searchInputRef}
type="text"
@@ -455,7 +455,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{filteredFavorites.length > 0 && (
<>
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider sticky top-0 z-10 -mx-1 flex items-center gap-2 border-b border-border/30 px-3 py-1.5 [background:linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))]">
<RiStarFill className="h-4 w-4 text-primary" />
<Icon name="star-fill" className="h-4 w-4 text-primary" />
{t('multirun.modelMultiSelect.sections.favorites')}
</div>
{filteredFavorites.map(({ model, providerID, modelID }) => {
@@ -470,7 +470,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
<>
{filteredFavorites.length > 0 && <div className="h-px bg-border/40 my-1" />}
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider sticky top-0 z-10 -mx-1 flex items-center gap-2 border-b border-border/30 px-3 py-1.5 [background:linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))]">
<RiTimeLine className="h-4 w-4" />
<Icon name="time" className="h-4 w-4" />
{t('multirun.modelMultiSelect.sections.recent')}
</div>
{filteredRecents.map(({ model, providerID, modelID }) => {
@@ -554,7 +554,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
size="chip"
className="px-2 gap-1.5 rounded-md !border-border/80 !bg-[var(--surface-subtle)] hover:!bg-[var(--interactive-hover)]/70 typography-meta font-medium text-foreground"
>
<RiBrainAi3Line
<Icon name="brain-ai-3"
className={cn(
'h-3.5 w-3.5 flex-shrink-0',
variantValue === DEFAULT_VARIANT_VALUE ? 'text-muted-foreground' : 'text-[color:var(--status-info)]'