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,5 +1,4 @@
|
||||
import React, { useRef, memo } from 'react';
|
||||
import { RiAttachment2, RiCloseLine, RiFileImageLine, RiFileLine, RiFilePdfLine, RiGithubLine, RiGitPullRequestLine, RiAddLine, RiPushpin2Line } from '@remixicon/react';
|
||||
import { useInputStore } from '@/sync/input-store';
|
||||
import type { AttachedFile } from '@/sync/session-ui-store';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
@@ -9,6 +8,7 @@ import { openExternalUrl } from '@/lib/url';
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
|
||||
import { useIsVSCodeRuntime } from '@/hooks/useRuntimeAPIs';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
|
||||
@@ -108,7 +108,7 @@ export const FileAttachmentButton = memo(() => {
|
||||
)}
|
||||
aria-label={t('chat.fileAttachment.actions.attachAria')}
|
||||
>
|
||||
<RiAttachment2 className={iconSizeClass} />
|
||||
<Icon name="attachment-2" className={iconSizeClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
@@ -171,7 +171,7 @@ const ImagePreview = memo(({ file, onRemove }: ImagePreviewProps) => {
|
||||
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
|
||||
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="close" className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
@@ -194,7 +194,7 @@ const ImagePreview = memo(({ file, onRemove }: ImagePreviewProps) => {
|
||||
title={t('chat.fileAttachment.actions.removeImage')}
|
||||
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
|
||||
>
|
||||
<RiCloseLine className="h-2.5 w-2.5" />
|
||||
<Icon name="close" className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -263,7 +263,7 @@ const FileChip = memo(({ file, onRemove }: FileChipProps) => {
|
||||
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
|
||||
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="close" className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
@@ -301,7 +301,7 @@ const VSCodeFileChip = memo(({ file, onRemove }: FileChipProps) => {
|
||||
aria-label={t('chat.fileAttachment.activeEditor.remove')}
|
||||
title={t('chat.fileAttachment.activeEditor.remove')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="close" className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<FileTypeIcon filePath={file.filename} extension={extension} className="h-4 w-4" />
|
||||
<span className={cn('text-foreground', isSelectionAttachment ? 'whitespace-nowrap' : 'truncate max-w-[200px]')}>
|
||||
@@ -450,7 +450,7 @@ export const ActiveEditorFileSuggestion = memo(() => {
|
||||
onClick={() => { void handlePinSelection(); }}
|
||||
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
|
||||
>
|
||||
<RiPushpin2Line className="h-4 w-4" />
|
||||
<Icon name="pushpin-2" className="h-4 w-4" />
|
||||
</button>
|
||||
<FileTypeIcon filePath={fileName} extension={ext} className="h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-xs whitespace-nowrap">{`${displayName}:${selectionRange}`}</span>
|
||||
@@ -469,7 +469,7 @@ export const ActiveEditorFileSuggestion = memo(() => {
|
||||
onClick={handleAddFile}
|
||||
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
</button>
|
||||
<FileTypeIcon filePath={fileName} extension={ext} className="h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-xs truncate max-w-[220px]">{displayName}</span>
|
||||
@@ -610,9 +610,9 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
|
||||
className="inline-flex items-center bg-muted/30 border border-border/30 typography-meta gap-1 px-2 py-0.5 rounded-lg text-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
{githubLinkKind === 'pr' ? (
|
||||
<RiGitPullRequestLine className="text-muted-foreground h-3.5 w-3.5" />
|
||||
<Icon name="git-pull-request" className="text-muted-foreground h-3.5 w-3.5" />
|
||||
) : (
|
||||
<RiGithubLine className="text-muted-foreground h-3.5 w-3.5" />
|
||||
<Icon name="github" className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
<div className="overflow-hidden max-w-[220px]">
|
||||
<span className="truncate block" title={fileName}>{fileName}</span>
|
||||
@@ -621,7 +621,7 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
|
||||
) : (
|
||||
<div className="inline-flex items-center bg-muted/30 border border-border/30 typography-meta gap-1 px-2 py-0.5 rounded-lg">
|
||||
{file.mime?.includes('pdf') ? (
|
||||
<RiFilePdfLine className="text-muted-foreground h-3.5 w-3.5" />
|
||||
<Icon name="file-pdf" className="text-muted-foreground h-3.5 w-3.5" />
|
||||
) : (
|
||||
<FileTypeIcon filePath={fileName} extension={ext} className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
@@ -668,7 +668,7 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full flex items-center justify-center bg-muted/30 text-muted-foreground">
|
||||
<RiFileImageLine className="h-6 w-6" />
|
||||
<Icon name="file-image" className="h-6 w-6" />
|
||||
</div>
|
||||
)}
|
||||
<span className="sr-only">{filename}</span>
|
||||
@@ -735,9 +735,9 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
{githubLinkKind === 'pr' ? (
|
||||
<RiGitPullRequestLine className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
<Icon name="git-pull-request" className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
) : (
|
||||
<RiGithubLine className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
<Icon name="github" className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -779,11 +779,11 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
{file.mime?.startsWith('image/') ? (
|
||||
<RiFileImageLine className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
<Icon name="file-image" className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
) : file.mime?.includes('pdf') ? (
|
||||
<RiFilePdfLine className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
<Icon name="file-pdf" className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
) : (
|
||||
<RiFileLine className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
<Icon name="file" className={cn("text-muted-foreground", compact ? "h-3.5 w-3.5" : "h-4 w-4")} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
Reference in New Issue
Block a user