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:
@@ -11,18 +11,8 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useGitIdentitiesStore, type GitIdentityProfile, type GitIdentityAuthType } from '@/stores/useGitIdentitiesStore';
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiGitBranchLine,
|
||||
RiBriefcaseLine,
|
||||
RiHomeLine,
|
||||
RiGraduationCapLine,
|
||||
RiCodeLine,
|
||||
RiInformationLine,
|
||||
RiKeyLine,
|
||||
RiLock2Line,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
@@ -35,11 +25,11 @@ const PROFILE_COLORS = [
|
||||
];
|
||||
|
||||
const PROFILE_ICONS = [
|
||||
{ key: 'branch', Icon: RiGitBranchLine, label: 'Branch' },
|
||||
{ key: 'briefcase', Icon: RiBriefcaseLine, label: 'Work' },
|
||||
{ key: 'house', Icon: RiHomeLine, label: 'Personal' },
|
||||
{ key: 'graduation', Icon: RiGraduationCapLine, label: 'School' },
|
||||
{ key: 'code', Icon: RiCodeLine, label: 'Code' },
|
||||
{ key: 'branch', Icon: 'git-branch', label: 'Branch' },
|
||||
{ key: 'briefcase', Icon: 'briefcase', label: 'Work' },
|
||||
{ key: 'house', Icon: 'home', label: 'Personal' },
|
||||
{ key: 'graduation', Icon: 'graduation-cap', label: 'School' },
|
||||
{ key: 'code', Icon: 'code', label: 'Code' },
|
||||
];
|
||||
|
||||
interface GitIdentityEditorDialogProps {
|
||||
@@ -261,7 +251,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
<span className="typography-ui-label text-foreground">{t('settings.gitIdentities.editor.field.icon')}</span>
|
||||
<div className="flex gap-1.5">
|
||||
{PROFILE_ICONS.map((i) => {
|
||||
const IconComponent = i.Icon;
|
||||
const iconName = i.Icon;
|
||||
return (
|
||||
<button
|
||||
key={i.key}
|
||||
@@ -275,7 +265,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
)}
|
||||
title={i.label}
|
||||
>
|
||||
<IconComponent
|
||||
<Icon name={iconName}
|
||||
className="w-3.5 h-3.5"
|
||||
style={{ color: icon === i.key ? currentColorValue : 'var(--surface-muted-foreground)' }}
|
||||
/>
|
||||
@@ -298,7 +288,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
{!isGlobalProfile && <span className="text-[var(--status-error)] text-xs">*</span>}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
{t('settings.gitIdentities.editor.field.userNameTooltip')}
|
||||
@@ -322,7 +312,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
{!isGlobalProfile && <span className="text-[var(--status-error)] text-xs">*</span>}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
{t('settings.gitIdentities.editor.field.emailAddressTooltip')}
|
||||
@@ -356,7 +346,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
aria-pressed={authType === 'ssh'}
|
||||
onClick={() => setAuthType('ssh')}
|
||||
>
|
||||
<RiLock2Line className="w-3.5 h-3.5 mr-1" /> SSH
|
||||
<Icon name="lock-2" className="w-3.5 h-3.5 mr-1" /> SSH
|
||||
</Button>
|
||||
<Button size="sm"
|
||||
type="button"
|
||||
@@ -364,7 +354,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
aria-pressed={authType === 'token'}
|
||||
onClick={() => setAuthType('token')}
|
||||
>
|
||||
<RiKeyLine className="w-3.5 h-3.5 mr-1" /> {t('settings.gitIdentities.editor.field.authToken')}
|
||||
<Icon name="key" className="w-3.5 h-3.5 mr-1" /> {t('settings.gitIdentities.editor.field.authToken')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -375,7 +365,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
<label className="typography-ui-label text-foreground">{t('settings.gitIdentities.editor.field.sshKeyPath')}</label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
{t('settings.gitIdentities.editor.field.sshKeyPathTooltip')}
|
||||
@@ -398,7 +388,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
<span className="text-[var(--status-error)] text-xs">*</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
<Icon name="information" className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
{t('settings.gitIdentities.editor.field.hostTooltip')}
|
||||
@@ -427,7 +417,7 @@ export const GitIdentityEditorDialog: React.FC<GitIdentityEditorDialogProps> = (
|
||||
onClick={() => setIsDeleteDialogOpen(true)}
|
||||
className="text-[var(--status-error)] hover:text-[var(--status-error)] border-[var(--status-error)]/30 hover:bg-[var(--status-error)]/10 mr-auto"
|
||||
>
|
||||
<RiDeleteBinLine className="w-3.5 h-3.5 mr-1" /> {t('settings.common.actions.delete')}
|
||||
<Icon name="delete-bin" className="w-3.5 h-3.5 mr-1" /> {t('settings.common.actions.delete')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)} className="text-foreground hover:bg-interactive-hover hover:text-foreground">
|
||||
|
||||
@@ -15,35 +15,23 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiGitBranchLine,
|
||||
RiBriefcaseLine,
|
||||
RiHomeLine,
|
||||
RiGraduationCapLine,
|
||||
RiCodeLine,
|
||||
RiHeartLine,
|
||||
RiMore2Line,
|
||||
RiDeleteBinLine,
|
||||
RiDownloadLine,
|
||||
RiShieldKeyholeLine,
|
||||
} from '@remixicon/react';
|
||||
import { useGitIdentitiesStore, type GitIdentityProfile, type DiscoveredGitCredential } from '@/stores/useGitIdentitiesStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { GitSettings } from '@/components/sections/openchamber/GitSettings';
|
||||
import { GitHubSettings } from '@/components/sections/openchamber/GitHubSettings';
|
||||
import { GitIdentityEditorDialog } from './GitIdentityEditorDialog';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
const ICON_MAP: Record<string, React.ComponentType<{ className?: string; style?: React.CSSProperties }>> = {
|
||||
branch: RiGitBranchLine,
|
||||
briefcase: RiBriefcaseLine,
|
||||
house: RiHomeLine,
|
||||
graduation: RiGraduationCapLine,
|
||||
code: RiCodeLine,
|
||||
heart: RiHeartLine,
|
||||
const ICON_MAP: Record<string, string> = {
|
||||
branch: 'git-branch',
|
||||
briefcase: 'briefcase',
|
||||
house: 'home',
|
||||
graduation: 'graduation-cap',
|
||||
code: 'code',
|
||||
heart: 'heart',
|
||||
};
|
||||
|
||||
const COLOR_MAP: Record<string, string> = {
|
||||
@@ -137,7 +125,7 @@ export const GitPage: React.FC = () => {
|
||||
<h3 className="typography-ui-header font-semibold text-foreground">{t('settings.gitIdentities.page.section.title')}</h3>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={() => openEditor('new')}>
|
||||
<RiAddLine className="w-3.5 h-3.5 mr-1" /> {t('settings.common.badge.new')}
|
||||
<Icon name="add" className="w-3.5 h-3.5 mr-1" /> {t('settings.common.badge.new')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -170,7 +158,7 @@ export const GitPage: React.FC = () => {
|
||||
{/* Empty state */}
|
||||
{!globalIdentity && profiles.length === 0 && unimportedCredentials.length === 0 && (
|
||||
<div className="py-8 px-4 text-center text-muted-foreground">
|
||||
<RiShieldKeyholeLine className="mx-auto mb-2 h-8 w-8 opacity-40" />
|
||||
<Icon name="shield-keyhole" className="mx-auto mb-2 h-8 w-8 opacity-40" />
|
||||
<p className="typography-ui-label">{t('settings.gitIdentities.page.empty.title')}</p>
|
||||
<p className="typography-meta mt-1 opacity-75">{t('settings.gitIdentities.page.empty.description')}</p>
|
||||
</div>
|
||||
@@ -257,7 +245,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
|
||||
hasBorder,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const IconComponent = ICON_MAP[profile.icon || 'branch'] || RiGitBranchLine;
|
||||
const iconName = ICON_MAP[profile.icon || 'branch'] || 'git-branch';
|
||||
const iconColor = COLOR_MAP[profile.color || ''];
|
||||
const authType = profile.authType || 'ssh';
|
||||
|
||||
@@ -273,7 +261,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onEdit(); }}
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<IconComponent className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
|
||||
<Icon name={iconName} className="w-4 h-4 shrink-0" style={{ color: iconColor }} />
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="typography-ui-label text-foreground truncate">{profile.name}</span>
|
||||
@@ -305,7 +293,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
|
||||
className="h-6 w-6 shrink-0 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<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-28">
|
||||
@@ -317,7 +305,7 @@ const IdentityRow: React.FC<IdentityRowProps> = ({
|
||||
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4 mr-px" />
|
||||
<Icon name="delete-bin" className="h-4 w-4 mr-px" />
|
||||
{t('settings.common.actions.delete')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
@@ -355,7 +343,7 @@ const DiscoveredRow: React.FC<DiscoveredRowProps> = ({ credential, onImport, has
|
||||
</span>
|
||||
</div>
|
||||
<Button size="sm" variant="ghost" onClick={onImport} className="gap-1 shrink-0">
|
||||
<RiDownloadLine className="h-3 w-3" />
|
||||
<Icon name="download" className="h-3 w-3" />
|
||||
{t('settings.gitIdentities.page.actions.import')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user