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:
@@ -23,18 +23,6 @@ import {
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from '@/components/ui/command';
|
||||
import {
|
||||
RiGitBranchLine,
|
||||
RiGitRepositoryLine,
|
||||
RiGithubLine,
|
||||
RiLoader4Line,
|
||||
RiRefreshLine,
|
||||
RiErrorWarningLine,
|
||||
RiCheckLine,
|
||||
RiExternalLinkLine,
|
||||
RiCloseLine,
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
@@ -58,6 +46,7 @@ import { useGitBranches, useGitStore, useGitLoadingBranches } from '@/stores/use
|
||||
import { GitHubIntegrationDialog } from './GitHubIntegrationDialog';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import type {
|
||||
GitHubIssue,
|
||||
GitHubIssueComment,
|
||||
@@ -1037,7 +1026,7 @@ export function NewWorktreeDialog({
|
||||
<div className={cn('flex items-center gap-1.5 text-destructive', isMobile ? 'w-full justify-center order-first' : 'mr-auto')}>
|
||||
{validation.touched && (validation.branchError || validation.worktreeError) && (
|
||||
<>
|
||||
<RiErrorWarningLine className="h-3.5 w-3.5" />
|
||||
<Icon name="error-warning" className="h-3.5 w-3.5" />
|
||||
<span className="typography-micro">
|
||||
{validation.branchError || validation.worktreeError}
|
||||
</span>
|
||||
@@ -1062,7 +1051,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canCreate || isCreating}
|
||||
className={cn('gap-1.5', isMobile && 'flex-1')}
|
||||
>
|
||||
{isCreating && <RiLoader4Line className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating && <Icon name="loader-4" className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1082,8 +1071,8 @@ export function NewWorktreeDialog({
|
||||
<div className="w-full mb-4">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <RiGitRepositoryLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <Icon name="git-repository" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={mode}
|
||||
onSelect={(id) => handleModeChange(id as Mode)}
|
||||
@@ -1110,7 +1099,7 @@ export function NewWorktreeDialog({
|
||||
<span className={existingBranchState.selectedBranch ? 'text-foreground' : 'text-muted-foreground'}>
|
||||
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
|
||||
</span>
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -1120,7 +1109,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canFetchBranches || isLoadingBranches}
|
||||
title={t('session.newWorktree.fetchBranches')}
|
||||
>
|
||||
{isLoadingBranches ? <RiLoader4Line className="size-4 animate-spin" /> : <RiRefreshLine className="size-4" />}
|
||||
{isLoadingBranches ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="refresh" className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1266,7 +1255,7 @@ export function NewWorktreeDialog({
|
||||
onClick={() => setGithubDialogOpen(true)}
|
||||
className="gap-1.5 h-7"
|
||||
>
|
||||
<RiGithubLine className="size-4 text-status-success" />
|
||||
<Icon name="github" className="size-4 text-status-success" />
|
||||
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -1293,7 +1282,7 @@ export function NewWorktreeDialog({
|
||||
/>
|
||||
{newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
|
||||
</span>
|
||||
@@ -1301,7 +1290,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
|
||||
</span>
|
||||
@@ -1335,7 +1324,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
title={t('session.newWorktree.resetToMatchBranchName')}
|
||||
>
|
||||
<RiRefreshLine className="h-3 w-3" />
|
||||
<Icon name="refresh" className="h-3 w-3" />
|
||||
<span>{t('session.newWorktree.actions.reset')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -1380,7 +1369,7 @@ export function NewWorktreeDialog({
|
||||
<span className={newBranchState.sourceBranch ? 'text-foreground' : 'text-muted-foreground'}>
|
||||
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
|
||||
</span>
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
{newBranchState.sourceBranch && (
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
@@ -1509,7 +1498,7 @@ export function NewWorktreeDialog({
|
||||
<div className="mt-2 px-2 py-1.5 rounded bg-muted/30">
|
||||
{/* Row 1: Type, number, title, actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="github" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
|
||||
{newBranchState.linkedIssue && (
|
||||
<span className="typography-micro text-muted-foreground shrink-0">
|
||||
@@ -1533,14 +1522,14 @@ export function NewWorktreeDialog({
|
||||
className="text-muted-foreground hover:text-foreground shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RiExternalLinkLine className="h-3 w-3" />
|
||||
<Icon name="external-link" className="h-3 w-3" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={handleClearLinkedItem}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1567,7 +1556,7 @@ export function NewWorktreeDialog({
|
||||
<DialogHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<DialogTitle className="flex items-center gap-2 shrink-0">
|
||||
<RiGitBranchLine className="h-5 w-5" />
|
||||
<Icon name="git-branch" className="h-5 w-5" />
|
||||
{t('session.newWorktree.title')}
|
||||
</DialogTitle>
|
||||
|
||||
@@ -1575,8 +1564,8 @@ export function NewWorktreeDialog({
|
||||
<div className="w-[280px] shrink-0">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <RiGitRepositoryLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <Icon name="git-repository" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={mode}
|
||||
onSelect={(id) => handleModeChange(id as Mode)}
|
||||
@@ -1602,7 +1591,7 @@ export function NewWorktreeDialog({
|
||||
<span className={cn('truncate', existingBranchState.selectedBranch ? 'text-foreground' : 'text-muted-foreground')}>
|
||||
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={6} portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
|
||||
@@ -1714,7 +1703,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canFetchBranches || isLoadingBranches}
|
||||
title={t('session.newWorktree.fetchBranches')}
|
||||
>
|
||||
{isLoadingBranches ? <RiLoader4Line className="size-4 animate-spin" /> : <RiRefreshLine className="size-4" />}
|
||||
{isLoadingBranches ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="refresh" className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1731,7 +1720,7 @@ export function NewWorktreeDialog({
|
||||
onClick={() => setGithubDialogOpen(true)}
|
||||
className="gap-1.5 h-7"
|
||||
>
|
||||
<RiGithubLine className="size-4 text-status-success" />
|
||||
<Icon name="github" className="size-4 text-status-success" />
|
||||
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -1758,7 +1747,7 @@ export function NewWorktreeDialog({
|
||||
/>
|
||||
{newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
|
||||
</span>
|
||||
@@ -1766,7 +1755,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
|
||||
</span>
|
||||
@@ -1800,7 +1789,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
title={t('session.newWorktree.resetToMatchBranchName')}
|
||||
>
|
||||
<RiRefreshLine className="h-3 w-3" />
|
||||
<Icon name="refresh" className="h-3 w-3" />
|
||||
<span>{t('session.newWorktree.actions.reset')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -1842,7 +1831,7 @@ export function NewWorktreeDialog({
|
||||
<span className={cn('truncate', newBranchState.sourceBranch ? 'text-foreground' : 'text-muted-foreground')}>
|
||||
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={sourceBranchDropdownContentRef}>
|
||||
@@ -1944,7 +1933,7 @@ export function NewWorktreeDialog({
|
||||
<div className="mt-2 px-2 py-1.5 rounded bg-muted/30">
|
||||
{/* Row 1: Type, number, title, actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="github" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
|
||||
{newBranchState.linkedIssue && (
|
||||
<span className="typography-micro text-muted-foreground shrink-0">
|
||||
@@ -1968,14 +1957,14 @@ export function NewWorktreeDialog({
|
||||
className="text-muted-foreground hover:text-foreground shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RiExternalLinkLine className="h-3 w-3" />
|
||||
<Icon name="external-link" className="h-3 w-3" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={handleClearLinkedItem}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2002,7 +1991,7 @@ export function NewWorktreeDialog({
|
||||
<div className="flex items-center gap-1.5 text-destructive">
|
||||
{validation.touched && (validation.branchError || validation.worktreeError) && (
|
||||
<>
|
||||
<RiErrorWarningLine className="h-3.5 w-3.5" />
|
||||
<Icon name="error-warning" className="h-3.5 w-3.5" />
|
||||
<span className="typography-micro">
|
||||
{validation.branchError || validation.worktreeError}
|
||||
</span>
|
||||
@@ -2025,7 +2014,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canCreate || isCreating}
|
||||
className="gap-1.5"
|
||||
>
|
||||
{isCreating && <RiLoader4Line className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating && <Icon name="loader-4" className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user