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:
@@ -9,16 +9,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { toast } from '@/components/ui';
|
||||
import {
|
||||
RiCheckLine,
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
RiGitBranchLine,
|
||||
RiLoader4Line,
|
||||
RiPencilLine,
|
||||
RiSearchLine,
|
||||
RiSplitCellsHorizontal,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { deleteGitBranch, getGitBranches, git, renameBranch } from '@/lib/gitApi';
|
||||
import type { GitBranch, GitWorktreeInfo } from '@/lib/api/types';
|
||||
@@ -304,7 +295,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
<DialogContent className="max-w-2xl max-h-[70vh] flex flex-col overflow-hidden gap-3">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGitBranchLine className="h-5 w-5" />
|
||||
<Icon name="git-branch" className="h-5 w-5" />
|
||||
{t('branchPickerDialog.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -313,7 +304,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
</DialogHeader>
|
||||
|
||||
<div className="relative flex-shrink-0">
|
||||
<RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('branchPickerDialog.search.placeholder')}
|
||||
value={searchQuery}
|
||||
@@ -371,7 +362,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
key={branchName}
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 hover:bg-interactive-hover/30 rounded-md overflow-hidden"
|
||||
>
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
@@ -445,9 +436,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
aria-label={t('branchPickerDialog.actions.createWorktreeAria')}
|
||||
>
|
||||
{isCreatingWorktree ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RiSplitCellsHorizontal className="h-4 w-4" />
|
||||
<Icon name="split-cells-horizontal" className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
@@ -465,7 +456,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-interactive-hover/40 text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50"
|
||||
aria-label={t('branchPickerDialog.actions.renameAria')}
|
||||
>
|
||||
<RiPencilLine className="h-4 w-4" />
|
||||
<Icon name="pencil" className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
@@ -489,9 +480,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
aria-label={hasAttachedWorktree ? t('branchPickerDialog.actions.deleteWorktreeAria') : t('branchPickerDialog.actions.deleteAria')}
|
||||
>
|
||||
{isDeleting ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
@@ -520,9 +511,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
aria-label={t('branchPickerDialog.actions.confirmRenameAria')}
|
||||
>
|
||||
{isRenaming ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RiCheckLine className="h-4 w-4" />
|
||||
<Icon name="check" className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
@@ -531,7 +522,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-interactive-hover/40 text-muted-foreground hover:text-foreground transition-colors"
|
||||
aria-label={t('branchPickerDialog.actions.cancelRenameAria')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -557,9 +548,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
aria-label={t('branchPickerDialog.actions.confirmDeleteAria')}
|
||||
>
|
||||
{isDeleting ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RiCheckLine className="h-4 w-4" />
|
||||
<Icon name="check" className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
@@ -568,7 +559,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-interactive-hover/40 text-muted-foreground hover:text-foreground transition-colors"
|
||||
aria-label={t('branchPickerDialog.actions.cancelDeleteAria')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { RiFolderLine, RiRefreshLine } from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { opencodeClient, type FilesystemEntry } from '@/lib/opencode/client';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
interface DirectoryAutocompleteProps {
|
||||
inputValue: string;
|
||||
@@ -280,7 +280,7 @@ export const DirectoryAutocomplete = React.forwardRef<DirectoryAutocompleteHandl
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-3">
|
||||
<RiRefreshLine className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="refresh" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-y-auto py-1">
|
||||
@@ -297,7 +297,7 @@ export const DirectoryAutocomplete = React.forwardRef<DirectoryAutocompleteHandl
|
||||
onClick={() => { handleSelectSuggestion(entry); onClose(); }}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
>
|
||||
<RiFolderLine className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<Icon name="folder" className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="truncate">{entry.name}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,18 +16,9 @@ import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toast } from '@/components/ui';
|
||||
import { IdentityDropdown } from '@/components/views/git/GitHeader';
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowLeftSLine,
|
||||
RiArrowUpSLine,
|
||||
RiCheckboxBlankLine,
|
||||
RiCheckboxLine,
|
||||
RiCornerDownLeftLine,
|
||||
RiFolder6Line,
|
||||
RiFolderAddLine,
|
||||
} from '@remixicon/react';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import {
|
||||
setDirectoryShowHidden,
|
||||
@@ -523,7 +514,7 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
onClick={() => setDirectoryShowHidden(!showHidden)}
|
||||
className="flex flex-shrink-0 items-center gap-2 rounded-lg px-2 py-1 typography-meta text-muted-foreground transition-colors hover:bg-interactive-hover/40"
|
||||
>
|
||||
{showHidden ? <RiCheckboxLine className="h-4 w-4 text-primary" /> : <RiCheckboxBlankLine className="h-4 w-4" />}
|
||||
{showHidden ? <Icon name="checkbox" className="h-4 w-4 text-primary" /> : <Icon name="checkbox-blank" className="h-4 w-4" />}
|
||||
{t('directoryExplorerDialog.toggle.showHidden')}
|
||||
</button>
|
||||
);
|
||||
@@ -552,7 +543,7 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
</div>
|
||||
) : null}
|
||||
<div className="relative">
|
||||
<RiFolderAddLine className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground/80" />
|
||||
<Icon name="folder-add" className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground/80" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
@@ -626,9 +617,9 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
)}
|
||||
>
|
||||
{row.type === 'up' ? (
|
||||
<RiArrowLeftSLine className="h-4 w-4 flex-shrink-0 text-muted-foreground/80" />
|
||||
<Icon name="arrow-left-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground/80" />
|
||||
) : (
|
||||
<RiFolder6Line className="h-4 w-4 flex-shrink-0 text-muted-foreground/80" />
|
||||
<Icon name="folder-6" className="h-4 w-4 flex-shrink-0 text-muted-foreground/80" />
|
||||
)}
|
||||
<span className="flex min-w-0 flex-1 items-center gap-1.5">
|
||||
<span className="truncate typography-ui-label text-foreground">{row.name}</span>
|
||||
@@ -657,17 +648,17 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
const footerHints = (
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 typography-micro text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<RiArrowUpSLine className="h-3.5 w-3.5" />
|
||||
<RiArrowDownSLine className="-ml-1 h-3.5 w-3.5" />
|
||||
<Icon name="arrow-up-s" className="h-3.5 w-3.5" />
|
||||
<Icon name="arrow-down-s" className="-ml-1 h-3.5 w-3.5" />
|
||||
{t('directoryExplorerDialog.footer.navigate')}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<RiCornerDownLeftLine className="h-3.5 w-3.5" />
|
||||
<Icon name="corner-down-left" className="h-3.5 w-3.5" />
|
||||
{t('directoryExplorerDialog.footer.select')}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span>{submitModifierLabel}</span>
|
||||
<RiCornerDownLeftLine className="h-3.5 w-3.5" />
|
||||
<Icon name="corner-down-left" className="h-3.5 w-3.5" />
|
||||
{t('directoryExplorerDialog.footer.add')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { RiAddLine, RiArrowDownSLine, RiArrowRightSLine, RiCheckLine, RiCloseLine, RiFolder6Line, RiPushpin2Line, RiPushpinLine } from '@remixicon/react';
|
||||
import { cn, formatPathForDisplay } from '@/lib/utils';
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
@@ -16,6 +15,7 @@ import type { DesktopSettings } from '@/lib/desktop';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
interface DirectoryItem {
|
||||
@@ -695,9 +695,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -728,7 +728,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'text-muted-foreground flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -760,7 +760,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.createNewDirectory')}
|
||||
>
|
||||
<RiAddLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="add" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -776,9 +776,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
|
||||
>
|
||||
{isPinned ? (
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
) : (
|
||||
<RiPushpinLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
@@ -809,7 +809,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
style={{ paddingLeft: `${(level + 1) * 14 + 6}px` }}
|
||||
>
|
||||
<div className="w-4" />
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={newDirName}
|
||||
@@ -838,7 +838,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.createDirectory')}
|
||||
>
|
||||
<RiCheckLine className="h-3 w-3 text-green-600" />
|
||||
<Icon name="check" className="h-3 w-3 text-green-600" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -849,7 +849,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.cancel')}
|
||||
>
|
||||
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="close" className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -881,9 +881,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-0.5 hover:bg-interactive-hover rounded"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-right-s" className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -898,7 +898,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
style={{ paddingLeft: `${(level + 1) * 12 + 8}px` }}
|
||||
>
|
||||
<div className="w-4" />
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={newDirName}
|
||||
@@ -927,7 +927,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.createDirectory')}
|
||||
>
|
||||
<RiCheckLine className="h-3 w-3 text-green-600" />
|
||||
<Icon name="check" className="h-3 w-3 text-green-600" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -938,7 +938,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-1 hover:bg-interactive-hover rounded"
|
||||
title={t('directoryTree.actions.cancel')}
|
||||
>
|
||||
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="close" className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -968,9 +968,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
@@ -996,7 +996,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'text-muted-foreground flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -1027,9 +1027,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
|
||||
>
|
||||
{isPinned ? (
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
) : (
|
||||
<RiPushpinLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
<Icon name="pushpin" className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
@@ -1105,9 +1105,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className={cn("hover:bg-interactive-hover rounded flex-shrink-0", isMobile ? "p-0.5" : "p-0.5")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1124,7 +1124,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
isSelected ? 'text-primary' : 'text-foreground'
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line
|
||||
<Icon name="folder-6"
|
||||
className={cn(
|
||||
'flex-shrink-0',
|
||||
isMobile ? 'h-4 w-4' : 'h-3.5 w-3.5',
|
||||
@@ -1151,7 +1151,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.unpinDirectory')}
|
||||
>
|
||||
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3.5 w-3.5")} />
|
||||
<Icon name="pushpin-2" className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3.5 w-3.5")} />
|
||||
</button>
|
||||
</div>
|
||||
{isExpanded && children && children.map((child) => renderPinnedTreeItem(child, 1))}
|
||||
@@ -1183,12 +1183,12 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
className="p-0.5 hover:bg-interactive-hover rounded flex-shrink-0"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="h-3 w-3" />
|
||||
<Icon name="arrow-right-s" className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
<RiFolder6Line className="h-3.5 w-3.5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<Icon name="folder-6" className="h-3.5 w-3.5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="typography-ui-label font-medium">{name}</div>
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
@@ -1207,7 +1207,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
title={t('directoryTree.actions.unpinDirectory')}
|
||||
>
|
||||
<RiPushpin2Line className="h-3 w-3 text-primary" />
|
||||
<Icon name="pushpin-2" className="h-3 w-3 text-primary" />
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
{isExpanded && children && (
|
||||
@@ -1238,9 +1238,9 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
)}
|
||||
>
|
||||
{isPinnedExpanded ? (
|
||||
<RiArrowDownSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-down-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
) : (
|
||||
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
<Icon name="arrow-right-s" className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
|
||||
)}
|
||||
<span>{t('directoryTree.section.pinned')}</span>
|
||||
<span className="ml-auto typography-micro text-muted-foreground/60 normal-case tracking-normal">
|
||||
@@ -1302,12 +1302,12 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
|
||||
aria-label={t('directoryTree.actions.selectWorkingDirectoryAria')}
|
||||
>
|
||||
<span className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<RiFolder6Line className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="folder-6" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="truncate" title={currentPath}>
|
||||
{formatPathForDisplay(currentPath, homeDirectory)}
|
||||
</span>
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[350px]">
|
||||
|
||||
@@ -9,16 +9,6 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
RiGithubLine,
|
||||
RiLoader4Line,
|
||||
RiSearchLine,
|
||||
RiErrorWarningLine,
|
||||
RiCheckLine,
|
||||
RiGitPullRequestLine,
|
||||
RiGitBranchLine,
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
@@ -27,6 +17,7 @@ import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
import { validateWorktreeCreate } from '@/lib/worktrees/worktreeManager';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import type {
|
||||
GitHubIssue,
|
||||
GitHubIssueSummary,
|
||||
@@ -299,7 +290,7 @@ export function GitHubIntegrationDialog({
|
||||
<>
|
||||
{!isGitHubConnected ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-8 gap-4">
|
||||
<RiGithubLine className="h-12 w-12 text-muted-foreground" />
|
||||
<Icon name="github" className="h-12 w-12 text-muted-foreground" />
|
||||
<div className="text-center">
|
||||
<p className="typography-ui-label text-foreground">{t('session.githubIntegration.connect.title')}</p>
|
||||
<p className="typography-small text-muted-foreground mt-1">
|
||||
@@ -312,7 +303,7 @@ export function GitHubIntegrationDialog({
|
||||
<>
|
||||
{/* Search */}
|
||||
<div className="relative mt-2">
|
||||
<RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
@@ -329,7 +320,7 @@ export function GitHubIntegrationDialog({
|
||||
{/* Loading */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<RiLoader4Line className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -337,7 +328,7 @@ export function GitHubIntegrationDialog({
|
||||
{error && (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="flex items-center gap-2 p-2 rounded-md bg-destructive/10 text-destructive">
|
||||
<RiErrorWarningLine className="h-4 w-4" />
|
||||
<Icon name="error-warning" className="h-4 w-4" />
|
||||
<span className="typography-small">{error}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,7 +382,7 @@ export function GitHubIntegrationDialog({
|
||||
)}
|
||||
{loadingMore && (
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -463,7 +454,7 @@ export function GitHubIntegrationDialog({
|
||||
)}
|
||||
{loadingMore && (
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -489,7 +480,7 @@ export function GitHubIntegrationDialog({
|
||||
{/* Selected Issue/PR display - hidden on mobile (shown in header instead) */}
|
||||
{!isMobile && (selectedIssue || selectedPr) && (
|
||||
<div className="flex items-center gap-2 px-2 h-8 rounded-md bg-muted/50 border border-border/50">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<span className="typography-small truncate max-w-[150px]">
|
||||
{selectedIssue
|
||||
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
|
||||
@@ -499,7 +490,7 @@ export function GitHubIntegrationDialog({
|
||||
onClick={handleClear}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -562,8 +553,8 @@ export function GitHubIntegrationDialog({
|
||||
<div className="w-full">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <Icon name="git-pull-request" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={activeTab}
|
||||
onSelect={(id) => {
|
||||
@@ -578,7 +569,7 @@ export function GitHubIntegrationDialog({
|
||||
{/* Selected Item Inline Display */}
|
||||
{(selectedIssue || selectedPr) && (
|
||||
<div className="flex items-center gap-2 px-2 py-1 rounded-md bg-muted/50 border border-border/50">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<span className="typography-small truncate flex-1">
|
||||
{selectedIssue
|
||||
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
|
||||
@@ -588,7 +579,7 @@ export function GitHubIntegrationDialog({
|
||||
onClick={handleClear}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -603,7 +594,7 @@ export function GitHubIntegrationDialog({
|
||||
<DialogHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<DialogTitle className="flex items-center gap-2 shrink-0">
|
||||
<RiGithubLine className="h-5 w-5" />
|
||||
<Icon name="github" className="h-5 w-5" />
|
||||
{t('session.githubIntegration.title')}
|
||||
</DialogTitle>
|
||||
|
||||
@@ -611,8 +602,8 @@ export function GitHubIntegrationDialog({
|
||||
<div className="w-[220px]">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <Icon name="git-pull-request" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={activeTab}
|
||||
onSelect={(id) => {
|
||||
|
||||
@@ -10,14 +10,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { toast } from '@/components/ui';
|
||||
import {
|
||||
RiCheckboxBlankLine,
|
||||
RiCheckboxLine,
|
||||
RiExternalLinkLine,
|
||||
RiGithubLine,
|
||||
RiLoader4Line,
|
||||
RiSearchLine,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
@@ -499,7 +492,7 @@ export function GitHubIssuePickerDialog({
|
||||
const content = (
|
||||
<>
|
||||
<div className="relative mt-2">
|
||||
<RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('session.githubIssuePicker.searchPlaceholder')}
|
||||
value={query}
|
||||
@@ -519,7 +512,7 @@ export function GitHubIssuePickerDialog({
|
||||
|
||||
{isLoading ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('session.githubIssuePicker.loading.issues')}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -553,7 +546,7 @@ export function GitHubIssuePickerDialog({
|
||||
</p>
|
||||
<div className="flex-shrink-0 h-5 flex items-center mr-2">
|
||||
{startingIssueNumber === directNumber ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -588,7 +581,7 @@ export function GitHubIssuePickerDialog({
|
||||
|
||||
<div className="flex-shrink-0 h-5 flex items-center mr-2">
|
||||
{startingIssueNumber === issue.number ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<a
|
||||
href={issue.url}
|
||||
@@ -601,7 +594,7 @@ export function GitHubIssuePickerDialog({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t('session.githubIssuePicker.actions.openInGitHubAria')}
|
||||
>
|
||||
<RiExternalLinkLine className="h-4 w-4" />
|
||||
<Icon name="external-link" className="h-4 w-4" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -621,7 +614,7 @@ export function GitHubIssuePickerDialog({
|
||||
>
|
||||
{isLoadingMore ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('session.githubIssuePicker.loading.more')}
|
||||
</span>
|
||||
) : (
|
||||
@@ -660,9 +653,9 @@ export function GitHubIssuePickerDialog({
|
||||
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
>
|
||||
{createInWorktree ? (
|
||||
<RiCheckboxLine className="h-4 w-4 text-primary" />
|
||||
<Icon name="checkbox" className="h-4 w-4 text-primary" />
|
||||
) : (
|
||||
<RiCheckboxBlankLine className="h-4 w-4" />
|
||||
<Icon name="checkbox-blank" className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<span className="typography-meta text-muted-foreground">{t('session.githubIssuePicker.actions.createInWorktree')}</span>
|
||||
@@ -673,7 +666,7 @@ export function GitHubIssuePickerDialog({
|
||||
{repoUrl ? (
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href={repoUrl} target="_blank" rel="noopener noreferrer">
|
||||
<RiExternalLinkLine className="size-4" />
|
||||
<Icon name="external-link" className="size-4" />
|
||||
{t('session.githubIssuePicker.actions.openRepo')}
|
||||
</a>
|
||||
</Button>
|
||||
@@ -714,7 +707,7 @@ export function GitHubIssuePickerDialog({
|
||||
<DialogContent className="max-w-2xl max-h-[70vh] flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-5 w-5" />
|
||||
<Icon name="github" className="h-5 w-5" />
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -11,12 +11,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { toast } from '@/components/ui';
|
||||
import {
|
||||
RiGithubLine,
|
||||
RiLoader4Line,
|
||||
RiSearchLine,
|
||||
RiExternalLinkLine,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
@@ -269,7 +264,7 @@ export function GitHubPrPickerDialog({
|
||||
<>
|
||||
<div className="mt-2 flex items-center gap-3">
|
||||
<div className="relative flex-1 min-w-0">
|
||||
<RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('session.githubPrPicker.searchPlaceholder')}
|
||||
value={query}
|
||||
@@ -306,7 +301,7 @@ export function GitHubPrPickerDialog({
|
||||
|
||||
{isLoading ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('session.githubPrPicker.loading.pullRequests')}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -340,7 +335,7 @@ export function GitHubPrPickerDialog({
|
||||
</p>
|
||||
<div className="flex-shrink-0 h-5 flex items-center mr-2">
|
||||
{loadingPrNumber === directNumber ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -374,7 +369,7 @@ export function GitHubPrPickerDialog({
|
||||
|
||||
<div className="flex-shrink-0 h-5 flex items-center mr-2">
|
||||
{loadingPrNumber === pr.number ? (
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
) : (
|
||||
<a
|
||||
href={pr.url}
|
||||
@@ -387,7 +382,7 @@ export function GitHubPrPickerDialog({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={t('session.githubPrPicker.actions.openInGitHubAria')}
|
||||
>
|
||||
<RiExternalLinkLine className="h-4 w-4" />
|
||||
<Icon name="external-link" className="h-4 w-4" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -407,7 +402,7 @@ export function GitHubPrPickerDialog({
|
||||
>
|
||||
{isLoadingMore ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
|
||||
{t('session.githubPrPicker.loading.more')}
|
||||
</span>
|
||||
) : (
|
||||
@@ -446,7 +441,7 @@ export function GitHubPrPickerDialog({
|
||||
<DialogContent className="max-w-2xl max-h-[70vh] flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-5 w-5" />
|
||||
<Icon name="github" className="h-5 w-5" />
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -23,18 +23,6 @@ import {
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from '@/components/ui/command';
|
||||
import {
|
||||
RiGitBranchLine,
|
||||
RiGitRepositoryLine,
|
||||
RiGithubLine,
|
||||
RiLoader4Line,
|
||||
RiRefreshLine,
|
||||
RiErrorWarningLine,
|
||||
RiCheckLine,
|
||||
RiExternalLinkLine,
|
||||
RiCloseLine,
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
@@ -58,6 +46,7 @@ import { useGitBranches, useGitStore, useGitLoadingBranches } from '@/stores/use
|
||||
import { GitHubIntegrationDialog } from './GitHubIntegrationDialog';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import type {
|
||||
GitHubIssue,
|
||||
GitHubIssueComment,
|
||||
@@ -1037,7 +1026,7 @@ export function NewWorktreeDialog({
|
||||
<div className={cn('flex items-center gap-1.5 text-destructive', isMobile ? 'w-full justify-center order-first' : 'mr-auto')}>
|
||||
{validation.touched && (validation.branchError || validation.worktreeError) && (
|
||||
<>
|
||||
<RiErrorWarningLine className="h-3.5 w-3.5" />
|
||||
<Icon name="error-warning" className="h-3.5 w-3.5" />
|
||||
<span className="typography-micro">
|
||||
{validation.branchError || validation.worktreeError}
|
||||
</span>
|
||||
@@ -1062,7 +1051,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canCreate || isCreating}
|
||||
className={cn('gap-1.5', isMobile && 'flex-1')}
|
||||
>
|
||||
{isCreating && <RiLoader4Line className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating && <Icon name="loader-4" className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1082,8 +1071,8 @@ export function NewWorktreeDialog({
|
||||
<div className="w-full mb-4">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <RiGitRepositoryLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <Icon name="git-repository" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={mode}
|
||||
onSelect={(id) => handleModeChange(id as Mode)}
|
||||
@@ -1110,7 +1099,7 @@ export function NewWorktreeDialog({
|
||||
<span className={existingBranchState.selectedBranch ? 'text-foreground' : 'text-muted-foreground'}>
|
||||
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
|
||||
</span>
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -1120,7 +1109,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canFetchBranches || isLoadingBranches}
|
||||
title={t('session.newWorktree.fetchBranches')}
|
||||
>
|
||||
{isLoadingBranches ? <RiLoader4Line className="size-4 animate-spin" /> : <RiRefreshLine className="size-4" />}
|
||||
{isLoadingBranches ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="refresh" className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1266,7 +1255,7 @@ export function NewWorktreeDialog({
|
||||
onClick={() => setGithubDialogOpen(true)}
|
||||
className="gap-1.5 h-7"
|
||||
>
|
||||
<RiGithubLine className="size-4 text-status-success" />
|
||||
<Icon name="github" className="size-4 text-status-success" />
|
||||
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -1293,7 +1282,7 @@ export function NewWorktreeDialog({
|
||||
/>
|
||||
{newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
|
||||
</span>
|
||||
@@ -1301,7 +1290,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
|
||||
</span>
|
||||
@@ -1335,7 +1324,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
title={t('session.newWorktree.resetToMatchBranchName')}
|
||||
>
|
||||
<RiRefreshLine className="h-3 w-3" />
|
||||
<Icon name="refresh" className="h-3 w-3" />
|
||||
<span>{t('session.newWorktree.actions.reset')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -1380,7 +1369,7 @@ export function NewWorktreeDialog({
|
||||
<span className={newBranchState.sourceBranch ? 'text-foreground' : 'text-muted-foreground'}>
|
||||
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
|
||||
</span>
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
{newBranchState.sourceBranch && (
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
@@ -1509,7 +1498,7 @@ export function NewWorktreeDialog({
|
||||
<div className="mt-2 px-2 py-1.5 rounded bg-muted/30">
|
||||
{/* Row 1: Type, number, title, actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="github" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
|
||||
{newBranchState.linkedIssue && (
|
||||
<span className="typography-micro text-muted-foreground shrink-0">
|
||||
@@ -1533,14 +1522,14 @@ export function NewWorktreeDialog({
|
||||
className="text-muted-foreground hover:text-foreground shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RiExternalLinkLine className="h-3 w-3" />
|
||||
<Icon name="external-link" className="h-3 w-3" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={handleClearLinkedItem}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1567,7 +1556,7 @@ export function NewWorktreeDialog({
|
||||
<DialogHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<DialogTitle className="flex items-center gap-2 shrink-0">
|
||||
<RiGitBranchLine className="h-5 w-5" />
|
||||
<Icon name="git-branch" className="h-5 w-5" />
|
||||
{t('session.newWorktree.title')}
|
||||
</DialogTitle>
|
||||
|
||||
@@ -1575,8 +1564,8 @@ export function NewWorktreeDialog({
|
||||
<div className="w-[280px] shrink-0">
|
||||
<SortableTabsStrip
|
||||
items={[
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <RiGitRepositoryLine className="h-3.5 w-3.5" /> },
|
||||
{ id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: <Icon name="git-branch" className="h-3.5 w-3.5" /> },
|
||||
{ id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: <Icon name="git-repository" className="h-3.5 w-3.5" /> },
|
||||
]}
|
||||
activeId={mode}
|
||||
onSelect={(id) => handleModeChange(id as Mode)}
|
||||
@@ -1602,7 +1591,7 @@ export function NewWorktreeDialog({
|
||||
<span className={cn('truncate', existingBranchState.selectedBranch ? 'text-foreground' : 'text-muted-foreground')}>
|
||||
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={6} portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
|
||||
@@ -1714,7 +1703,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canFetchBranches || isLoadingBranches}
|
||||
title={t('session.newWorktree.fetchBranches')}
|
||||
>
|
||||
{isLoadingBranches ? <RiLoader4Line className="size-4 animate-spin" /> : <RiRefreshLine className="size-4" />}
|
||||
{isLoadingBranches ? <Icon name="loader-4" className="size-4 animate-spin" /> : <Icon name="refresh" className="size-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1731,7 +1720,7 @@ export function NewWorktreeDialog({
|
||||
onClick={() => setGithubDialogOpen(true)}
|
||||
className="gap-1.5 h-7"
|
||||
>
|
||||
<RiGithubLine className="size-4 text-status-success" />
|
||||
<Icon name="github" className="size-4 text-status-success" />
|
||||
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -1758,7 +1747,7 @@ export function NewWorktreeDialog({
|
||||
/>
|
||||
{newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
|
||||
</span>
|
||||
@@ -1766,7 +1755,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
|
||||
<div className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<RiCheckLine className="h-3.5 w-3.5 text-status-success" />
|
||||
<Icon name="check" className="h-3.5 w-3.5 text-status-success" />
|
||||
<span className="typography-micro">
|
||||
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
|
||||
</span>
|
||||
@@ -1800,7 +1789,7 @@ export function NewWorktreeDialog({
|
||||
)}
|
||||
title={t('session.newWorktree.resetToMatchBranchName')}
|
||||
>
|
||||
<RiRefreshLine className="h-3 w-3" />
|
||||
<Icon name="refresh" className="h-3 w-3" />
|
||||
<span>{t('session.newWorktree.actions.reset')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -1842,7 +1831,7 @@ export function NewWorktreeDialog({
|
||||
<span className={cn('truncate', newBranchState.sourceBranch ? 'text-foreground' : 'text-muted-foreground')}>
|
||||
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={sourceBranchDropdownContentRef}>
|
||||
@@ -1944,7 +1933,7 @@ export function NewWorktreeDialog({
|
||||
<div className="mt-2 px-2 py-1.5 rounded bg-muted/30">
|
||||
{/* Row 1: Type, number, title, actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RiGithubLine className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
<Icon name="github" className="h-3.5 w-3.5 text-status-success shrink-0" />
|
||||
|
||||
{newBranchState.linkedIssue && (
|
||||
<span className="typography-micro text-muted-foreground shrink-0">
|
||||
@@ -1968,14 +1957,14 @@ export function NewWorktreeDialog({
|
||||
className="text-muted-foreground hover:text-foreground shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RiExternalLinkLine className="h-3 w-3" />
|
||||
<Icon name="external-link" className="h-3 w-3" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={handleClearLinkedItem}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
<RiCloseLine className="h-3.5 w-3.5" />
|
||||
<Icon name="close" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2002,7 +1991,7 @@ export function NewWorktreeDialog({
|
||||
<div className="flex items-center gap-1.5 text-destructive">
|
||||
{validation.touched && (validation.branchError || validation.worktreeError) && (
|
||||
<>
|
||||
<RiErrorWarningLine className="h-3.5 w-3.5" />
|
||||
<Icon name="error-warning" className="h-3.5 w-3.5" />
|
||||
<span className="typography-micro">
|
||||
{validation.branchError || validation.worktreeError}
|
||||
</span>
|
||||
@@ -2025,7 +2014,7 @@ export function NewWorktreeDialog({
|
||||
disabled={!canCreate || isCreating}
|
||||
className="gap-1.5"
|
||||
>
|
||||
{isCreating && <RiLoader4Line className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating && <Icon name="loader-4" className="h-3.5 w-3.5 animate-spin" />}
|
||||
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { RiAddLine, RiDeleteBinLine, RiSendPlaneLine } from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import {
|
||||
deleteProjectPlanFile,
|
||||
getProjectContextData,
|
||||
@@ -575,7 +575,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
||||
aria-label={t('rightSidebar.contextNotesTodo.todo.addAria')}
|
||||
title={t('rightSidebar.contextNotesTodo.todo.addAria')}
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
<Icon name="add" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -623,7 +623,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
||||
aria-label={t('rightSidebar.contextNotesTodo.todo.actions.delete', { text: todo.text })}
|
||||
title={t('rightSidebar.contextNotesTodo.todo.actions.delete', { text: todo.text })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-3.5 w-3.5" />
|
||||
<Icon name="delete-bin" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -634,7 +634,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
||||
aria-label={t('rightSidebar.contextNotesTodo.todo.actions.send', { text: todo.text })}
|
||||
title={t('rightSidebar.contextNotesTodo.todo.actions.send', { text: todo.text })}
|
||||
>
|
||||
<RiSendPlaneLine className="h-3.5 w-3.5" />
|
||||
<Icon name="send-plane" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
@@ -692,7 +692,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
||||
aria-label={t('rightSidebar.contextNotesTodo.plans.importFromFile')}
|
||||
title={t('rightSidebar.contextNotesTodo.plans.importFromFile')}
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
<Icon name="add" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -723,7 +723,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
||||
title={t('rightSidebar.contextNotesTodo.plans.deletePlan')}
|
||||
aria-label={t('rightSidebar.contextNotesTodo.plans.deletePlanWithTitle', { title: plan.title })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-3.5 w-3.5" />
|
||||
<Icon name="delete-bin" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -9,12 +9,12 @@ import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { toast } from '@/components/ui';
|
||||
import { RiAddLine, RiCloseLine, RiCalendarLine, RiArrowLeftSLine, RiArrowRightSLine, RiArrowDownSLine } from '@remixicon/react';
|
||||
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
|
||||
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
||||
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
|
||||
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
|
||||
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import type { ScheduledTask } from '@/lib/scheduledTasksApi';
|
||||
@@ -700,7 +700,6 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
};
|
||||
}, [isDatePickerOpen]);
|
||||
|
||||
|
||||
const variantOptions = React.useMemo(() => {
|
||||
const provider = providers.find((item) => item.id === draft.execution.providerID);
|
||||
const model = provider?.models?.find((item) => item.id === draft.execution.modelID) as { variants?: Record<string, unknown> } | undefined;
|
||||
@@ -1066,10 +1065,10 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
onClick={() => setIsDatePickerOpen((prev) => !prev)}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<RiCalendarLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="calendar" className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="typography-ui-label text-foreground">{formatDateLabel(draft.schedule.onceDate, t('sessions.scheduledTasks.editor.date.placeholder'), locale)}</span>
|
||||
</span>
|
||||
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
|
||||
{isDatePickerOpen ? (
|
||||
@@ -1082,7 +1081,7 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
aria-label={t('sessions.scheduledTasks.editor.date.previousMonth')}
|
||||
disabled={isAtCurrentMonth}
|
||||
>
|
||||
<RiArrowLeftSLine className="h-4 w-4" />
|
||||
<Icon name="arrow-left-s" className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="typography-ui-label text-foreground">
|
||||
{new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(calendarMonth)}
|
||||
@@ -1093,7 +1092,7 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
onClick={() => setCalendarMonth((prev) => shiftMonth(prev, 1))}
|
||||
aria-label={t('sessions.scheduledTasks.editor.date.nextMonth')}
|
||||
>
|
||||
<RiArrowRightSLine className="h-4 w-4" />
|
||||
<Icon name="arrow-right-s" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1255,7 +1254,7 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
onClick={() => removeTimeAt(index)}
|
||||
aria-label={t('sessions.scheduledTasks.editor.times.removeAria')}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<Icon name="close" className="h-4 w-4" />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1263,7 +1262,7 @@ export function ScheduledTaskEditorDialog(props: {
|
||||
</div>
|
||||
<div>
|
||||
<Button type="button" size="sm" variant="outline" onClick={addTime}>
|
||||
<RiAddLine className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.editor.times.add')}
|
||||
<Icon name="add" className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.editor.times.add')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,19 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { toast } from '@/components/ui';
|
||||
import {
|
||||
RiLoader4Line,
|
||||
RiPlayLine,
|
||||
RiEdit2Line,
|
||||
RiDeleteBinLine,
|
||||
RiAddLine,
|
||||
RiFolderLine,
|
||||
RiTimerLine,
|
||||
RiHistoryLine,
|
||||
RiCheckboxCircleLine,
|
||||
RiErrorWarningLine,
|
||||
RiPulseLine,
|
||||
} from '@remixicon/react';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
@@ -159,14 +147,14 @@ const STATUS_META: Record<
|
||||
ScheduledTaskStatus,
|
||||
{
|
||||
tone: StatusTone;
|
||||
Icon: React.ComponentType<{ className?: string }>;
|
||||
Icon: string;
|
||||
spin?: boolean;
|
||||
}
|
||||
> = {
|
||||
success: { tone: 'success', Icon: RiCheckboxCircleLine },
|
||||
error: { tone: 'error', Icon: RiErrorWarningLine },
|
||||
running: { tone: 'warning', Icon: RiLoader4Line, spin: true },
|
||||
idle: { tone: 'muted', Icon: RiPulseLine },
|
||||
success: { tone: 'success', Icon: 'checkbox-circle' },
|
||||
error: { tone: 'error', Icon: 'error-warning' },
|
||||
running: { tone: 'warning', Icon: 'loader-4', spin: true },
|
||||
idle: { tone: 'muted', Icon: 'pulse' },
|
||||
};
|
||||
|
||||
const toneStyle = (tone: StatusTone): React.CSSProperties => {
|
||||
@@ -213,7 +201,7 @@ export function ScheduledTasksDialog() {
|
||||
iconColor: currentTheme.colors.surface.foreground,
|
||||
},
|
||||
);
|
||||
const ProjectIcon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
|
||||
const projectIconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
|
||||
const iconColor = project.color ? PROJECT_COLOR_MAP[project.color] : undefined;
|
||||
|
||||
return (
|
||||
@@ -225,10 +213,10 @@ export function ScheduledTasksDialog() {
|
||||
>
|
||||
<img src={imageUrl} alt="" className="h-full w-full object-contain" draggable={false} />
|
||||
</span>
|
||||
) : ProjectIcon ? (
|
||||
<ProjectIcon className="h-3.5 w-3.5 shrink-0" style={iconColor ? { color: iconColor } : undefined} />
|
||||
) : projectIconName ? (
|
||||
<Icon name={projectIconName} className="h-3.5 w-3.5 shrink-0" style={iconColor ? { color: iconColor } : undefined} />
|
||||
) : (
|
||||
<RiFolderLine className="h-3.5 w-3.5 shrink-0 text-muted-foreground/80" style={iconColor ? { color: iconColor } : undefined} />
|
||||
<Icon name="folder" className="h-3.5 w-3.5 shrink-0 text-muted-foreground/80" style={iconColor ? { color: iconColor } : undefined}/>
|
||||
)}
|
||||
<span className="truncate">{displayLabel}</span>
|
||||
</span>
|
||||
@@ -423,7 +411,7 @@ export function ScheduledTasksDialog() {
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
{projectSelector}
|
||||
<Button onClick={openNewTaskEditor} disabled={!selectedProjectID}>
|
||||
<RiAddLine className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
<Icon name="add" className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -433,7 +421,7 @@ export function ScheduledTasksDialog() {
|
||||
<div className="min-h-[280px]">
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 typography-meta text-muted-foreground">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" /> {t('sessions.scheduledTasks.dialog.loading')}
|
||||
<Icon name="loader-4" className="h-4 w-4 animate-spin" /> {t('sessions.scheduledTasks.dialog.loading')}
|
||||
</div>
|
||||
) : tasks.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border p-4 typography-meta text-muted-foreground">
|
||||
@@ -474,7 +462,7 @@ export function ScheduledTasksDialog() {
|
||||
|
||||
<div className="mt-3 flex flex-wrap items-center gap-x-5 gap-y-1 typography-micro text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<RiTimerLine className="h-3.5 w-3.5" />
|
||||
<Icon name="timer" className="h-3.5 w-3.5" />
|
||||
<span className="font-medium text-foreground">{t('sessions.scheduledTasks.dialog.nextRun.label')}</span>
|
||||
{nextAt ? (
|
||||
<>
|
||||
@@ -487,14 +475,14 @@ export function ScheduledTasksDialog() {
|
||||
)}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<RiHistoryLine className="h-3.5 w-3.5" />
|
||||
<Icon name="history" className="h-3.5 w-3.5" />
|
||||
<span className="font-medium text-foreground">{t('sessions.scheduledTasks.dialog.lastRun.label')}</span>
|
||||
{status === 'running' ? (
|
||||
<span
|
||||
className="inline-flex items-center gap-1"
|
||||
style={{ color: 'var(--status-warning)' }}
|
||||
>
|
||||
<RiLoader4Line className="h-3.5 w-3.5 animate-spin" />
|
||||
<Icon name="loader-4" className="h-3.5 w-3.5 animate-spin" />
|
||||
{t('sessions.scheduledTasks.dialog.lastRun.runningNow')}
|
||||
</span>
|
||||
) : lastAt ? (
|
||||
@@ -504,7 +492,7 @@ export function ScheduledTasksDialog() {
|
||||
className="inline-flex items-center gap-1"
|
||||
style={{ color: `var(--status-${meta.tone})` }}
|
||||
>
|
||||
<meta.Icon className="h-3.5 w-3.5" />
|
||||
<Icon name={meta.Icon} className="h-3.5 w-3.5" />
|
||||
{statusLabel}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -522,7 +510,7 @@ export function ScheduledTasksDialog() {
|
||||
className="mt-3 flex items-start gap-2 rounded-md border p-2 typography-micro"
|
||||
style={toneStyle('error')}
|
||||
>
|
||||
<RiErrorWarningLine className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<Icon name="error-warning" className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span className="min-w-0 break-words">{task.state.lastError}</span>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -553,7 +541,7 @@ export function ScheduledTasksDialog() {
|
||||
onClick={() => void handleRunNow(task)}
|
||||
disabled={isBusy}
|
||||
>
|
||||
<RiPlayLine className="h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.runNow')}
|
||||
<Icon name="play" className="h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.runNow')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -565,7 +553,7 @@ export function ScheduledTasksDialog() {
|
||||
disabled={isBusy}
|
||||
aria-label={t('sessions.scheduledTasks.dialog.actions.editAria', { taskName: task.name })}
|
||||
>
|
||||
<RiEdit2Line className="h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.edit')}
|
||||
<Icon name="edit-2" className="h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.edit')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -574,7 +562,7 @@ export function ScheduledTasksDialog() {
|
||||
disabled={isBusy}
|
||||
aria-label={t('sessions.scheduledTasks.dialog.actions.deleteAria', { taskName: task.name })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -612,7 +600,7 @@ export function ScheduledTasksDialog() {
|
||||
onClick={openNewTaskEditor}
|
||||
disabled={!selectedProjectID}
|
||||
>
|
||||
<RiAddLine className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
<Icon name="add" className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { RiCheckboxBlankLine, RiCheckboxLine, RiDeleteBinLine, RiGitBranchLine } from '@remixicon/react';
|
||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { DirectoryExplorerDialog } from './DirectoryExplorerDialog';
|
||||
import { cn, formatPathForDisplay } from '@/lib/utils';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
@@ -606,7 +606,7 @@ export const SessionDialogs: React.FC = () => {
|
||||
{isWorktreeDelete ? (
|
||||
<div className="space-y-2 rounded-lg bg-muted/30 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<RiGitBranchLine className="h-4 w-4 text-muted-foreground" />
|
||||
<Icon name="git-branch" className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="typography-meta font-medium text-foreground">{t('sessions.sidebar.sessionDialogs.worktree.label')}</span>
|
||||
{targetWorktree?.label ? (
|
||||
<span className="typography-micro text-muted-foreground/70">{targetWorktree.label}</span>
|
||||
@@ -651,9 +651,9 @@ export const SessionDialogs: React.FC = () => {
|
||||
)}
|
||||
>
|
||||
{deleteDialogShouldRemoveRemote ? (
|
||||
<RiCheckboxLine className="size-4 text-primary" />
|
||||
<Icon name="checkbox" className="size-4 text-primary" />
|
||||
) : (
|
||||
<RiCheckboxBlankLine className="size-4" />
|
||||
<Icon name="checkbox-blank" className="size-4" />
|
||||
)}
|
||||
{t('sessions.sidebar.sessionDialogs.actions.deleteRemoteBranch')}
|
||||
</button>
|
||||
@@ -680,9 +680,9 @@ export const SessionDialogs: React.FC = () => {
|
||||
)}
|
||||
>
|
||||
{deleteDialogShouldDeleteLocalBranch ? (
|
||||
<RiCheckboxLine className="size-4 text-primary" />
|
||||
<Icon name="checkbox" className="size-4 text-primary" />
|
||||
) : (
|
||||
<RiCheckboxBlankLine className="size-4" />
|
||||
<Icon name="checkbox-blank" className="size-4" />
|
||||
)}
|
||||
{t('sessions.sidebar.sessionDialogs.actions.deleteLocalBranch')}
|
||||
</button>
|
||||
@@ -713,7 +713,7 @@ export const SessionDialogs: React.FC = () => {
|
||||
className="inline-flex items-center gap-1.5 typography-meta 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="size-4 text-primary" /> : <RiCheckboxBlankLine className="size-4" />}
|
||||
{!showDeletionDialog ? <Icon name="checkbox" className="size-4 text-primary" /> : <Icon name="checkbox-blank" className="size-4" />}
|
||||
{t('sessions.sidebar.dialogs.neverAsk')}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -779,7 +779,7 @@ export const SessionDialogs: React.FC = () => {
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className={cn(isWorktreeDelete && 'flex items-center gap-2')}>
|
||||
{isWorktreeDelete && <RiDeleteBinLine className="h-5 w-5" />}
|
||||
{isWorktreeDelete && <Icon name="delete-bin" className="h-5 w-5" />}
|
||||
{deleteDialogTitle}
|
||||
</DialogTitle>
|
||||
{deleteDialogDescription && <DialogDescription>{deleteDialogDescription}</DialogDescription>}
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RiFolderLine,
|
||||
RiFolderOpenLine,
|
||||
RiArrowRightSLine,
|
||||
RiArrowDownSLine,
|
||||
RiPencilAiLine,
|
||||
RiDeleteBinLine,
|
||||
RiCheckLine,
|
||||
RiCloseLine,
|
||||
RiAddLine,
|
||||
RiFolderAddLine,
|
||||
} from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { SessionFolder } from '@/stores/useSessionFoldersStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
|
||||
interface SessionFolderItemProps<TSessionNode> {
|
||||
folder: SessionFolder;
|
||||
@@ -143,7 +132,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
};
|
||||
}, [isRenaming]);
|
||||
|
||||
const FolderIcon = isCollapsed ? RiFolderLine : RiFolderOpenLine;
|
||||
const folderIconName = isCollapsed ? 'folder' : 'folder-open';
|
||||
const isSubFolder = depth > 0;
|
||||
|
||||
return (
|
||||
@@ -179,7 +168,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
? (alwaysShowActions ? 'pr-7' : 'group-hover/folder:pr-7 group-focus-within/folder:pr-7')
|
||||
: '',
|
||||
)}>
|
||||
<FolderIcon className={cn('h-3.5 w-3.5 flex-shrink-0', isDropTarget ? 'text-primary' : 'text-muted-foreground')} />
|
||||
<Icon name={folderIconName} className={cn('h-3.5 w-3.5 flex-shrink-0', isDropTarget ? 'text-primary' : 'text-muted-foreground')} />
|
||||
|
||||
{renaming ? (
|
||||
<form
|
||||
@@ -220,7 +209,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<RiCheckLine className="size-4" />
|
||||
<Icon name="check" className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -232,7 +221,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<Icon name="close" className="size-4" />
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
@@ -244,9 +233,9 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
• {sessions.length}
|
||||
</span>
|
||||
{isCollapsed ? (
|
||||
<RiArrowRightSLine className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-right-s" className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
) : (
|
||||
<RiArrowDownSLine className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -274,7 +263,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
aria-label={t('sessions.sidebar.folderItem.newSessionAria', { folderName: folder.name })}
|
||||
title={t('sessions.sidebar.project.actions.newSession')}
|
||||
>
|
||||
<RiAddLine className="h-3.5 w-3.5" />
|
||||
<Icon name="add" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
{/* Only allow sub-folders at depth 0 (one level deep max) */}
|
||||
@@ -289,7 +278,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
aria-label={t('sessions.sidebar.folderItem.newSubfolderAria', { folderName: folder.name })}
|
||||
title={t('sessions.sidebar.folderItem.newSubfolder')}
|
||||
>
|
||||
<RiFolderAddLine className="h-3.5 w-3.5" />
|
||||
<Icon name="folder-add" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
{!archivedBucket ? (
|
||||
@@ -302,7 +291,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
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.folderItem.renameAria', { folderName: folder.name })}
|
||||
>
|
||||
<RiPencilAiLine className="h-3.5 w-3.5" />
|
||||
<Icon name="pencil-ai" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
@@ -316,7 +305,7 @@ const SessionFolderItemBase = <TSessionNode,>({
|
||||
? t('sessions.sidebar.folderItem.deleteArchivedInFolderAria', { folderName: folder.name })
|
||||
: t('sessions.sidebar.folderItem.deleteFolderAria', { folderName: folder.name })}
|
||||
>
|
||||
<RiDeleteBinLine className="h-3.5 w-3.5" />
|
||||
<Icon name="delete-bin" className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { RiLayoutLeftLine } from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -38,6 +37,7 @@ import { useStickyProjectHeaders } from './sidebar/hooks/useStickyProjectHeaders
|
||||
import { getGitHubPrStatusKey, usePrVisualSummaryByKeys, useGitHubPrStatusStore } from '@/stores/useGitHubPrStatusStore';
|
||||
import { ProjectEditDialog } from '@/components/layout/ProjectEditDialog';
|
||||
import { UpdateDialog } from '@/components/ui/UpdateDialog';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { SessionGroupSection } from './sidebar/SessionGroupSection';
|
||||
import { SidebarHeader } from './sidebar/SidebarHeader';
|
||||
import { SidebarActivitySections } from './sidebar/SidebarActivitySections';
|
||||
@@ -1648,7 +1648,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
className={desktopSidebarToggleButtonClass}
|
||||
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>
|
||||
|
||||
@@ -17,8 +17,8 @@ import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useAgentsStore } from '@/stores/useAgentsStore';
|
||||
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { RiArrowDownSLine } from '@remixicon/react';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type TodoSendTarget = 'session' | 'worktree';
|
||||
@@ -70,7 +70,7 @@ const ThinkingPill = ({ value, options, disabled, onChange }: ThinkingPillProps)
|
||||
)}
|
||||
>
|
||||
<span className="typography-micro whitespace-nowrap font-medium capitalize">{label}</span>
|
||||
<RiArrowDownSLine className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -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