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 from 'react';
|
||||
import { RiArrowLeftRightLine, RiChat4Line, RiCloseLine, RiDonutChartFill, RiFileTextLine, RiFullscreenExitLine, RiFullscreenLine, RiGlobalLine, RiRefreshLine, RiExternalLinkLine, RiTerminalBoxLine, RiCursorLine } from '@remixicon/react';
|
||||
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -20,6 +19,7 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useInputStore } from '@/sync/input-store';
|
||||
import { ContextPanelContent } from './ContextSidebarTab';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
const CONTEXT_PANEL_MIN_WIDTH = 360;
|
||||
const CONTEXT_PANEL_MAX_WIDTH = 1400;
|
||||
@@ -296,23 +296,23 @@ const getTabIcon = (tab: { mode: 'diff' | 'file' | 'context' | 'plan' | 'chat' |
|
||||
}
|
||||
|
||||
if (tab.mode === 'diff') {
|
||||
return <RiArrowLeftRightLine className="h-3.5 w-3.5" />;
|
||||
return <Icon name="arrow-left-right" className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (tab.mode === 'plan') {
|
||||
return <RiFileTextLine className="h-3.5 w-3.5" />;
|
||||
return <Icon name="file-text" className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (tab.mode === 'context') {
|
||||
return <RiDonutChartFill className="h-3.5 w-3.5" />;
|
||||
return <Icon name="donut-chart-fill" className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (tab.mode === 'chat') {
|
||||
return <RiChat4Line className="h-3.5 w-3.5" />;
|
||||
return <Icon name="chat-4" className="h-3.5 w-3.5" />;
|
||||
}
|
||||
|
||||
if (tab.mode === 'preview') {
|
||||
return <RiGlobalLine className="h-3.5 w-3.5 text-[var(--status-info)]" />;
|
||||
return <Icon name="global" className="h-3.5 w-3.5 text-[var(--status-info)]" />;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -919,7 +919,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
|
||||
aria-label={t('contextPanel.preview.actions.reload')}
|
||||
disabled={!effectiveSrc}
|
||||
>
|
||||
<RiRefreshLine className="h-3.5 w-3.5" />
|
||||
<Icon name="refresh" className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -934,7 +934,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
|
||||
aria-label={t('contextPanel.preview.actions.openExternal')}
|
||||
disabled={!directSrc}
|
||||
>
|
||||
<RiExternalLinkLine className="h-3.5 w-3.5" />
|
||||
<Icon name="external-link" className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
{isLoopback ? (
|
||||
<Button
|
||||
@@ -947,7 +947,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
|
||||
aria-label={t('contextPanel.preview.inspect.toggle')}
|
||||
disabled={!bridgeReady}
|
||||
>
|
||||
<RiCursorLine className="h-3.5 w-3.5" />
|
||||
<Icon name="cursor" className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
) : null}
|
||||
{isLoopback ? (
|
||||
@@ -961,7 +961,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
|
||||
aria-label={bridgeReady ? t('contextPanel.preview.console.open') : t('contextPanel.preview.console.waiting')}
|
||||
disabled={!bridgeReady && consoleEvents.length === 0}
|
||||
>
|
||||
<RiTerminalBoxLine className="h-3.5 w-3.5" />
|
||||
<Icon name="terminal-box" className="h-3.5 w-3.5" />
|
||||
{consoleErrorCount > 0 ? (
|
||||
<span className="typography-micro text-status-error">{consoleErrorCount}</span>
|
||||
) : null}
|
||||
@@ -1380,7 +1380,7 @@ export const ContextPanel: React.FC = () => {
|
||||
? <PreviewPane rawUrl={activeTab.targetPath ?? ''} onNavigate={(url) => openContextPreview(effectiveDirectory, url)} />
|
||||
: (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<RiGlobalLine className="h-12 w-12 text-muted-foreground/50" />
|
||||
<Icon name="global" className="h-12 w-12 text-muted-foreground/50" />
|
||||
<div className="typography-ui-header text-foreground">{t('contextPanel.preview.title')}</div>
|
||||
<div className="max-w-sm typography-micro text-muted-foreground">{t('contextPanel.preview.description')}</div>
|
||||
</div>
|
||||
@@ -1433,7 +1433,7 @@ export const ContextPanel: React.FC = () => {
|
||||
title={isExpanded ? t('contextPanel.actions.collapsePanel') : t('contextPanel.actions.expandPanel')}
|
||||
aria-label={isExpanded ? t('contextPanel.actions.collapsePanel') : t('contextPanel.actions.expandPanel')}
|
||||
>
|
||||
{isExpanded ? <RiFullscreenExitLine className="h-3.5 w-3.5" /> : <RiFullscreenLine className="h-3.5 w-3.5" />}
|
||||
{isExpanded ? <Icon name="fullscreen-exit" className="h-3.5 w-3.5" /> : <Icon name="fullscreen" className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1444,7 +1444,7 @@ export const ContextPanel: React.FC = () => {
|
||||
title={t('contextPanel.actions.closePanel')}
|
||||
aria-label={t('contextPanel.actions.closePanel')}
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import type { Message, Part } from '@opencode-ai/sdk/v2';
|
||||
import { RiCheckLine, RiFileCopyLine } from '@remixicon/react';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
|
||||
import { deriveMessageRole } from '@/components/chat/message/messageRole';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
@@ -565,7 +565,7 @@ export const ContextPanelContent: React.FC = () => {
|
||||
aria-label={isCopied ? t('contextSidebar.actions.copied') : t('contextSidebar.actions.copyJson')}
|
||||
title={isCopied ? t('contextSidebar.actions.copied') : t('contextSidebar.actions.copy')}
|
||||
>
|
||||
{isCopied ? <RiCheckLine className="size-3.5" /> : <RiFileCopyLine className="size-3.5" />}
|
||||
{isCopied ? <Icon name="check" className="size-3.5" /> : <Icon name="file-copy" className="size-3.5" />}
|
||||
</button>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { SortableTabsStrip, type SortableTabsStripItem } from '@/components/ui/sortable-tabs-strip';
|
||||
|
||||
import { RiArrowLeftSLine, RiChat4Line, RiChatNewLine, RiCheckLine, RiCloseLine, RiCommandLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGithubFill, RiLayoutLeftLine, RiLayoutRightLine, RiPictureInPicture2Line, RiPlayListAddLine, RiRefreshLine, RiServerLine, RiStackLine, RiTerminalBoxLine, RiTimerLine, RiAlertLine, type RemixiconComponentType } from '@remixicon/react';
|
||||
import { DiffIcon } from '@/components/icons/DiffIcon';
|
||||
import { useUIStore, type MainTab } from '@/stores/useUIStore';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
@@ -56,7 +55,6 @@ import {
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react';
|
||||
import type { UsageWindow } from '@/types';
|
||||
import type { GitHubAuthStatus } from '@/lib/api/types';
|
||||
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
|
||||
@@ -68,8 +66,10 @@ import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton';
|
||||
import { canUseElectronDesktopIPC, invokeDesktop, isDesktopShell, isVSCodeRuntime, startDesktopWindowDrag } from '@/lib/desktop';
|
||||
import { desktopHostsGet, locationMatchesHost, redactSensitiveUrl } from '@/lib/desktopHosts';
|
||||
import { resolveSessionDiffStats } from '@/components/session/sidebar/utils';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import type { Session } from '@opencode-ai/sdk/v2/client';
|
||||
import type { IconName } from "@/components/icon/icons";
|
||||
|
||||
const DESKTOP_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:bg-interactive-hover transition-colors';
|
||||
const MOBILE_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center gap-2 p-2 rounded-md typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-interactive-hover transition-colors';
|
||||
@@ -80,7 +80,7 @@ type HeaderIconActionButtonProps = {
|
||||
ariaLabel: string;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
Icon: RemixiconComponentType;
|
||||
Icon: IconName;
|
||||
iconClassName?: string;
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ const HeaderIconActionButton = React.memo(function HeaderIconActionButton({
|
||||
ariaLabel,
|
||||
onClick,
|
||||
className,
|
||||
Icon,
|
||||
Icon: iconName,
|
||||
iconClassName,
|
||||
}: HeaderIconActionButtonProps) {
|
||||
if (!visible) {
|
||||
@@ -106,7 +106,7 @@ const HeaderIconActionButton = React.memo(function HeaderIconActionButton({
|
||||
aria-label={ariaLabel}
|
||||
className={className ?? DESKTOP_HEADER_ICON_BUTTON_CLASS}
|
||||
>
|
||||
<Icon className={iconClassName ?? 'h-[18px] w-[18px]'} />
|
||||
<Icon name={iconName} className={iconClassName ?? 'h-[18px] w-[18px]'} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -162,7 +162,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
) : (
|
||||
<RiGithubFill className="h-3.5 w-3.5 text-foreground" />
|
||||
<Icon name="github-fill" className="h-3.5 w-3.5 text-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -195,7 +195,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full border border-border/60 bg-muted">
|
||||
<RiGithubFill className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="github-fill" className="h-3 w-3 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
<span className="flex min-w-0 flex-1 flex-col">
|
||||
@@ -208,7 +208,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
{isCurrent ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
|
||||
{isCurrent ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
@@ -231,7 +231,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
) : (
|
||||
<RiGithubFill className="h-3.5 w-3.5 text-foreground" />
|
||||
<Icon name="github-fill" className="h-3.5 w-3.5 text-foreground" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -321,7 +321,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
isDesktopApp ? 'w-auto max-w-[14rem] justify-start gap-1.5 px-2.5' : 'h-8 w-8'
|
||||
)}
|
||||
>
|
||||
<RiStackLine className="h-[18px] w-[18px]" />
|
||||
<Icon name="stack" className="h-[18px] w-[18px]" />
|
||||
{isDesktopApp ? (
|
||||
<span className="truncate typography-ui-label font-medium text-foreground">{compactCurrentInstanceLabel}</span>
|
||||
) : null}
|
||||
@@ -411,7 +411,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
disabled={isQuotaLoading || isUsageRefreshSpinning}
|
||||
aria-label={t('header.services.refreshRateLimitsAria')}
|
||||
>
|
||||
<RiRefreshLine className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
|
||||
<Icon name="refresh" className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,7 +485,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
>
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md px-1 py-1.5 text-left hover:bg-[var(--interactive-hover)]/50 transition-colors">
|
||||
<span className="typography-ui-label font-medium text-foreground">{family.familyLabel}</span>
|
||||
{isExpanded ? <RiArrowDownSLine className="h-4 w-4 text-muted-foreground" /> : <RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />}
|
||||
{isExpanded ? <Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" /> : <Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="space-y-2.5 pb-1 pl-1 pt-1">
|
||||
@@ -552,7 +552,6 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const isSameContextUsage = (
|
||||
a: SessionContextUsage | null,
|
||||
b: SessionContextUsage | null,
|
||||
@@ -623,7 +622,7 @@ const getActiveContextMode = (panelState: {
|
||||
interface TabConfig {
|
||||
id: MainTab;
|
||||
label: string;
|
||||
icon: RemixiconComponentType | 'diff';
|
||||
icon: IconName | 'diff';
|
||||
badge?: number;
|
||||
showDot?: boolean;
|
||||
}
|
||||
@@ -1179,7 +1178,6 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
return lastProjectActionsContextRef.current;
|
||||
}, [actionDirectory, activeProjectRef]);
|
||||
|
||||
|
||||
const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled);
|
||||
const isSessionPlanAvailable = useSessionUIStore((state) => state.isSessionPlanAvailable);
|
||||
const planTabAvailable = planModeEnabled && currentSessionId ? isSessionPlanAvailable(currentSessionId) : false;
|
||||
@@ -1508,17 +1506,17 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
const tabs: TabConfig[] = React.useMemo(() => {
|
||||
if (isMobile) {
|
||||
const base: TabConfig[] = [
|
||||
{ id: 'chat', label: t('layout.mainTab.chat'), icon: RiChat4Line },
|
||||
{ id: 'chat', label: t('layout.mainTab.chat'), icon: "chat-4" },
|
||||
];
|
||||
|
||||
if (showPlanTab) {
|
||||
base.push({ id: 'plan', label: t('layout.mainTab.plan'), icon: RiFileTextLine });
|
||||
base.push({ id: 'plan', label: t('layout.mainTab.plan'), icon: "file-text" });
|
||||
}
|
||||
|
||||
base.push(
|
||||
{ id: 'diff', label: t('layout.mainTab.diff'), icon: 'diff' },
|
||||
{ id: 'files', label: t('layout.mainTab.files'), icon: RiFolder6Line },
|
||||
{ id: 'terminal', label: t('layout.mainTab.terminal'), icon: RiTerminalBoxLine },
|
||||
{ id: 'files', label: t('layout.mainTab.files'), icon: "folder-6" },
|
||||
{ id: 'terminal', label: t('layout.mainTab.terminal'), icon: "terminal-box" },
|
||||
);
|
||||
|
||||
return base;
|
||||
@@ -1539,13 +1537,13 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
}, [activeMainTab, isMobile, setActiveMainTab]);
|
||||
|
||||
const servicesTabs = React.useMemo(() => {
|
||||
const base: Array<{ value: 'instance' | 'usage' | 'mcp'; label: string; icon: RemixiconComponentType }> = [];
|
||||
const base: Array<{ value: 'instance' | 'usage' | 'mcp'; label: string; icon: IconName }> = [];
|
||||
if (isDesktopApp) {
|
||||
base.push({ value: 'instance', label: t('layout.services.instance'), icon: RiServerLine });
|
||||
base.push({ value: 'instance', label: t('layout.services.instance'), icon: "server" });
|
||||
}
|
||||
base.push(
|
||||
{ value: 'usage', label: t('layout.services.usage'), icon: RiTimerLine },
|
||||
{ value: 'mcp', label: 'MCP', icon: McpIcon as unknown as RemixiconComponentType }
|
||||
{ value: 'usage', label: t('layout.services.usage'), icon: "timer" },
|
||||
{ value: 'mcp', label: 'MCP', icon: McpIcon as unknown as IconName }
|
||||
);
|
||||
return base;
|
||||
}, [isDesktopApp, t]);
|
||||
@@ -1554,7 +1552,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
return servicesTabs.map((tab) => ({
|
||||
id: tab.value,
|
||||
label: tab.label,
|
||||
icon: <tab.icon className="h-3.5 w-3.5" />,
|
||||
icon: <Icon name={tab.icon} className="h-3.5 w-3.5" />,
|
||||
}));
|
||||
}, [servicesTabs]);
|
||||
|
||||
@@ -1628,8 +1626,8 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
|
||||
const mobileServicesTabItems = React.useMemo<SortableTabsStripItem[]>(() => {
|
||||
return [
|
||||
{ id: 'usage', label: t('layout.services.usage'), icon: <RiTimerLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'mcp', label: 'MCP', icon: <RiCommandLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'usage', label: t('layout.services.usage'), icon: <Icon name="timer" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'mcp', label: 'MCP', icon: <Icon name="command" className="h-3.5 w-3.5" /> },
|
||||
];
|
||||
}, [t]);
|
||||
|
||||
@@ -1709,14 +1707,14 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
const renderTab = (tab: TabConfig) => {
|
||||
const isActive = activeMainTab === tab.id;
|
||||
const isDiffTab = tab.icon === 'diff';
|
||||
const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType);
|
||||
const tabIconName = isDiffTab ? null : (tab.icon as IconName);
|
||||
const isChatTab = tab.id === 'chat';
|
||||
|
||||
const renderIcon = (iconSize: number) => {
|
||||
if (isDiffTab) {
|
||||
return <DiffIcon size={iconSize} />;
|
||||
}
|
||||
return Icon ? <Icon size={iconSize} /> : null;
|
||||
return tabIconName ? <Icon name={tabIconName} className={`h-${iconSize/4} w-${iconSize/4}`} /> : null;
|
||||
};
|
||||
|
||||
const tabButton = (
|
||||
@@ -1766,7 +1764,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
onClick={handleOpenContextPlan}
|
||||
className={cn(desktopHeaderIconButtonClass, isContextPlanActive && 'bg-[var(--interactive-hover)]')}
|
||||
>
|
||||
<RiFileTextLine className="h-[18px] w-[18px]" />
|
||||
<Icon name="file-text" className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -1807,13 +1805,13 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
title={t('header.actions.terminalPanelWithShortcut', { shortcut: shortcutLabel('toggle_terminal') })}
|
||||
ariaLabel={t('header.actions.toggleTerminalPanelAria')}
|
||||
onClick={toggleBottomTerminal}
|
||||
Icon={RiTerminalBoxLine}
|
||||
Icon={'terminal-box'}
|
||||
/>
|
||||
<HeaderIconActionButton
|
||||
title={t('header.actions.rightSidebarWithShortcut', { shortcut: shortcutLabel('toggle_right_sidebar') })}
|
||||
ariaLabel={t('header.actions.toggleRightSidebarAria')}
|
||||
onClick={toggleRightSidebar}
|
||||
Icon={RiLayoutRightLine}
|
||||
Icon={'layout-right'}
|
||||
/>
|
||||
<DesktopGitHubControl
|
||||
isMobile={isMobile}
|
||||
@@ -1848,7 +1846,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
ariaLabel={t('header.actions.openSessionsAria')}
|
||||
onClick={handleOpenSessionSwitcher}
|
||||
className={`${desktopHeaderIconButtonClass} shrink-0`}
|
||||
Icon={RiLayoutLeftLine}
|
||||
Icon={'layout-left'}
|
||||
/>
|
||||
|
||||
<div className={cn('flex min-w-0 flex-1 items-center', !isSidebarOpen && 'pl-3')}>
|
||||
@@ -1861,7 +1859,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
onClick={handleHeaderNewSession}
|
||||
className={cn(desktopHeaderIconButtonClass, 'mr-6 shrink-0')}
|
||||
>
|
||||
<RiChatNewLine className="h-[18px] w-[18px]" />
|
||||
<Icon name="chat-new" className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -1886,7 +1884,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
{activeProjectLabel ? <span className="truncate">{activeProjectLabel}</span> : null}
|
||||
{currentBranchLabel ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-0.5">
|
||||
<RiGitBranchLine className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" />
|
||||
<Icon name="git-branch" className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" />
|
||||
<span className="truncate">{currentBranchLabel}</span>
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1902,7 +1900,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
"inline-flex min-w-0 items-center gap-0.5",
|
||||
worktreeBadgeKind === 'attention' || worktreeBadgeKind === 'invalid' || worktreeBadgeKind === 'missing' ? 'text-status-warning' : 'text-muted-foreground/60'
|
||||
)}>
|
||||
<RiAlertLine className="h-3 w-3 flex-shrink-0" />
|
||||
<Icon name="alert" className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="truncate">{worktreeBadge}</span>
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1943,7 +1941,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
ariaLabel={isNewSessionDraftOpen ? t('header.actions.newMiniChatAria') : t('header.actions.openSessionMiniChatAria')}
|
||||
onClick={handleOpenCurrentMiniChat}
|
||||
className={cn(desktopHeaderIconButtonClass, desktopSidebarActionsInline && showDesktopHeaderContextUsage ? 'mr-3.5' : 'mr-1')}
|
||||
Icon={RiPictureInPicture2Line}
|
||||
Icon={'picture-in-picture-2'}
|
||||
/>
|
||||
{desktopSidebarActionsInline ? desktopSidebarActions : null}
|
||||
{!desktopSidebarActionsInline && desktopRightSidebarActionsHost
|
||||
@@ -1968,7 +1966,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
)}
|
||||
aria-label={leftDrawerOpen ? t('header.actions.closeSessionsAria') : t('header.actions.openSessionsAria')}
|
||||
>
|
||||
<RiLayoutLeftLine className="h-5 w-5" />
|
||||
<Icon name="layout-left" className="h-5 w-5" />
|
||||
</button>
|
||||
) : isSessionSwitcherOpen ? (
|
||||
<button
|
||||
@@ -1977,7 +1975,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-interactive-active"
|
||||
aria-label={t('header.actions.backAria')}
|
||||
>
|
||||
<RiArrowLeftSLine className="h-5 w-5" />
|
||||
<Icon name="arrow-left-s" className="h-5 w-5" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@@ -1986,7 +1984,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-interactive-active"
|
||||
aria-label={t('header.actions.openSessionsAria')}
|
||||
>
|
||||
<RiPlayListAddLine className="h-5 w-5" />
|
||||
<Icon name="play-list-add" className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -2009,7 +2007,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
{tabs.map((tab) => {
|
||||
const isActive = activeMainTab === tab.id;
|
||||
const isDiffTab = tab.icon === 'diff';
|
||||
const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType);
|
||||
const tabIconName = isDiffTab ? null : (tab.icon as IconName);
|
||||
return (
|
||||
<Tooltip key={tab.id}>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -2032,8 +2030,8 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
>
|
||||
{isDiffTab ? (
|
||||
<DiffIcon className="h-5 w-5" />
|
||||
) : Icon ? (
|
||||
<Icon className="h-5 w-5" />
|
||||
) : tabIconName ? (
|
||||
<Icon name={tabIconName} className="h-5 w-5" />
|
||||
) : null}
|
||||
{tab.badge !== undefined && tab.badge > 0 && (
|
||||
<span className="absolute -top-1 -right-1 text-[10px] font-semibold text-primary">
|
||||
@@ -2088,7 +2086,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
aria-label={t('header.services.viewAria')}
|
||||
className={mobileHeaderIconButtonClass}
|
||||
>
|
||||
<RiStackLine className="h-5 w-5" />
|
||||
<Icon name="stack" className="h-5 w-5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
@@ -2128,7 +2126,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover"
|
||||
aria-label={t('header.services.closeAria')}
|
||||
>
|
||||
<RiCloseLine className="h-5 w-5" />
|
||||
<Icon name="close" className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2187,7 +2185,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
disabled={isQuotaLoading || isUsageRefreshSpinning}
|
||||
aria-label={t('header.services.refreshRateLimitsAria')}
|
||||
>
|
||||
<RiRefreshLine className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
|
||||
<Icon name="refresh" className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2279,9 +2277,9 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
{family.familyLabel}
|
||||
</span>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
@@ -2348,7 +2346,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
)}
|
||||
aria-label={rightDrawerOpen ? 'Close git sidebar' : 'Open git sidebar'}
|
||||
>
|
||||
<RiLayoutRightLine className="h-5 w-5" />
|
||||
<Icon name="layout-right" className="h-5 w-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowDownSLine,
|
||||
RiGlobalLine,
|
||||
RiLoader4Line,
|
||||
RiPlayLine,
|
||||
RiSearchLine,
|
||||
RiStopLine,
|
||||
} from '@remixicon/react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -17,6 +8,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
@@ -737,9 +729,9 @@ export const ProjectActionsButton = ({
|
||||
}
|
||||
|
||||
const selectedIconKey = (resolvedSelected.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
|
||||
const SelectedIcon = resolvedSelected.id === AUTO_DISCOVER_ACTION_ID
|
||||
? RiSearchLine
|
||||
: PROJECT_ACTION_ICON_MAP[selectedIconKey] || RiPlayLine;
|
||||
const selectedIconName = resolvedSelected.id === AUTO_DISCOVER_ACTION_ID
|
||||
? 'search'
|
||||
: PROJECT_ACTION_ICON_MAP[selectedIconKey] || 'play';
|
||||
const selectedButtonLabel = formatActionButtonLabel(
|
||||
resolvedSelected.name,
|
||||
t('projectActions.label.fallbackAction'),
|
||||
@@ -778,10 +770,10 @@ export const ProjectActionsButton = ({
|
||||
: t('projectActions.actions.runNamedAria', { name: resolvedSelected.name })}
|
||||
>
|
||||
{isStoppingSelected || isWaitingForSelectedPreview
|
||||
? <RiLoader4Line className="h-5 w-5 animate-spin text-[var(--status-warning)]" />
|
||||
? <Icon name="loader-4" className="h-5 w-5 animate-spin text-[var(--status-warning)]" />
|
||||
: selectedRunning
|
||||
? <RiStopLine className="h-5 w-5 text-[var(--status-warning)]" />
|
||||
: <SelectedIcon className="h-5 w-5" />}
|
||||
? <Icon name="stop" className="h-5 w-5 text-[var(--status-warning)]" />
|
||||
: <Icon name={selectedIconName} className="h-5 w-5" />}
|
||||
</button>
|
||||
{showSelectedPreviewButton ? (
|
||||
<Tooltip>
|
||||
@@ -792,7 +784,7 @@ export const ProjectActionsButton = ({
|
||||
aria-label={t('projectActions.actions.openPreview')}
|
||||
onClick={handleOpenSelectedPreview}
|
||||
>
|
||||
<RiGlobalLine className="h-4 w-4" />
|
||||
<Icon name="global" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={6}>{t('projectActions.actions.openPreview')}</TooltipContent>
|
||||
@@ -805,20 +797,20 @@ export const ProjectActionsButton = ({
|
||||
className="app-region-no-drag -ml-1 inline-flex h-9 w-5 items-center justify-center rounded-[10px] text-muted-foreground hover:bg-interactive-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label={t('projectActions.actions.chooseActionAria')}
|
||||
>
|
||||
<RiArrowDownSLine className="h-3.5 w-3.5" />
|
||||
<Icon name="arrow-down-s" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-52 max-h-[70vh] overflow-y-auto">
|
||||
<DropdownMenuItem className="flex items-center gap-2" onClick={openProjectActionsSettings}>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
<span className="typography-ui-label text-foreground">{t('projectActions.actions.addNewAction')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{displayActions.map((entry) => {
|
||||
const iconKey = (entry.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
|
||||
const Icon = entry.id === AUTO_DISCOVER_ACTION_ID
|
||||
? RiSearchLine
|
||||
: PROJECT_ACTION_ICON_MAP[iconKey] || RiPlayLine;
|
||||
const iconName = entry.id === AUTO_DISCOVER_ACTION_ID
|
||||
? 'search'
|
||||
: PROJECT_ACTION_ICON_MAP[iconKey] || 'play';
|
||||
const runKey = toProjectActionRunKey(normalizedDirectory, entry.id);
|
||||
const runState = projectActionRuns[runKey];
|
||||
const isRunning = Boolean(runState);
|
||||
@@ -832,12 +824,12 @@ export const ProjectActionsButton = ({
|
||||
handleSelectAction(entry, true);
|
||||
}}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<Icon name={iconName} className="h-4 w-4" />
|
||||
<span className="typography-ui-label text-foreground truncate">{entry.name}</span>
|
||||
{isStopping || runState?.status === 'waiting-for-preview'
|
||||
? <RiLoader4Line className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
? <Icon name="loader-4" className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
: isRunning
|
||||
? <RiStopLine className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
|
||||
? <Icon name="stop" className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
|
||||
: null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
@@ -873,10 +865,10 @@ export const ProjectActionsButton = ({
|
||||
>
|
||||
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
{isStoppingSelected || isWaitingForSelectedPreview
|
||||
? <RiLoader4Line className="h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
? <Icon name="loader-4" className="h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
: selectedRunning
|
||||
? <RiStopLine className="h-4 w-4 text-[var(--status-warning)]" />
|
||||
: <SelectedIcon className="h-4 w-4" />}
|
||||
? <Icon name="stop" className="h-4 w-4 text-[var(--status-warning)]" />
|
||||
: <Icon name={selectedIconName} className="h-4 w-4" />}
|
||||
</span>
|
||||
{!compact ? <span className="header-open-label whitespace-nowrap">{selectedButtonLabel}</span> : null}
|
||||
</button>
|
||||
@@ -894,7 +886,7 @@ export const ProjectActionsButton = ({
|
||||
)}
|
||||
aria-label={t('projectActions.actions.openPreview')}
|
||||
>
|
||||
<RiGlobalLine className="h-4 w-4" />
|
||||
<Icon name="global" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={6}>{t('projectActions.actions.openPreview')}</TooltipContent>
|
||||
@@ -912,20 +904,20 @@ export const ProjectActionsButton = ({
|
||||
)}
|
||||
aria-label={t('projectActions.actions.chooseActionAria')}
|
||||
>
|
||||
<RiArrowDownSLine className="h-4 w-4" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="center" className="w-52 max-h-[70vh] overflow-y-auto" style={{ translate: '-30px 0' }}>
|
||||
<DropdownMenuItem className="flex items-center gap-2" onClick={openProjectActionsSettings}>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
<span className="typography-ui-label text-foreground">{t('projectActions.actions.addNewAction')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{displayActions.map((entry) => {
|
||||
const iconKey = (entry.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
|
||||
const Icon = entry.id === AUTO_DISCOVER_ACTION_ID
|
||||
? RiSearchLine
|
||||
: PROJECT_ACTION_ICON_MAP[iconKey] || RiPlayLine;
|
||||
const iconName = entry.id === AUTO_DISCOVER_ACTION_ID
|
||||
? 'search'
|
||||
: PROJECT_ACTION_ICON_MAP[iconKey] || 'play';
|
||||
const runKey = toProjectActionRunKey(normalizedDirectory, entry.id);
|
||||
const runState = projectActionRuns[runKey];
|
||||
const isRunning = Boolean(runState);
|
||||
@@ -939,12 +931,12 @@ export const ProjectActionsButton = ({
|
||||
handleSelectAction(entry);
|
||||
}}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<Icon name={iconName} className="h-4 w-4" />
|
||||
<span className="typography-ui-label text-foreground truncate">{entry.name}</span>
|
||||
{isStopping || runState?.status === 'waiting-for-preview'
|
||||
? <RiLoader4Line className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
? <Icon name="loader-4" className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
|
||||
: isRunning
|
||||
? <RiStopLine className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
|
||||
? <Icon name="stop" className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
|
||||
: null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PROJECT_ICONS, PROJECT_COLORS, PROJECT_COLOR_MAP, getProjectIconImageUr
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
interface ProjectEditDialogProps {
|
||||
open: boolean;
|
||||
@@ -329,7 +330,7 @@ export const ProjectEditDialog: React.FC<ProjectEditDialogProps> = ({
|
||||
<span className="w-4 h-0.5 bg-muted-foreground/40 rotate-45 rounded-full" />
|
||||
</button>
|
||||
{PROJECT_ICONS.map((i) => {
|
||||
const IconComponent = i.Icon;
|
||||
const iconName = i.Icon;
|
||||
return (
|
||||
<button
|
||||
key={i.key}
|
||||
@@ -343,7 +344,7 @@ export const ProjectEditDialog: React.FC<ProjectEditDialogProps> = ({
|
||||
)}
|
||||
title={i.label}
|
||||
>
|
||||
<IconComponent
|
||||
<Icon name={iconName}
|
||||
className="w-4 h-4"
|
||||
style={currentColorVar ? { color: currentColorVar } : undefined}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { RiBookletLine, RiFolder3Line, RiGitBranchLine } from '@remixicon/react';
|
||||
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { ProjectNotesTodoPanel } from '@/components/session/ProjectNotesTodoPanel';
|
||||
import { GitView } from '@/components/views/GitView';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useGitStore } from '@/stores/useGitStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
@@ -103,17 +103,17 @@ export const RightSidebarTabs: React.FC = () => {
|
||||
{
|
||||
id: 'git',
|
||||
label: t('layout.rightSidebar.git'),
|
||||
icon: <RiGitBranchLine className="h-3.5 w-3.5" />,
|
||||
icon: <Icon name="git-branch" className="h-3.5 w-3.5" />,
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
label: t('layout.rightSidebar.files'),
|
||||
icon: <RiFolder3Line className="h-3.5 w-3.5" />,
|
||||
icon: <Icon name="folder-3" className="h-3.5 w-3.5" />,
|
||||
},
|
||||
{
|
||||
id: 'context',
|
||||
label: t('layout.rightSidebar.context'),
|
||||
icon: <RiBookletLine className="h-3.5 w-3.5" />,
|
||||
icon: <Icon name="booklet" className="h-3.5 w-3.5" />,
|
||||
},
|
||||
], [t]);
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
RiFileAddLine,
|
||||
RiFileCopyLine,
|
||||
RiFolder3Fill,
|
||||
RiFolderAddLine,
|
||||
RiFolderOpenFill,
|
||||
RiFolderReceivedLine,
|
||||
RiLoader4Line,
|
||||
RiMore2Fill,
|
||||
RiRefreshLine,
|
||||
RiSearchLine,
|
||||
RiDownloadLine,
|
||||
} from '@remixicon/react';
|
||||
|
||||
import { toast } from '@/components/ui';
|
||||
import {
|
||||
@@ -48,6 +32,7 @@ import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { cn, getRevealLabelKey } from '@/lib/utils';
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
@@ -221,9 +206,9 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
>
|
||||
{isDir ? (
|
||||
isExpanded ? (
|
||||
<RiFolderOpenFill className="h-4 w-4 flex-shrink-0 text-primary/60" />
|
||||
<Icon name="folder-open-fill" className="h-4 w-4 flex-shrink-0 text-primary/60" />
|
||||
) : (
|
||||
<RiFolder3Fill className="h-4 w-4 flex-shrink-0 text-primary/60" />
|
||||
<Icon name="folder-3-fill" className="h-4 w-4 flex-shrink-0 text-primary/60" />
|
||||
)
|
||||
) : (
|
||||
getFileIcon(node.path, node.extension)
|
||||
@@ -252,13 +237,13 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
className="h-6 w-6"
|
||||
onClick={handleMenuButtonClick}
|
||||
>
|
||||
<RiMore2Fill className="h-4 w-4" />
|
||||
<Icon name="more-2-fill" className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" side="bottom" onCloseAutoFocus={() => setContextMenuPath(null)}>
|
||||
{canRename && (
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('rename', node); }}>
|
||||
<RiEditLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.rename')}
|
||||
<Icon name="edit" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.rename')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={(e) => {
|
||||
@@ -271,19 +256,19 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
toast.error(t('sidebarFilesTree.toast.copyFailed'));
|
||||
});
|
||||
}}>
|
||||
<RiFileCopyLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.copyPath')}
|
||||
<Icon name="file-copy" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.copyPath')}
|
||||
</DropdownMenuItem>
|
||||
{!isDir && downloadFile && (
|
||||
<DropdownMenuItem onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void downloadFile(node.path);
|
||||
}}>
|
||||
<RiDownloadLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.save')}
|
||||
<Icon name="download" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.save')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canReveal && (
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onRevealPath(node.path); }}>
|
||||
<RiFolderReceivedLine className="mr-2 h-4 w-4" /> {t(getRevealLabelKey())}
|
||||
<Icon name="folder-received" className="mr-2 h-4 w-4" /> {t(getRevealLabelKey())}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{isDir && (canCreateFile || canCreateFolder) && (
|
||||
@@ -291,12 +276,12 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
<DropdownMenuSeparator />
|
||||
{canCreateFile && (
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('createFile', node); }}>
|
||||
<RiFileAddLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFile')}
|
||||
<Icon name="file-add" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFile')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canCreateFolder && (
|
||||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('createFolder', node); }}>
|
||||
<RiFolderAddLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFolder')}
|
||||
<Icon name="folder-add" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFolder')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</>
|
||||
@@ -308,7 +293,7 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
onClick={(e) => { e.stopPropagation(); onOpenDialog('delete', node); }}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<RiDeleteBinLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.delete')}
|
||||
<Icon name="delete-bin" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.delete')}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
@@ -817,7 +802,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
<section className="flex h-full min-h-0 flex-col overflow-hidden bg-sidebar">
|
||||
<div className="flex items-center gap-2 border-b border-border/40 px-3 py-2">
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<RiSearchLine className="pointer-events-none absolute left-2 top-2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="pointer-events-none absolute left-2 top-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
value={searchQuery}
|
||||
@@ -835,7 +820,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
searchInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -847,7 +832,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
className="h-8 w-8 p-0 flex-shrink-0"
|
||||
title={t('sidebarFilesTree.actions.newFileTitle')}
|
||||
>
|
||||
<RiFileAddLine className="h-4 w-4" />
|
||||
<Icon name="file-add" className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{canCreateFolder && (
|
||||
@@ -858,11 +843,11 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
className="h-8 w-8 p-0 flex-shrink-0"
|
||||
title={t('sidebarFilesTree.actions.newFolderTitle')}
|
||||
>
|
||||
<RiFolderAddLine className="h-4 w-4" />
|
||||
<Icon name="folder-add" className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={() => void refreshRoot()} className="h-8 w-8 p-0 flex-shrink-0" title={t('sidebarFilesTree.actions.refreshTitle')}>
|
||||
<RiRefreshLine className="h-4 w-4" />
|
||||
<Icon name="refresh" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -870,7 +855,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
<ul className="flex flex-col">
|
||||
{searching ? (
|
||||
<li className="flex items-center gap-1.5 px-2 py-1 typography-meta text-muted-foreground">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('sidebarFilesTree.state.searching')}
|
||||
</li>
|
||||
) : searchResults.length > 0 ? (
|
||||
@@ -956,7 +941,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
onClick={() => void handleDialogSubmit()}
|
||||
disabled={isDialogSubmitting || (activeDialog !== 'delete' && !dialogInputValue.trim())}
|
||||
>
|
||||
{isDialogSubmitting ? <RiLoader4Line className="animate-spin" /> : (
|
||||
{isDialogSubmitting ? <Icon name="loader-4" className="animate-spin" /> : (
|
||||
activeDialog === 'delete' ? t('sidebarFilesTree.dialog.delete.confirm') : t('sidebarFilesTree.dialog.confirm')
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useI18n } from '@/lib/i18n';
|
||||
import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
||||
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
|
||||
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
|
||||
import { useUpdateStore } from '@/stores/useUpdateStore';
|
||||
@@ -30,7 +31,6 @@ import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
|
||||
import type { UsageWindow } from '@/types';
|
||||
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
|
||||
import { RiAddLine, RiArrowLeftLine, RiRefreshLine, RiRobot2Line, RiSettings3Line, RiTimerLine } from '@remixicon/react';
|
||||
|
||||
const SettingsView = lazyWithChunkRecovery(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView })));
|
||||
|
||||
@@ -220,7 +220,6 @@ export const VSCodeLayout: React.FC = () => {
|
||||
setCurrentView('sessions');
|
||||
}, []);
|
||||
|
||||
|
||||
// Listen for connection status changes
|
||||
React.useEffect(() => {
|
||||
// Catch up with the latest status even if the extension posted the connection message
|
||||
@@ -695,7 +694,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label={t('vscodeLayout.actions.backToSessionsAria')}
|
||||
>
|
||||
<RiArrowLeftLine className="h-5 w-5" />
|
||||
<Icon name="arrow-left" className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-sm font-medium truncate flex-1" title={title}>{title}</h1>
|
||||
@@ -705,7 +704,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label={t('vscodeLayout.actions.newSessionAria')}
|
||||
>
|
||||
<RiAddLine className="h-5 w-5" />
|
||||
<Icon name="add" className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
{onAgentManager && (
|
||||
@@ -714,7 +713,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label={t('vscodeLayout.actions.openAgentManagerAria')}
|
||||
>
|
||||
<RiRobot2Line className="h-5 w-5" />
|
||||
<Icon name="robot-2" className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
{showMcp && (
|
||||
@@ -737,7 +736,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
disabled={isQuotaLoading}
|
||||
>
|
||||
<RiTimerLine className="h-5 w-5" />
|
||||
<Icon name="timer" className="h-5 w-5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
@@ -785,7 +784,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
disabled={isQuotaLoading}
|
||||
aria-label={t('vscodeLayout.quota.actions.refreshAria')}
|
||||
>
|
||||
<RiRefreshLine className="h-4 w-4" />
|
||||
<Icon name="refresh" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
@@ -870,7 +869,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label={t('vscodeLayout.actions.settingsAria')}
|
||||
>
|
||||
<RiSettings3Line className="h-5 w-5" />
|
||||
<Icon name="settings-3" className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
{showContextUsage && stableContextUsage && stableContextUsage.totalTokens > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user