Files
fusion/packages/dashboard/app/components/WorkspaceSelector.tsx
gsxdsm 9e03c47b91 feat(KB-175): move file browser to header with workspace support
- Add workspace file APIs and hooks for session-scoped file browsing
- Create WorkspaceSelector component for switching between workspaces
- Update FileBrowserModal with workspace mode and improved UX
- Add files button to header with workspace-aware file browsing
- Add session files indicator to task cards for quick file access
- Include comprehensive tests for all new components and hooks
2026-03-31 01:26:57 -07:00

98 lines
3.3 KiB
TypeScript

import { useMemo, useState } from "react";
import { ChevronDown, FolderGit2, FolderRoot } from "lucide-react";
import type { WorkspaceInfo } from "../hooks/useWorkspaces";
import "./WorkspaceSelector.css";
interface WorkspaceSelectorProps {
currentWorkspace: string;
projectName: string;
workspaces: WorkspaceInfo[];
onSelect: (workspace: string) => void;
}
function truncateTitle(title?: string, maxLength = 44): string | undefined {
if (!title) return undefined;
if (title.length <= maxLength) return title;
return `${title.slice(0, maxLength - 1)}`;
}
export function WorkspaceSelector({
currentWorkspace,
projectName,
workspaces,
onSelect,
}: WorkspaceSelectorProps) {
const [open, setOpen] = useState(false);
const currentLabel = useMemo(() => {
if (currentWorkspace === "project") {
return projectName || "Project Root";
}
const match = workspaces.find((workspace) => workspace.id === currentWorkspace);
return match?.label ?? currentWorkspace;
}, [currentWorkspace, projectName, workspaces]);
return (
<div className="workspace-selector">
<button
type="button"
className="workspace-selector-trigger"
onClick={() => setOpen((value) => !value)}
aria-haspopup="listbox"
aria-expanded={open}
>
{currentWorkspace === "project" ? <FolderRoot size={14} /> : <FolderGit2 size={14} />}
<span className="workspace-selector-trigger-label">{currentLabel}</span>
<ChevronDown size={14} className={`workspace-selector-trigger-icon${open ? " open" : ""}`} />
</button>
{open && (
<div className="workspace-selector-menu" role="listbox" aria-label="Select workspace">
<button
type="button"
className={`workspace-selector-option${currentWorkspace === "project" ? " active" : ""}`}
onClick={() => {
onSelect("project");
setOpen(false);
}}
>
<div className="workspace-selector-option-main">
<FolderRoot size={14} />
<span>Project Root</span>
</div>
<span className="workspace-selector-option-meta">{projectName}</span>
</button>
{workspaces.length > 0 && (
<div className="workspace-selector-group">
<div className="workspace-selector-group-label">Task Worktrees</div>
{workspaces.map((workspace) => (
<button
key={workspace.id}
type="button"
className={`workspace-selector-option${currentWorkspace === workspace.id ? " active" : ""}`}
onClick={() => {
onSelect(workspace.id);
setOpen(false);
}}
>
<div className="workspace-selector-option-main">
<FolderGit2 size={14} />
<span>{workspace.id}</span>
</div>
{workspace.title && (
<span className="workspace-selector-option-meta" title={workspace.title}>
{truncateTitle(workspace.title)}
</span>
)}
</button>
))}
</div>
)}
</div>
)}
</div>
);
}