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:
@@ -6,7 +6,7 @@ import {
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { RiArrowDownSLine, RiFolderLine } from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -59,9 +59,9 @@ export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ clas
|
||||
'flex items-center gap-1.5 text-left'
|
||||
)}
|
||||
>
|
||||
<RiFolderLine className="h-4 w-4 opacity-70" />
|
||||
<Icon name="folder" className="h-4 w-4 opacity-70" />
|
||||
<span className="min-w-0 flex-1 truncate typography-ui-label font-medium">{label}</span>
|
||||
<RiArrowDownSLine className="size-4 opacity-50" />
|
||||
<Icon name="arrow-down-s" className="size-4 opacity-50" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-auto">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RiAddLine } from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -54,7 +54,7 @@ export const SettingsSidebarHeader: React.FC<SettingsSidebarHeaderProps> = ({
|
||||
onClick={onAdd}
|
||||
aria-label={addButtonLabel}
|
||||
>
|
||||
<RiAddLine className="size-4" />
|
||||
<Icon name="add" className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,14 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface SettingsSidebarItemAction {
|
||||
/** Label shown in dropdown menu */
|
||||
label: string;
|
||||
/** Icon component to show before label */
|
||||
icon?: React.ComponentType<{ className?: string }>;
|
||||
icon?: string;
|
||||
/** Callback when action is clicked */
|
||||
onClick: () => void;
|
||||
/** If true, uses destructive styling (red text) */
|
||||
@@ -47,10 +47,10 @@ interface SettingsSidebarItemProps {
|
||||
* metadata={agent.description}
|
||||
* selected={selectedId === agent.id}
|
||||
* onSelect={() => setSelectedId(agent.id)}
|
||||
* icon={<RiRobotLine className="h-4 w-4" />}
|
||||
* icon={<'robot' className="h-4 w-4" />}
|
||||
* actions={[
|
||||
* { label: 'Duplicate', icon: RiFileCopyLine, onClick: handleDuplicate },
|
||||
* { label: 'Delete', icon: RiDeleteBinLine, onClick: handleDelete, destructive: true },
|
||||
* { label: 'Duplicate', icon: 'file-copy', onClick: handleDuplicate },
|
||||
* { label: 'Delete', icon: 'delete-bin', onClick: handleDelete, destructive: true },
|
||||
* ]}
|
||||
* />
|
||||
*/
|
||||
@@ -103,12 +103,12 @@ export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
|
||||
variant="ghost"
|
||||
className="h-6 w-6 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
|
||||
>
|
||||
<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="w-fit min-w-20">
|
||||
{actions.map((action) => {
|
||||
const Icon = action.icon;
|
||||
const iconName = action.icon;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={action.label}
|
||||
@@ -120,7 +120,7 @@ export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
|
||||
action.destructive && 'text-destructive focus:text-destructive'
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-4 w-4 mr-px" />}
|
||||
{iconName && <Icon name={iconName} className="h-4 w-4 mr-px" />}
|
||||
{action.label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { RiArrowDownSLine } from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
interface SidebarGroupProps {
|
||||
/** Group display label (e.g. "business", "automation-ai") */
|
||||
@@ -64,7 +64,7 @@ export const SidebarGroup: React.FC<SidebarGroupProps> = ({
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
)}
|
||||
>
|
||||
<RiArrowDownSLine
|
||||
<Icon name="arrow-down-s"
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-200',
|
||||
!expanded && '-rotate-90',
|
||||
|
||||
Reference in New Issue
Block a user