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,23 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiChat4Line,
|
||||
RiCheckLine,
|
||||
RiCheckboxCircleLine,
|
||||
RiAiGenerate2,
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
RiCloseLine,
|
||||
RiEditLine,
|
||||
RiErrorWarningLine,
|
||||
RiExternalLinkLine,
|
||||
RiGitClosePullRequestLine,
|
||||
RiGitMergeLine,
|
||||
RiGitPrDraftLine,
|
||||
RiGitPullRequestLine,
|
||||
RiInformationLine,
|
||||
RiLoader4Line,
|
||||
RiRefreshLine,
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -45,6 +26,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useSelectionStore } from '@/sync/selection-store';
|
||||
@@ -770,7 +752,7 @@ export const PullRequestSection: React.FC<{
|
||||
{run.detailsUrl ? (
|
||||
<Button variant="outline" size="sm" asChild className="flex-shrink-0">
|
||||
<a href={run.detailsUrl} target="_blank" rel="noopener noreferrer">
|
||||
<RiExternalLinkLine className="size-4" />
|
||||
<Icon name="external-link" className="size-4" />
|
||||
Open
|
||||
</a>
|
||||
</Button>
|
||||
@@ -859,7 +841,7 @@ export const PullRequestSection: React.FC<{
|
||||
(isFail ? 'bg-destructive/10 text-destructive' : 'text-muted-foreground')
|
||||
}
|
||||
>
|
||||
{stepExpanded ? <RiArrowDownSLine className="size-4" /> : <RiArrowRightSLine className="size-4" />}
|
||||
{stepExpanded ? <Icon name="arrow-down-s" className="size-4" /> : <Icon name="arrow-right-s" className="size-4" />}
|
||||
<span className="truncate">{step.name}</span>
|
||||
{step.conclusion ? <span className="ml-auto flex-shrink-0">{step.conclusion}</span> : null}
|
||||
</button>
|
||||
@@ -1385,13 +1367,13 @@ export const PullRequestSection: React.FC<{
|
||||
const shouldShowConnectionNotice = githubAuthChecked && status?.connected === false;
|
||||
const prVisualState = getPrVisualState(status);
|
||||
const prColorVar = prVisualState ? `var(--pr-${prVisualState})` : 'var(--status-info)';
|
||||
const PrStateIcon = prVisualState === 'draft'
|
||||
? RiGitPrDraftLine
|
||||
const prStateIconName = prVisualState === 'draft'
|
||||
? 'git-pr-draft'
|
||||
: prVisualState === 'merged'
|
||||
? RiGitMergeLine
|
||||
? 'git-merge'
|
||||
: prVisualState === 'closed'
|
||||
? RiGitClosePullRequestLine
|
||||
: RiGitPullRequestLine;
|
||||
? 'git-close-pull-request'
|
||||
: 'git-pull-request';
|
||||
const prStatusText = pr
|
||||
? [
|
||||
`${pr.state}${pr.draft ? ' (draft)' : ''}`,
|
||||
@@ -1424,13 +1406,13 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={() => void openExternal(pr.url)}
|
||||
aria-label={t('gitView.pr.actions.openOnGitHubAria')}
|
||||
>
|
||||
<PrStateIcon className="size-4 shrink-0" style={{ color: prColorVar }} />
|
||||
<Icon name={prStateIconName} className="size-4 shrink-0" style={{ color: prColorVar }} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openOnGitHub')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<PrStateIcon className="size-4 shrink-0" style={{ color: 'var(--surface-muted-foreground)' }} />
|
||||
<Icon name={prStateIconName} className="size-4 shrink-0" style={{ color: 'var(--surface-muted-foreground)' }} />
|
||||
)}
|
||||
<h3 className="typography-ui-header font-semibold text-foreground truncate">{t('gitView.pullRequest.title')}</h3>
|
||||
{pr ? (
|
||||
@@ -1438,7 +1420,7 @@ export const PullRequestSection: React.FC<{
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{isLoading ? <RiLoader4Line className="size-4 animate-spin text-muted-foreground" /> : null}
|
||||
{isLoading ? <Icon name="loader-4" className="size-4 animate-spin text-muted-foreground" /> : null}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -1448,7 +1430,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={() => void refresh({ force: true })}
|
||||
aria-label={t('gitView.pr.actions.refreshAria')}
|
||||
>
|
||||
<RiRefreshLine className="size-3.5 text-muted-foreground" />
|
||||
<Icon name="refresh" className="size-3.5 text-muted-foreground" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.refresh')}</p></TooltipContent>
|
||||
@@ -1493,7 +1475,7 @@ export const PullRequestSection: React.FC<{
|
||||
{repoUrl ? (
|
||||
<Button variant="outline" size="sm" asChild className="w-fit">
|
||||
<a href={repoUrl} target="_blank" rel="noopener noreferrer">
|
||||
<RiExternalLinkLine className="size-4" />
|
||||
<Icon name="external-link" className="size-4" />
|
||||
Open Repo
|
||||
</a>
|
||||
</Button>
|
||||
@@ -1503,7 +1485,7 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
{!pr && !isInitialStatusResolved && !error && !shouldShowConnectionNotice ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<RiLoader4Line className="size-4 animate-spin" />
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
{t('gitView.pr.checkingStatus')}
|
||||
</div>
|
||||
) : pr ? (
|
||||
@@ -1574,7 +1556,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isUpdating}
|
||||
aria-label={t('gitView.pr.actions.cancelEditingAria')}
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<Icon name="close" className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.cancelEditing')}</p></TooltipContent>
|
||||
@@ -1588,7 +1570,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isUpdating || !editTitle.trim()}
|
||||
aria-label={t('gitView.pr.actions.savePrAria')}
|
||||
>
|
||||
{isUpdating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiCheckLine className="size-4" />}
|
||||
{isUpdating ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="check" className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.savePr')}</p></TooltipContent>
|
||||
@@ -1604,7 +1586,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={() => setIsEditingPr(true)}
|
||||
aria-label={t('gitView.pr.actions.editPrAria')}
|
||||
>
|
||||
<RiEditLine className="size-4" />
|
||||
<Icon name="edit" className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.editPr')}</p></TooltipContent>
|
||||
@@ -1623,7 +1605,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isLoadingCheckDetails}
|
||||
aria-label={t('gitView.pr.actions.openChecksAria')}
|
||||
>
|
||||
{isLoadingCheckDetails ? <RiLoader4Line className="size-4 animate-spin" /> : <RiInformationLine className="size-4" />}
|
||||
{isLoadingCheckDetails ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="information" className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openChecks')}</p></TooltipContent>
|
||||
@@ -1640,7 +1622,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={sendFailedChecksToChat}
|
||||
aria-label={t('gitView.pr.actions.resolveFailedChecksAria')}
|
||||
>
|
||||
<RiErrorWarningLine className="size-4" />
|
||||
<Icon name="error-warning" className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.resolveFailedChecks')}</p></TooltipContent>
|
||||
@@ -1656,7 +1638,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={openCommentsDialog}
|
||||
aria-label={t('gitView.pr.actions.openCommentsAria')}
|
||||
>
|
||||
<RiChat4Line className="size-4" />
|
||||
<Icon name="chat-4" className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openComments')}</p></TooltipContent>
|
||||
@@ -1671,7 +1653,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={sendCommentsToChat}
|
||||
aria-label={t('gitView.pr.actions.shareCommentsAria')}
|
||||
>
|
||||
<RiAiGenerate2 className="size-4" />
|
||||
<Icon name="ai-generate-2" className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.shareComments')}</p></TooltipContent>
|
||||
@@ -1688,7 +1670,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isMarkingReady || isMerging || isUpdating || isEditingPr}
|
||||
aria-label={t('gitView.pr.actions.markReadyAria')}
|
||||
>
|
||||
{isMarkingReady ? <RiLoader4Line className="size-4 animate-spin" /> : <RiCheckboxCircleLine className="size-4" />}
|
||||
{isMarkingReady ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="checkbox-circle" className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.markReady')}</p></TooltipContent>
|
||||
@@ -1722,7 +1704,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isMerging || isMarkingReady || pr.state !== 'open' || pr.draft || isUpdating || isEditingPr}
|
||||
aria-label={t('gitView.pr.actions.mergePrAria')}
|
||||
>
|
||||
{isMerging ? <RiLoader4Line className="size-4 animate-spin" /> : <RiGitMergeLine className="size-4" />}
|
||||
{isMerging ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="git-merge" className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.mergePr')}</p></TooltipContent>
|
||||
@@ -1745,7 +1727,7 @@ export const PullRequestSection: React.FC<{
|
||||
{repoUrl ? (
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href={repoUrl} target="_blank" rel="noopener noreferrer">
|
||||
<RiExternalLinkLine className="size-4" />
|
||||
<Icon name="external-link" className="size-4" />
|
||||
{t('gitView.pr.actions.repo')}
|
||||
</a>
|
||||
</Button>
|
||||
@@ -1906,7 +1888,7 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={generateDescription}
|
||||
disabled={isGenerating || isCreating}
|
||||
>
|
||||
{isGenerating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiAiGenerate2 className="size-4 text-primary" />}
|
||||
{isGenerating ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="ai-generate-2" className="size-4 text-primary" />}
|
||||
{t('gitView.commit.generate')}
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
@@ -1917,7 +1899,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isCreating || !isConnected || !targetBaseBranch.trim() || (!useDetectedUpstream && targetBaseBranch.trim() === branch)}
|
||||
>
|
||||
<span className="inline-flex size-4 items-center justify-center">
|
||||
{isCreating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiGitPullRequestLine className="size-4" />}
|
||||
{isCreating ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="git-pull-request" className="size-4" />}
|
||||
</span>
|
||||
<span>{t('gitView.pr.actions.createPr')}</span>
|
||||
</Button>
|
||||
@@ -1930,7 +1912,7 @@ export const PullRequestSection: React.FC<{
|
||||
<DialogContent className="max-w-2xl max-h-[70vh] flex flex-col min-h-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGitPullRequestLine className="h-5 w-5" />
|
||||
<Icon name="git-pull-request" className="h-5 w-5" />
|
||||
{t('gitView.pr.checkDetails.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -1941,7 +1923,7 @@ export const PullRequestSection: React.FC<{
|
||||
<div className="flex-1 min-h-0 overflow-y-auto mt-2">
|
||||
{isLoadingCheckDetails ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('gitView.loading.loading')}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1971,7 +1953,7 @@ export const PullRequestSection: React.FC<{
|
||||
<DialogContent className="max-w-2xl max-h-[82vh] min-h-[38rem] flex flex-col gap-2">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGitPullRequestLine className="h-5 w-5" />
|
||||
<Icon name="git-pull-request" className="h-5 w-5" />
|
||||
{t('gitView.pr.comments.title')}
|
||||
{pr ? (
|
||||
<span className="typography-meta text-muted-foreground">{t('gitView.pr.numberLabel', { number: pr.number })}</span>
|
||||
@@ -1982,7 +1964,7 @@ export const PullRequestSection: React.FC<{
|
||||
<ScrollShadow className="mt-2 max-h-[66vh] overflow-y-auto overlay-scrollbar-target overlay-scrollbar-container">
|
||||
{isLoadingCommentsDetails ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('gitView.loading.loading')}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -2023,7 +2005,7 @@ export const PullRequestSection: React.FC<{
|
||||
}}
|
||||
aria-label={t('gitView.pr.actions.sendCommentToAgentAria')}
|
||||
>
|
||||
<RiAiGenerate2 className="size-3.5" />
|
||||
<Icon name="ai-generate-2" className="size-3.5" />
|
||||
{t('gitView.pr.actions.sendToAgent')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
Reference in New Issue
Block a user