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:
@@ -7,12 +7,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
RiFolderLine,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { SessionFolder } from '@/stores/useSessionFoldersStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -67,7 +62,7 @@ export const BulkActionBar: React.FC<Props> = ({
|
||||
className={iconButtonClass}
|
||||
aria-label={t('sessions.sidebar.bulkActions.moveToFolder')}
|
||||
>
|
||||
<RiFolderLine className="h-4 w-4" />
|
||||
<Icon name="folder" className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
@@ -87,7 +82,7 @@ export const BulkActionBar: React.FC<Props> = ({
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={onCreateFolderAndMove}>
|
||||
<RiAddLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="add" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.folders.newFolderEllipsis')}
|
||||
</DropdownMenuItem>
|
||||
{canRemoveFromFolder ? (
|
||||
@@ -95,7 +90,7 @@ export const BulkActionBar: React.FC<Props> = ({
|
||||
onClick={onRemoveFromFolder}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<RiCloseLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="close" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.folders.removeFromFolder')}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
@@ -111,7 +106,7 @@ export const BulkActionBar: React.FC<Props> = ({
|
||||
className={cn(destructiveIconButtonClass)}
|
||||
aria-label={destructiveLabel}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}><p>{destructiveLabel}</p></TooltipContent>
|
||||
@@ -125,7 +120,7 @@ export const BulkActionBar: React.FC<Props> = ({
|
||||
className={iconButtonClass}
|
||||
aria-label={t('sessions.sidebar.header.actions.exitSelection')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}><p>{t('sessions.sidebar.header.actions.exitSelection')}</p></TooltipContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { RiCheckboxBlankLine, RiCheckboxLine } from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
@@ -65,7 +65,7 @@ export function SessionDeleteConfirmDialog(props: {
|
||||
className="inline-flex items-center gap-1.5 typography-ui-label text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50"
|
||||
aria-pressed={!showDeletionDialog}
|
||||
>
|
||||
{!showDeletionDialog ? <RiCheckboxLine className="h-4 w-4 text-primary" /> : <RiCheckboxBlankLine className="h-4 w-4" />}
|
||||
{!showDeletionDialog ? <Icon name="checkbox" className="h-4 w-4 text-primary" /> : <Icon name="checkbox-blank" className="h-4 w-4" />}
|
||||
{t('sessions.sidebar.dialogs.neverAsk')}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -135,7 +135,7 @@ export function BulkSessionDeleteConfirmDialog(props: {
|
||||
className="inline-flex items-center gap-1.5 typography-ui-label text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50"
|
||||
aria-pressed={!showDeletionDialog}
|
||||
>
|
||||
{!showDeletionDialog ? <RiCheckboxLine className="h-4 w-4 text-primary" /> : <RiCheckboxBlankLine className="h-4 w-4" />}
|
||||
{!showDeletionDialog ? <Icon name="checkbox" className="h-4 w-4 text-primary" /> : <Icon name="checkbox-blank" className="h-4 w-4" />}
|
||||
{t('sessions.sidebar.dialogs.neverAsk')}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArchiveLine,
|
||||
RiArrowDownSLine,
|
||||
RiArrowLeftLongLine,
|
||||
RiArrowRightSLine,
|
||||
RiDeleteBinLine,
|
||||
RiGitBranchLine,
|
||||
} from '@remixicon/react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { sessionEvents } from '@/lib/sessionEvents';
|
||||
import type { MainTab } from '@/stores/useUIStore';
|
||||
@@ -536,7 +528,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex shrink-0 items-center gap-1 leading-none align-middle">
|
||||
<span className="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center">
|
||||
<RiGitBranchLine
|
||||
<Icon name="git-branch"
|
||||
className={cn('h-3.5 w-3.5 shrink-0', alwaysShowActions ? 'hidden' : 'group-hover/gh:hidden')}
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}
|
||||
/>
|
||||
@@ -544,7 +536,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
'text-muted-foreground h-3.5 w-3.5 items-center justify-center',
|
||||
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/gh:inline-flex',
|
||||
)}>
|
||||
{isCollapsed ? <RiArrowRightSLine className="h-3.5 w-3.5" /> : <RiArrowDownSLine className="h-3.5 w-3.5" />}
|
||||
{isCollapsed ? <Icon name="arrow-right-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-down-s" className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
</span>
|
||||
{prIndicator.url ? (
|
||||
@@ -568,7 +560,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
{baseBranchLabel && headBranchLabel ? (
|
||||
<>
|
||||
<span>{baseBranchLabel}</span>
|
||||
<RiArrowLeftLongLine className="mx-0.5 inline h-3 w-3 align-[-2px]" />
|
||||
<Icon name="arrow-left-long" className="mx-0.5 inline h-3 w-3 align-[-2px]" />
|
||||
<span>{headBranchLabel}</span>
|
||||
</>
|
||||
) : (
|
||||
@@ -593,12 +585,12 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
) : group.isArchivedBucket ? (
|
||||
<span className="inline-flex min-w-0 max-w-full items-center gap-1">
|
||||
<span className="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center">
|
||||
<RiArchiveLine className={cn('h-3.5 w-3.5 shrink-0 text-muted-foreground', alwaysShowActions ? 'hidden' : 'group-hover/gh:hidden')} />
|
||||
<Icon name="archive" className={cn('h-3.5 w-3.5 shrink-0 text-muted-foreground', alwaysShowActions ? 'hidden' : 'group-hover/gh:hidden')} />
|
||||
<span className={cn(
|
||||
'text-muted-foreground h-3.5 w-3.5 items-center justify-center',
|
||||
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/gh:inline-flex',
|
||||
)}>
|
||||
{isCollapsed ? <RiArrowRightSLine className="h-3.5 w-3.5" /> : <RiArrowDownSLine className="h-3.5 w-3.5" />}
|
||||
{isCollapsed ? <Icon name="arrow-right-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-down-s" className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{renderHighlightedText(group.label, normalizedSessionSearchQuery)}</span>
|
||||
@@ -606,7 +598,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
) : (!group.isMain || group.worktree) ? (
|
||||
<span className="inline-flex min-w-0 max-w-full items-center gap-1">
|
||||
<span className="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center">
|
||||
<RiGitBranchLine
|
||||
<Icon name="git-branch"
|
||||
className={cn('h-3.5 w-3.5 shrink-0 text-muted-foreground', alwaysShowActions ? 'hidden' : 'group-hover/gh:hidden')}
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}
|
||||
/>
|
||||
@@ -614,7 +606,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
'text-muted-foreground h-3.5 w-3.5 items-center justify-center',
|
||||
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/gh:inline-flex',
|
||||
)}>
|
||||
{isCollapsed ? <RiArrowRightSLine className="h-3.5 w-3.5" /> : <RiArrowDownSLine className="h-3.5 w-3.5" />}
|
||||
{isCollapsed ? <Icon name="arrow-right-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-down-s" className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{renderHighlightedText(group.label, normalizedSessionSearchQuery)}</span>
|
||||
@@ -626,16 +618,15 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
{showBranchSubtitle && statusLine ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-1.5 leading-tight">
|
||||
{group.isArchivedBucket ? (
|
||||
<RiArchiveLine className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="archive" className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
) : (!group.isMain || isGitProject) ? (
|
||||
showInlinePrTitle && prIndicator ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
|
||||
<RiGitBranchLine
|
||||
className="h-3.5 w-3.5 text-muted-foreground"
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}
|
||||
/>
|
||||
<Icon name="git-branch" className="h-3.5 w-3.5 text-muted-foreground"
|
||||
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}/>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6} align="start" className="max-w-sm">
|
||||
@@ -645,7 +636,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
{baseBranchLabel && headBranchLabel ? (
|
||||
<>
|
||||
<span>{baseBranchLabel}</span>
|
||||
<RiArrowLeftLongLine className="mx-0.5 inline h-3 w-3 align-[-2px]" />
|
||||
<Icon name="arrow-left-long" className="mx-0.5 inline h-3 w-3 align-[-2px]" />
|
||||
<span>{headBranchLabel}</span>
|
||||
</>
|
||||
) : (
|
||||
@@ -666,10 +657,9 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<RiGitBranchLine
|
||||
className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground"
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}
|
||||
/>
|
||||
<Icon name="git-branch" className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground"
|
||||
|
||||
style={branchIconColor ? { color: branchIconColor } : undefined}/>
|
||||
)
|
||||
) : null}
|
||||
<span
|
||||
@@ -698,7 +688,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
aria-label={t('sessions.sidebar.group.actions.deleteArchivedInGroupAria', { label: group.label })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.group.actions.deleteArchivedSessions')}</p></TooltipContent>
|
||||
@@ -722,7 +712,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
aria-label={t('sessions.sidebar.group.actions.deleteGroupAria', { label: group.label })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.group.actions.deleteWorktree')}</p></TooltipContent>
|
||||
@@ -745,7 +735,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
aria-label={t('sessions.sidebar.group.actions.newDraftInGroupAria', { label: group.label })}
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.project.actions.newDraftSession')}</p></TooltipContent>
|
||||
|
||||
@@ -11,34 +11,12 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArchiveLine,
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
RiChat4Line,
|
||||
RiCheckLine,
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
RiDownloadLine,
|
||||
RiErrorWarningLine,
|
||||
RiFileCopyLine,
|
||||
RiFolderLine,
|
||||
RiLinkUnlinkM,
|
||||
RiMore2Line,
|
||||
RiPencilAiLine,
|
||||
RiPushpinLine,
|
||||
RiShare2Line,
|
||||
RiShieldLine,
|
||||
RiUnpinLine,
|
||||
RiGitBranchLine,
|
||||
RiWindowLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { canUseElectronDesktopIPC, invokeDesktop, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { buildExportFilename, downloadAsMarkdown, formatSessionAsMarkdown, getExportRevealLabelKey, revealExportedMarkdown, saveAsMarkdownDesktop } from '@/lib/exportSession';
|
||||
import type { ChildSessionExport } from '@/lib/exportSession';
|
||||
import { buildSessionMessageRecordsSnapshot, useDirectoryStore, useGlobalSessionStatus, useSession, useSessionPermissions } from '@/sync/sync-context';
|
||||
@@ -479,7 +457,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
title={t('sessions.sidebar.session.rename.save')}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiCheckLine className="size-4" />
|
||||
<Icon name="check" className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -488,17 +466,17 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
title={t('sessions.sidebar.session.rename.cancel')}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<Icon name="close" className="size-4" />
|
||||
</button>
|
||||
</form>
|
||||
{!isMinimalMode ? (
|
||||
<div className="flex items-center justify-between gap-3 text-muted-foreground/60 min-w-0 overflow-hidden leading-tight" style={{ fontSize: 'calc(var(--text-ui-label) * 0.85)' }}>
|
||||
<div className="flex min-w-0 items-center gap-1.5 overflow-hidden">
|
||||
{hasChildren ? <span className="inline-flex items-center justify-center flex-shrink-0">{isExpanded ? <RiArrowDownSLine className="h-3 w-3" /> : <RiArrowRightSLine className="h-3 w-3" />}</span> : null}
|
||||
{hasChildren ? <span className="inline-flex items-center justify-center flex-shrink-0">{isExpanded ? <Icon name="arrow-down-s" className="h-3 w-3" /> : <Icon name="arrow-right-s" className="h-3 w-3" />}</span> : null}
|
||||
<span className="flex-shrink-0">{sessionUpdatedLabel}</span>
|
||||
{sessionDiffStats ? <span className="flex flex-shrink-0 items-center gap-0 text-[0.92em]"><span className="text-status-success/80">+{sessionDiffStats.additions}</span><span className="text-status-error/65">/-{sessionDiffStats.deletions}</span></span> : null}
|
||||
{hasSecondaryProjectLabel ? <span className="truncate">{secondaryMeta?.projectLabel}</span> : null}
|
||||
{hasSecondaryBranchLabel ? <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" /><span className="truncate">{secondaryMeta?.branchLabel}</span></span> : null}
|
||||
{hasSecondaryBranchLabel ? <span className="inline-flex min-w-0 items-center gap-0.5"><Icon name="git-branch" className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" /><span className="truncate">{secondaryMeta?.branchLabel}</span></span> : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -537,7 +515,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
)}
|
||||
>
|
||||
{showStatusMarker ? statusMarkerContent : null}
|
||||
{isPinnedSession ? <RiPushpinLine className="h-3 w-3 flex-shrink-0 text-primary" aria-label={t('sessions.sidebar.session.status.pinned')} /> : null}
|
||||
{isPinnedSession ? <Icon name="pushpin" className="h-3 w-3 flex-shrink-0 text-primary" aria-label={t('sessions.sidebar.session.status.pinned')}/> : null}
|
||||
</span>
|
||||
) : null;
|
||||
const subsessionChevron = hasChildren ? (
|
||||
@@ -566,12 +544,12 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
? t('sessions.sidebar.session.subsessions.collapse')
|
||||
: t('sessions.sidebar.session.subsessions.expand')}
|
||||
>
|
||||
{isExpanded ? <RiArrowDownSLine className="h-3 w-3" /> : <RiArrowRightSLine className="h-3 w-3" />}
|
||||
{isExpanded ? <Icon name="arrow-down-s" className="h-3 w-3" /> : <Icon name="arrow-right-s" className="h-3 w-3" />}
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const streamingIndicator = isZombie
|
||||
? <RiErrorWarningLine className="h-4 w-4 text-status-warning" />
|
||||
? <Icon name="error-warning" className="h-4 w-4 text-status-warning" />
|
||||
: null;
|
||||
|
||||
const handleMenuOpenChange = (open: boolean) => {
|
||||
@@ -663,33 +641,33 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
}}
|
||||
className="[&>svg]:mr-1"
|
||||
>
|
||||
<RiPencilAiLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="pencil-ai" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.session.menu.rename')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => togglePinnedSession(session.id)} className="[&>svg]:mr-1">
|
||||
{isPinnedSession ? <RiUnpinLine className="mr-1 h-4 w-4" /> : <RiPushpinLine className="mr-1 h-4 w-4" />}
|
||||
{isPinnedSession ? <Icon name="unpin" className="mr-1 h-4 w-4" /> : <Icon name="pushpin" className="mr-1 h-4 w-4" />}
|
||||
{isPinnedSession ? t('sessions.sidebar.session.menu.unpin') : t('sessions.sidebar.session.menu.pin')}
|
||||
</DropdownMenuItem>
|
||||
{!resolvedSession.share ? (
|
||||
<DropdownMenuItem onClick={() => handleShareSession(resolvedSession)} className="[&>svg]:mr-1">
|
||||
<RiShare2Line className="mr-1 h-4 w-4" />
|
||||
<Icon name="share-2" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.session.menu.share')}
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuItem onClick={() => { if (resolvedSession.share?.url) handleCopyShareUrl(resolvedSession.share.url, session.id); }} className="[&>svg]:mr-1">
|
||||
{copiedSessionId === session.id
|
||||
? <><RiCheckLine className="mr-1 h-4 w-4" style={{ color: 'var(--status-success)' }} />{t('sessions.sidebar.session.menu.copied')}</>
|
||||
: <><RiFileCopyLine className="mr-1 h-4 w-4" />{t('sessions.sidebar.session.menu.copyLink')}</>}
|
||||
? <><Icon name="check" className="mr-1 h-4 w-4" style={{ color: 'var(--status-success)' }}/>{t('sessions.sidebar.session.menu.copied')}</>
|
||||
: <><Icon name="file-copy" className="mr-1 h-4 w-4" />{t('sessions.sidebar.session.menu.copyLink')}</>}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleUnshareSession(session.id)} className="[&>svg]:mr-1">
|
||||
<RiLinkUnlinkM className="mr-1 h-4 w-4" />
|
||||
<Icon name="link-unlink-m" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.session.menu.unshare')}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => { void handleExportSession(); }} className="[&>svg]:mr-1">
|
||||
<RiDownloadLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="download" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.session.menu.exportMarkdown')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -700,7 +678,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="[&>svg]:mr-1"><RiFolderLine className="h-4 w-4" />{t('sessions.sidebar.folders.moveToFolder')}</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubTrigger className="[&>svg]:mr-1"><Icon name="folder" className="h-4 w-4" />{t('sessions.sidebar.folders.moveToFolder')}</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="min-w-[180px]">
|
||||
{scopeFolders.length === 0 ? (
|
||||
<DropdownMenuItem disabled className="text-muted-foreground">{t('sessions.sidebar.folders.none')}</DropdownMenuItem>
|
||||
@@ -708,18 +686,18 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
scopeFolders.map((folder) => (
|
||||
<DropdownMenuItem key={folder.id} onClick={() => { if (currentFolderId === folder.id) removeSessionFromFolder(sessionDirectory, session.id); else addSessionToFolder(sessionDirectory, folder.id, session.id); }}>
|
||||
<span className="flex-1 truncate">{folder.name}</span>
|
||||
{currentFolderId === folder.id ? <RiCheckLine className="ml-2 h-3.5 w-3.5 text-primary flex-shrink-0" /> : null}
|
||||
{currentFolderId === folder.id ? <Icon name="check" className="ml-2 h-3.5 w-3.5 text-primary flex-shrink-0" /> : null}
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => { const newFolder = createFolderAndStartRename(sessionDirectory); if (!newFolder) return; addSessionToFolder(sessionDirectory, newFolder.id, session.id); }}>
|
||||
<RiAddLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="add" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.folders.newFolderEllipsis')}
|
||||
</DropdownMenuItem>
|
||||
{currentFolderId ? (
|
||||
<DropdownMenuItem onClick={() => { removeSessionFromFolder(sessionDirectory, session.id); }} className="text-destructive focus:text-destructive">
|
||||
<RiCloseLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="close" className="mr-1 h-4 w-4" />
|
||||
{t('sessions.sidebar.folders.removeFromFolder')}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
@@ -742,7 +720,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
}}
|
||||
className="[&>svg]:mr-1"
|
||||
>
|
||||
<RiChat4Line className="mr-1 h-4 w-4" />
|
||||
<Icon name="chat-4" className="mr-1 h-4 w-4" />
|
||||
<span className="truncate">{t('sessions.sidebar.session.menu.openInSidePanel')}</span>
|
||||
<span className="shrink-0 typography-micro px-1 rounded leading-none pb-px text-[var(--status-warning)] bg-[var(--status-warning)]/10">{t('sessions.sidebar.session.menu.betaBadge')}</span>
|
||||
</DropdownMenuItem>
|
||||
@@ -754,14 +732,14 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
onClick={handleOpenMiniChatWindow}
|
||||
className="[&>svg]:mr-1"
|
||||
>
|
||||
<RiWindowLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="window" className="mr-1 h-4 w-4" />
|
||||
<span className="truncate">{t('sessions.sidebar.session.menu.openMiniChatWindow')}</span>
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-destructive focus:text-destructive [&>svg]:mr-1" onClick={() => handleDeleteSession(session, { archivedBucket })}>
|
||||
<RiDeleteBinLine className="mr-1 h-4 w-4" />
|
||||
<Icon name="delete-bin" className="mr-1 h-4 w-4" />
|
||||
{archivedBucket ? t('sessions.sidebar.bulkActions.delete') : t('sessions.sidebar.bulkActions.archive')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -824,7 +802,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
) : null}
|
||||
{pendingPermissionCount > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 rounded bg-destructive/10 px-1 py-0.5 text-[0.7rem] text-destructive flex-shrink-0" title={t('sessions.sidebar.session.status.permissionRequired')} aria-label={t('sessions.sidebar.session.status.permissionRequired')}>
|
||||
<RiShieldLine className="h-3 w-3" />
|
||||
<Icon name="shield" className="h-3 w-3" />
|
||||
<span className="leading-none">{pendingPermissionCount}</span>
|
||||
</span>
|
||||
) : null}
|
||||
@@ -841,7 +819,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
<div className={cn('flex items-center gap-3 text-left text-muted-foreground', secondaryMeta?.branchLabel ? 'justify-between' : 'justify-start')}>
|
||||
{secondaryMeta?.branchLabel ? (
|
||||
<div className="flex min-w-0 items-center gap-1.5 overflow-hidden">
|
||||
<span className="inline-flex min-w-0 items-center gap-0.5"><RiGitBranchLine className="h-3 w-3 flex-shrink-0" /><span className="truncate">{secondaryMeta.branchLabel}</span></span>
|
||||
<span className="inline-flex min-w-0 items-center gap-0.5"><Icon name="git-branch" className="h-3 w-3 flex-shrink-0" /><span className="truncate">{secondaryMeta.branchLabel}</span></span>
|
||||
</div>
|
||||
) : null}
|
||||
{sessionDiffStats ? <span className="flex flex-shrink-0 items-center gap-0.5"><span className="text-status-success">+{sessionDiffStats.additions}</span><span className="text-status-error">-{sessionDiffStats.deletions}</span></span> : null}
|
||||
@@ -875,7 +853,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
<div className={cn('block min-w-0 flex-1 truncate typography-ui-label font-normal', isActive ? 'text-primary' : 'text-foreground')}>{renderHighlightedText(sessionTitle, normalizedSessionSearchQuery)}</div>
|
||||
{pendingPermissionCount > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 rounded bg-destructive/10 px-1 py-0.5 text-[0.7rem] text-destructive flex-shrink-0" title={t('sessions.sidebar.session.status.permissionRequired')} aria-label={t('sessions.sidebar.session.status.permissionRequired')}>
|
||||
<RiShieldLine className="h-3 w-3" />
|
||||
<Icon name="shield" className="h-3 w-3" />
|
||||
<span className="leading-none">{pendingPermissionCount}</span>
|
||||
</span>
|
||||
) : null}
|
||||
@@ -887,7 +865,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
<span className="flex-shrink-0">{sessionUpdatedLabel}</span>
|
||||
{sessionDiffStats ? <span className="flex flex-shrink-0 items-center gap-0 text-[0.92em]"><span className="text-status-success/80">+{sessionDiffStats.additions}</span><span className="text-muted-foreground/60">/</span><span className="text-status-error/65">-{sessionDiffStats.deletions}</span></span> : null}
|
||||
{hasSecondaryProjectLabel ? <span className="truncate">{secondaryMeta?.projectLabel}</span> : null}
|
||||
{hasSecondaryBranchLabel ? <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" /><span className="truncate">{secondaryMeta?.branchLabel}</span></span> : null}
|
||||
{hasSecondaryBranchLabel ? <span className="inline-flex min-w-0 items-center gap-0.5"><Icon name="git-branch" className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" /><span className="truncate">{secondaryMeta?.branchLabel}</span></span> : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -924,7 +902,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
onClick={handleQuickArchiveClick}
|
||||
onKeyDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<RiArchiveLine className={cn(isMinimalMode && !alwaysShowActions ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
||||
<Icon name="archive" className={cn(isMinimalMode && !alwaysShowActions ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left" sideOffset={8}>
|
||||
@@ -950,7 +928,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
onClick={handleMenuTriggerClick}
|
||||
onKeyDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<RiMore2Line className={cn(isMinimalMode && !alwaysShowActions ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
||||
<Icon name="more-2" className={cn(isMinimalMode && !alwaysShowActions ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
{sessionMenuContent}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { SessionNode } from './types';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
type ActivityItem = {
|
||||
node: SessionNode;
|
||||
@@ -77,7 +77,7 @@ export function SidebarActivitySections({ sections, renderSessionNode }: Props):
|
||||
aria-expanded={!isCollapsed}
|
||||
>
|
||||
<span className="inline-flex h-4 w-4 items-center justify-center text-muted-foreground">
|
||||
{isCollapsed ? <RiArrowRightSLine className="h-3.5 w-3.5" /> : <RiArrowDownSLine className="h-3.5 w-3.5" />}
|
||||
{isCollapsed ? <Icon name="arrow-right-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-down-s" className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
<span className="text-[14px] font-normal text-foreground/95">{section.title}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { RiInformationLine, RiQuestionLine, RiSettings3Line } from '@remixicon/react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type Props = {
|
||||
@@ -32,7 +32,7 @@ export function SidebarFooter({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button type="button" onClick={onOpenSettings} className={footerButtonClassName} aria-label={t('sessions.sidebar.footer.actions.settings')}>
|
||||
<RiSettings3Line className="h-4.5 w-4.5" />
|
||||
<Icon name="settings-3" className="h-4.5 w-4.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}><p>{t('sessions.sidebar.footer.actions.settings')}</p></TooltipContent>
|
||||
@@ -40,7 +40,7 @@ export function SidebarFooter({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button type="button" onClick={onOpenShortcuts} className={footerButtonClassName} aria-label={t('sessions.sidebar.footer.actions.shortcuts')}>
|
||||
<RiQuestionLine className="h-4.5 w-4.5" />
|
||||
<Icon name="question" className="h-4.5 w-4.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}><p>{t('sessions.sidebar.footer.actions.shortcuts')}</p></TooltipContent>
|
||||
@@ -48,7 +48,7 @@ export function SidebarFooter({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button type="button" onClick={onOpenAbout} className={footerButtonClassName} aria-label={t('sessions.sidebar.footer.actions.aboutOpenChamber')}>
|
||||
<RiInformationLine className="h-4.5 w-4.5" />
|
||||
<Icon name="information" className="h-4.5 w-4.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}><p>{t('sessions.sidebar.footer.actions.aboutOpenChamber')}</p></TooltipContent>
|
||||
|
||||
@@ -7,21 +7,9 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import {
|
||||
RiCheckLine,
|
||||
RiCheckboxMultipleLine,
|
||||
RiChatNewLine,
|
||||
RiEqualizer2Line,
|
||||
RiFolderAddLine,
|
||||
RiLayoutLeftLine,
|
||||
RiSearchLine,
|
||||
RiCloseLine,
|
||||
RiContractUpDownLine,
|
||||
RiExpandUpDownLine,
|
||||
RiCalendarScheduleLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ArrowsMerge } from '@/components/icons/ArrowsMerge';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
@@ -118,7 +106,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md typography-ui-label font-medium text-foreground transition-colors hover:bg-interactive-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50"
|
||||
aria-label={t('sessions.sidebar.header.actions.closeSessions')}
|
||||
>
|
||||
<RiLayoutLeftLine className="h-[18px] w-[18px]" />
|
||||
<Icon name="layout-left" className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.closeSessions')}</p></TooltipContent>
|
||||
@@ -132,7 +120,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className={headerActionButtonClass}
|
||||
aria-label={t('sessions.sidebar.header.actions.addProject')}
|
||||
>
|
||||
<RiFolderAddLine className={headerActionIconClass} />
|
||||
<Icon name="folder-add" className={headerActionIconClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.addProject')}</p></TooltipContent>
|
||||
@@ -145,7 +133,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className={headerActionButtonClass}
|
||||
aria-label={t('sessions.sidebar.header.actions.newSession')}
|
||||
>
|
||||
<RiChatNewLine className={headerActionIconClass} />
|
||||
<Icon name="chat-new" className={headerActionIconClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.newSession')}</p></TooltipContent>
|
||||
@@ -176,7 +164,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className={headerActionButtonClass}
|
||||
aria-label={t('sessions.sidebar.header.actions.scheduledTasks')}
|
||||
>
|
||||
<RiCalendarScheduleLine className={headerActionIconClass} />
|
||||
<Icon name="calendar-schedule" className={headerActionIconClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.scheduledTasks')}</p></TooltipContent>
|
||||
@@ -191,7 +179,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
aria-label={t('sessions.sidebar.header.actions.searchSessions')}
|
||||
aria-expanded={isSessionSearchOpen}
|
||||
>
|
||||
<RiSearchLine className={headerActionIconClass} />
|
||||
<Icon name="search" className={headerActionIconClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.searchSessions')}</p></TooltipContent>
|
||||
@@ -208,7 +196,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
: t('sessions.sidebar.header.actions.selectSessions')}
|
||||
aria-pressed={selectionModeEnabled}
|
||||
>
|
||||
<RiCheckboxMultipleLine className={headerActionIconClass} />
|
||||
<Icon name="checkbox-multiple" className={headerActionIconClass} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
@@ -227,7 +215,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className={headerActionButtonClass}
|
||||
aria-label={t('sessions.sidebar.header.actions.sessionDisplayMode')}
|
||||
>
|
||||
<RiEqualizer2Line className={headerActionIconClass} />
|
||||
<Icon name="equalizer-2" className={headerActionIconClass} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
@@ -239,14 +227,14 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<span>{t('sessions.sidebar.header.displayMode.default')}</span>
|
||||
{displayMode === 'default' ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
|
||||
{displayMode === 'default' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setDisplayMode('minimal')}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<span>{t('sessions.sidebar.header.displayMode.minimal')}</span>
|
||||
{displayMode === 'minimal' ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
|
||||
{displayMode === 'minimal' ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
@@ -254,15 +242,15 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<span>{t('sessions.sidebar.header.displayMode.showRecent')}</span>
|
||||
{showRecentSection ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
|
||||
{showRecentSection ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={collapseAllProjects} className="flex items-center gap-2">
|
||||
<RiContractUpDownLine className="h-4 w-4" />
|
||||
<Icon name="contract-up-down" className="h-4 w-4" />
|
||||
<span>{t('sessions.sidebar.header.displayMode.collapseAll')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={expandAllProjects} className="flex items-center gap-2">
|
||||
<RiExpandUpDownLine className="h-4 w-4" />
|
||||
<Icon name="expand-up-down" className="h-4 w-4" />
|
||||
<span>{t('sessions.sidebar.header.displayMode.expandAll')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -281,7 +269,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
<span>{t('sessions.sidebar.header.search.escapeHint')}</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<RiSearchLine className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Icon name="search" className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
ref={sessionSearchInputRef}
|
||||
value={sessionSearchQuery}
|
||||
@@ -306,7 +294,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
|
||||
className="absolute right-1 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground hover:bg-interactive-hover/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
aria-label={t('sessions.sidebar.header.search.clear')}
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
useDroppable,
|
||||
type DragEndEvent,
|
||||
} from '@dnd-kit/core';
|
||||
import { RiStickyNoteLine } from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
export const DraggableSessionRow: React.FC<{
|
||||
sessionId: string;
|
||||
@@ -122,7 +122,7 @@ export const SessionFolderDndScope: React.FC<{
|
||||
}}
|
||||
className="flex items-center rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] px-2.5 py-1 shadow-none pointer-events-none"
|
||||
>
|
||||
<RiStickyNoteLine className="h-4 w-4 text-muted-foreground mr-2 flex-shrink-0" />
|
||||
<Icon name="sticky-note" className="h-4 w-4 text-muted-foreground mr-2 flex-shrink-0" />
|
||||
<div className="min-w-0 flex-1 truncate typography-ui-label font-normal text-foreground">
|
||||
{activeDragTitle}
|
||||
</div>
|
||||
|
||||
@@ -8,16 +8,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
RiCloseLine,
|
||||
RiFolderLine,
|
||||
RiMore2Line,
|
||||
RiNodeTree,
|
||||
RiPencilAiLine,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
@@ -104,7 +95,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
setImageFailed(false);
|
||||
}, [id, projectIconImage?.updatedAt]);
|
||||
|
||||
const ProjectIcon = projectIcon ? PROJECT_ICON_MAP[projectIcon] : null;
|
||||
const projectIconName = projectIcon ? PROJECT_ICON_MAP[projectIcon] : null;
|
||||
const iconColor = projectColor ? (PROJECT_COLOR_MAP[projectColor] ?? null) : null;
|
||||
const imageUrl = !imageFailed
|
||||
? getProjectIconImageUrl({ id, iconImage: projectIconImage }, {
|
||||
@@ -186,7 +177,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
'h-3.5 w-3.5 items-center justify-center text-muted-foreground',
|
||||
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/project:inline-flex group-focus-within/project:inline-flex',
|
||||
)}>
|
||||
{isCollapsed ? <RiArrowRightSLine className="h-3.5 w-3.5" /> : <RiArrowDownSLine className="h-3.5 w-3.5" />}
|
||||
{isCollapsed ? <Icon name="arrow-right-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-down-s" className="h-3.5 w-3.5" />}
|
||||
</span>
|
||||
{imageUrl ? (
|
||||
<span
|
||||
@@ -204,10 +195,10 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
onError={() => setImageFailed(true)}
|
||||
/>
|
||||
</span>
|
||||
) : ProjectIcon ? (
|
||||
<ProjectIcon className={cn('h-3.5 w-3.5', alwaysShowActions ? 'hidden' : 'group-hover/project:hidden group-focus-within/project:hidden')} style={iconColor ? { color: iconColor } : undefined} />
|
||||
) : projectIconName ? (
|
||||
<Icon name={projectIconName} className={cn('h-3.5 w-3.5', alwaysShowActions ? 'hidden' : 'group-hover/project:hidden group-focus-within/project:hidden')} style={iconColor ? { color: iconColor } : undefined} />
|
||||
) : (
|
||||
<RiFolderLine className={cn('h-3.5 w-3.5 text-muted-foreground/80', alwaysShowActions ? 'hidden' : 'group-hover/project:hidden group-focus-within/project:hidden')} style={iconColor ? { color: iconColor } : undefined} />
|
||||
<Icon name="folder" className={cn('h-3.5 w-3.5 text-muted-foreground/80', alwaysShowActions ? 'hidden' : 'group-hover/project:hidden group-focus-within/project:hidden')} style={iconColor ? { color: iconColor } : undefined} />
|
||||
)}
|
||||
</span>
|
||||
<span className={cn(
|
||||
@@ -242,7 +233,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
)}
|
||||
aria-label={t('sessions.sidebar.project.actions.newWorktree')}
|
||||
>
|
||||
<RiNodeTree className="h-4 w-4" />
|
||||
<Icon name="node-tree" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
@@ -271,25 +262,25 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
onMouseDown={handleMenuTriggerMouseDown}
|
||||
onClick={handleMenuTriggerClick}
|
||||
>
|
||||
<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="min-w-[180px]">
|
||||
{showCreateButtons && !isRepo && !hideDirectoryControls && onNewSession && (
|
||||
<DropdownMenuItem onClick={onNewSession}>
|
||||
<RiAddLine className="mr-1.5 h-4 w-4" />
|
||||
<Icon name="add" className="mr-1.5 h-4 w-4" />
|
||||
{t('sessions.sidebar.project.actions.newSession')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={onRenameStart}>
|
||||
<RiPencilAiLine className="mr-1.5 h-4 w-4" />
|
||||
<Icon name="pencil-ai" className="mr-1.5 h-4 w-4" />
|
||||
{t('sessions.sidebar.session.menu.rename')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onClose}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<RiCloseLine className="mr-1.5 h-4 w-4" />
|
||||
<Icon name="close" className="mr-1.5 h-4 w-4" />
|
||||
{t('sessions.sidebar.project.actions.closeProject')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -314,7 +305,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
||||
? t('sessions.sidebar.project.actions.newDraftSession')
|
||||
: t('sessions.sidebar.project.actions.newSession')}
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
|
||||
Reference in New Issue
Block a user