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:
@@ -1,12 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiGitBranchLine,
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
RiMore2Line,
|
||||
RiFileCopyLine,
|
||||
RiLoader4Line,
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
@@ -31,6 +23,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
interface AgentGroupDetailProps {
|
||||
@@ -163,7 +156,7 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="typography-heading-lg text-foreground truncate">{group.name}</h1>
|
||||
{groupBusy && <RiLoader4Line className="h-4 w-4 animate-spin text-amber-500 flex-shrink-0" />}
|
||||
{groupBusy && <Icon name="loader-4" className="h-4 w-4 animate-spin text-amber-500 flex-shrink-0" />}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-1 typography-meta text-muted-foreground">
|
||||
<span>
|
||||
@@ -173,7 +166,7 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<RiGitBranchLine className="h-3.5 w-3.5" />
|
||||
<Icon name="git-branch" className="h-3.5 w-3.5" />
|
||||
{selectedSession?.worktreeMetadata?.label || selectedSession?.branch || t('agentManager.detail.header.noBranch')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -209,7 +202,7 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<RiArrowDownSLine className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[var(--anchor-width)]">
|
||||
@@ -237,13 +230,13 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
</div>
|
||||
{session.branch && (
|
||||
<div className="flex items-center gap-1 typography-micro text-muted-foreground/60">
|
||||
<RiGitBranchLine className="h-3 w-3" />
|
||||
<Icon name="git-branch" className="h-3 w-3" />
|
||||
<span className="truncate">{session.worktreeMetadata?.label || session.branch}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedSession?.id === session.id && (
|
||||
<RiCheckLine className="h-4 w-4 text-primary flex-shrink-0" />
|
||||
<Icon name="check" className="h-4 w-4 text-primary flex-shrink-0" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
@@ -254,7 +247,7 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="flex-shrink-0" aria-label={t('agentManager.detail.actions.worktreeActionsAria')}>
|
||||
<RiMore2Line className="h-4 w-4" />
|
||||
<Icon name="more-2" className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-[220px]">
|
||||
@@ -278,7 +271,7 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
}}
|
||||
disabled={!selectedSession?.path}
|
||||
>
|
||||
<RiFileCopyLine className="h-4 w-4 mr-px" />
|
||||
<Icon name="file-copy" className="h-4 w-4 mr-px" />
|
||||
{t('agentManager.detail.actions.copyWorktreePath')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiAddCircleLine,
|
||||
RiAddLine,
|
||||
RiArrowDownSLine,
|
||||
RiCloseLine,
|
||||
RiFileImageLine,
|
||||
RiFileLine,
|
||||
RiGitBranchLine,
|
||||
RiHourglassFill,
|
||||
RiSendPlane2Line,
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -22,6 +11,7 @@ import { BranchSelector, useBranchOptions } from '@/components/multirun/BranchSe
|
||||
import { AgentSelector } from '@/components/multirun/AgentSelector';
|
||||
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
|
||||
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { isIMECompositionEvent } from '@/lib/ime';
|
||||
import { getWorktreeSetupCommands } from '@/lib/openchamberConfig';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
@@ -438,7 +428,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
{/* Branch Selection */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="typography-ui-label font-medium text-foreground flex items-center gap-1.5">
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
{t('agentManager.empty.baseBranch.label')}
|
||||
</label>
|
||||
<BranchSelector
|
||||
@@ -465,7 +455,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
) : null;
|
||||
})()}
|
||||
</p>
|
||||
<RiArrowDownSLine className={cn(
|
||||
<Icon name="arrow-down-s" className={cn(
|
||||
'h-4 w-4 text-muted-foreground transition-transform duration-200',
|
||||
isSetupCommandsOpen && 'rotate-180'
|
||||
)} />
|
||||
@@ -500,7 +490,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
className="flex-shrink-0 flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
aria-label={t('agentManager.empty.setupCommands.removeCommandAria')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -509,7 +499,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
onClick={() => setSetupCommands([...setupCommands, ''])}
|
||||
className="flex items-center gap-1.5 typography-meta text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
<Icon name="add" className="h-3.5 w-3.5" />
|
||||
{t('agentManager.empty.setupCommands.addCommand')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -583,9 +573,9 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
className="inline-flex items-center gap-1.5 px-2 py-1 bg-muted/30 border border-border/30 rounded-md typography-meta"
|
||||
>
|
||||
{file.mimeType.startsWith('image/') ? (
|
||||
<RiFileImageLine className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="file-image" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<RiFileLine className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="file" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
)}
|
||||
<span className="truncate max-w-[120px]" title={file.filename}>
|
||||
{file.filename}
|
||||
@@ -595,7 +585,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
onClick={() => handleRemoveFile(file.id)}
|
||||
className="text-muted-foreground hover:text-destructive ml-0.5"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -620,7 +610,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-foreground transition-colors"
|
||||
aria-label={t('agentManager.empty.prompt.addAttachmentAria')}
|
||||
>
|
||||
<RiAddCircleLine className="h-[18px] w-[18px]" />
|
||||
<Icon name="add-circle" className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -645,9 +635,9 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
||||
aria-label={t('agentManager.empty.actions.startAgentGroupAria')}
|
||||
>
|
||||
{isSubmittingOrCreating ? (
|
||||
<RiHourglassFill className="h-[18px] w-[18px] animate-spin" />
|
||||
<Icon name="hourglass-fill" className="h-[18px] w-[18px] animate-spin" />
|
||||
) : (
|
||||
<RiSendPlane2Line className="h-[18px] w-[18px]" />
|
||||
<Icon name="send-plane-2" className="h-[18px] w-[18px]" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowDownSLine,
|
||||
RiMore2Line,
|
||||
RiSearchLine,
|
||||
RiGitBranchLine,
|
||||
RiLoader4Line,
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -25,6 +17,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore';
|
||||
import { useAllSessionStatuses } from '@/sync/sync-context';
|
||||
@@ -92,11 +85,11 @@ const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, isBu
|
||||
<span className="truncate typography-ui-label font-normal text-foreground">
|
||||
{group.name}
|
||||
</span>
|
||||
{isBusy && <RiLoader4Line className="h-3 w-3 animate-spin text-amber-500 flex-shrink-0" />}
|
||||
{isBusy && <Icon name="loader-4" className="h-3 w-3 animate-spin text-amber-500 flex-shrink-0" />}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="typography-micro text-muted-foreground/60 flex items-center gap-1">
|
||||
<RiGitBranchLine className="h-3 w-3" />
|
||||
<Icon name="git-branch" className="h-3 w-3" />
|
||||
{group.sessionCount === 1
|
||||
? t('agentManager.sidebar.item.modelCountSingle', { count: group.sessionCount })
|
||||
: t('agentManager.sidebar.item.modelCountPlural', { count: group.sessionCount })}
|
||||
@@ -126,7 +119,7 @@ const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, isBu
|
||||
aria-label={t('agentManager.sidebar.item.groupMenuAria')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RiMore2Line className="h-3.5 w-3.5" />
|
||||
<Icon name="more-2" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-[140px]">
|
||||
@@ -218,7 +211,7 @@ export const AgentManagerSidebar: React.FC<AgentManagerSidebarProps> = ({
|
||||
{/* Search Input */}
|
||||
<div className="px-2.5 pt-3 pb-2">
|
||||
<div className="relative">
|
||||
<RiSearchLine className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
@@ -235,14 +228,14 @@ export const AgentManagerSidebar: React.FC<AgentManagerSidebarProps> = ({
|
||||
className="w-full justify-start gap-2 h-8"
|
||||
onClick={onNewAgent}
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
<span className="typography-ui-label">{t('agentManager.sidebar.actions.newAgentGroup')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Agent Groups Section Header */}
|
||||
<div className="px-2.5 py-1.5 flex items-center gap-1">
|
||||
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="typography-micro font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{t('agentManager.sidebar.section.agentGroups')}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user