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:
@@ -8,7 +8,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { RiAddLine, RiArrowDownSLine, RiArrowRightSLine, RiCheckLine, RiCloseLine, RiFolder6Line, RiPushpin2Line, RiPushpinLine } from '@remixicon/react';
|
||||
import { cn, formatPathForDisplay } from '@/lib/utils';
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
@@ -16,6 +15,7 @@ import type { DesktopSettings } from '@/lib/desktop';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
interface DirectoryItem {
|
||||
@@ -695,9 +695,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -728,7 +728,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'text-muted-foreground flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -760,7 +760,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.createNewDirectory')}
|
||||
>
|
||||
<RiAddLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="add" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -776,9 +776,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
|
||||
>
|
||||
{isPinned ? (
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
) : (
|
||||
<RiPushpinLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
@@ -809,7 +809,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
style={{ paddingLeft: `${(level + 1) * 14 + 6}px` }}
|
||||
>
|
||||
<div className="w-4" />
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={newDirName}
|
||||
@@ -838,7 +838,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.createDirectory')}
|
||||
>
|
||||
<RiCheckLine className="h-3 w-3 text-green-600" />
|
||||
<Icon name="check" className="h-3 w-3 text-green-600" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -849,7 +849,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.cancel')}
|
||||
>
|
||||
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="close" className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -881,9 +881,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-0.5 hover:bg-interactive-hover rounded"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-right-s" className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -898,7 +898,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
style={{ paddingLeft: `${(level + 1) * 12 + 8}px` }}
|
||||
>
|
||||
<div className="w-4" />
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={newDirName}
|
||||
@@ -927,7 +927,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.createDirectory')}
|
||||
>
|
||||
<RiCheckLine className="h-3 w-3 text-green-600" />
|
||||
<Icon name="check" className="h-3 w-3 text-green-600" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -938,7 +938,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.cancel')}
|
||||
>
|
||||
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="close" className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -968,9 +968,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -996,7 +996,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'text-muted-foreground flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -1027,9 +1027,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
|
||||
>
|
||||
{isPinned ? (
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
) : (
|
||||
<RiPushpinLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
@@ -1105,9 +1105,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded flex-shrink-0", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1124,7 +1124,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isSelected ? 'text-primary' : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -1151,7 +1151,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.unpinDirectory')}
|
||||
>
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3.5 w-3.5")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3.5 w-3.5")} />
|
||||
</button>
|
||||
</div>
|
||||
{isExpanded && children && children.map((child) => renderPinnedTreeItem(child, 1))}
|
||||
@@ -1183,12 +1183,12 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-0.5 hover:bg-interactive-hover rounded flex-shrink-0"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-right-s" className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="typography-ui-label font-medium">{name}</div>
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
@@ -1207,7 +1207,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.unpinDirectory')}
|
||||
>
|
||||
<RiPushpin2Line className="h-3 w-3 text-primary" />
|
||||
<Icon name="pushpin-2" className="h-3 w-3 text-primary" />
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
{isExpanded && children && (
|
||||
@@ -1238,9 +1238,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
>
|
||||
{isPinnedExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
<span>{t('directoryTree.section.pinned')}</span>
|
||||
<span className="ml-auto typography-micro text-muted-foreground/60 normal-case tracking-normal">
|
||||
@@ -1302,12 +1302,12 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
aria-label={t('directoryTree.actions.selectWorkingDirectoryAria')}
|
||||
>
|
||||
<span className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<RiFolder6Line className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="truncate" title={currentPath}>
|
||||
{formatPathForDisplay(currentPath, homeDirectory)}
|
||||
</span>
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[350px]">
|
||||
|
||||
Reference in New Issue
Block a user