Files
openchamber/packages/ui/src/components/session/sidebar/BulkActionBar.tsx
T
Bohdan Triapitsyn 08258908c2 feat: bulk select sessions in sidebar
Select toggle in header enters selection mode. Click toggles, shift-click
ranges, parent auto-includes subtasks, Cmd/Ctrl+A selects visible, Esc
exits, Cmd/Ctrl+Backspace deletes. Bulk bar: move to folder (existing or
new inline), archive/delete via existing confirm + "Never ask" pref.
Cross-scope forbidden — new scope clears previous.

Also: web sidebar header folds toggle into action row (no separate chrome
row, same position on toggle); sidebar min width bumped 250→280 so icons
fit; minimal-mode hover reveal padding trimmed.
2026-04-20 23:32:32 +03:00

133 lines
4.6 KiB
TypeScript

import React from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import {
RiAddLine,
RiCloseLine,
RiDeleteBinLine,
RiFolderLine,
} from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { SessionFolder } from '@/stores/useSessionFoldersStore';
type Props = {
selectedCount: number;
scopeKey: string | null;
scopeFolders: SessionFolder[];
archivedBucket: boolean;
onMoveToFolder: (folderId: string) => void;
onCreateFolderAndMove: () => void;
onRemoveFromFolder: () => void;
canRemoveFromFolder: boolean;
onDelete: () => void;
onDone: () => void;
};
export const BulkActionBar: React.FC<Props> = ({
selectedCount,
scopeKey,
scopeFolders,
archivedBucket,
onMoveToFolder,
onCreateFolderAndMove,
onRemoveFromFolder,
canRemoveFromFolder,
onDelete,
onDone,
}) => {
const canMoveToFolder = Boolean(scopeKey) && !archivedBucket;
const destructiveLabel = archivedBucket ? 'Delete' : 'Archive';
const iconButtonClass = 'inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-interactive-hover/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50';
const destructiveIconButtonClass = 'inline-flex h-7 w-7 items-center justify-center rounded-md text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50';
return (
<div className="flex shrink-0 items-center gap-1 border-t border-border px-2.5 py-1.5">
<span className="typography-ui-label text-muted-foreground whitespace-nowrap">
{selectedCount} selected
</span>
<div className="ml-auto flex items-center gap-0.5">
{canMoveToFolder ? (
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<button
type="button"
className={iconButtonClass}
aria-label="Move to folder"
>
<RiFolderLine className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}><p>Move to folder</p></TooltipContent>
</Tooltip>
<DropdownMenuContent align="end" className="min-w-[180px]">
{scopeFolders.length === 0 ? (
<DropdownMenuItem disabled className="text-muted-foreground">
No folders yet
</DropdownMenuItem>
) : (
scopeFolders.map((folder) => (
<DropdownMenuItem key={folder.id} onClick={() => onMoveToFolder(folder.id)}>
<span className="flex-1 truncate">{folder.name}</span>
</DropdownMenuItem>
))
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onCreateFolderAndMove}>
<RiAddLine className="mr-1 h-4 w-4" />
New folder...
</DropdownMenuItem>
{canRemoveFromFolder ? (
<DropdownMenuItem
onClick={onRemoveFromFolder}
className="text-destructive focus:text-destructive"
>
<RiCloseLine className="mr-1 h-4 w-4" />
Remove from folder
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={onDelete}
className={cn(destructiveIconButtonClass)}
aria-label={destructiveLabel}
>
<RiDeleteBinLine className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}><p>{destructiveLabel}</p></TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={onDone}
className={iconButtonClass}
aria-label="Exit selection"
>
<RiCloseLine className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}><p>Exit selection</p></TooltipContent>
</Tooltip>
</div>
</div>
);
};