68c94dfa3a
* docs: define product and design direction * style: establish restrained workbench primitives * refactor: declutter board and task topbars * style: align dockview panel chrome * fix: align task topbar vcs control sizing * refactor: surface integrations navigation * fix: repair integrations and task creation workflow * refactor: include linear in integrations navigation * style: restore familiar workbench tones * fix: add dockview tab breathing room * fix: show configured integration shortcuts * fix: hover open integrations menu * fix: stabilize integrations hover menu * fix: align home and dockview topbars * fix: normalize home topbar controls * fix: compact home topbar height * fix: simplify home chrome and split buttons * fix: move integrations to left chrome * fix: restore home stats shortcut * fix: add dockview tab breathing room * fix: border home stats button * fix: streamline dockview topbar actions * fix: align task split button seam * fix: address review feedback * fix: restore topbar e2e affordances * fix: stabilize workflow e2e isolation * fix: add task topbar action overflow * fix: address PR review feedback
209 lines
6.6 KiB
TypeScript
209 lines
6.6 KiB
TypeScript
"use client";
|
|
|
|
import { Button } from "@kandev/ui/button";
|
|
import { Checkbox } from "@kandev/ui/checkbox";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@kandev/ui/dropdown-menu";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select";
|
|
import { IconAdjustmentsHorizontal } from "@tabler/icons-react";
|
|
import { useKanbanDisplaySettings } from "@/hooks/use-kanban-display-settings";
|
|
import type { Workspace, Repository } from "@/lib/types/http";
|
|
import type { WorkflowsState } from "@/lib/state/slices";
|
|
import type { ComponentProps } from "react";
|
|
|
|
type KanbanDisplayDropdownProps = {
|
|
triggerSize?: ComponentProps<typeof Button>["size"];
|
|
};
|
|
|
|
function getRepositoryPlaceholder(
|
|
repositoriesLoading: boolean,
|
|
repositoriesEmpty: boolean,
|
|
): string {
|
|
if (repositoriesLoading) return "Loading repositories...";
|
|
if (repositoriesEmpty) return "No repositories";
|
|
return "Select repository";
|
|
}
|
|
|
|
function WorkspaceSection({
|
|
activeWorkspaceId,
|
|
workspaces,
|
|
onWorkspaceChange,
|
|
}: {
|
|
activeWorkspaceId: string | null;
|
|
workspaces: Workspace[];
|
|
onWorkspaceChange: (id: string | null) => void;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<DropdownMenuLabel className="px-0 text-foreground">Workspace</DropdownMenuLabel>
|
|
<Select
|
|
value={activeWorkspaceId ?? ""}
|
|
onValueChange={(value) => onWorkspaceChange(value || null)}
|
|
>
|
|
<SelectTrigger className="w-full border-border" data-testid="workspace-select-trigger">
|
|
<SelectValue placeholder="Select workspace" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{workspaces.map((workspace: Workspace) => (
|
|
<SelectItem
|
|
key={workspace.id}
|
|
value={workspace.id}
|
|
data-testid={`workspace-select-item-${workspace.id}`}
|
|
>
|
|
{workspace.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WorkflowSection({
|
|
activeWorkflowId,
|
|
workflows,
|
|
onWorkflowChange,
|
|
}: {
|
|
activeWorkflowId: string | null;
|
|
workflows: WorkflowsState["items"];
|
|
onWorkflowChange: (id: string | null) => void;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<DropdownMenuLabel className="px-0 text-foreground">Workflow</DropdownMenuLabel>
|
|
<Select
|
|
value={activeWorkflowId ?? "all"}
|
|
onValueChange={(value) => onWorkflowChange(value === "all" ? null : value)}
|
|
>
|
|
<SelectTrigger data-testid="display-workflow-filter" className="w-full border-border">
|
|
<SelectValue placeholder="Select workflow" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Workflows</SelectItem>
|
|
{workflows.map((workflow: WorkflowsState["items"][number]) => (
|
|
<SelectItem key={workflow.id} value={workflow.id}>
|
|
{workflow.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function RepositorySection({
|
|
repositoryValue,
|
|
repositories,
|
|
repositoriesLoading,
|
|
onRepositoryChange,
|
|
}: {
|
|
repositoryValue: string;
|
|
repositories: Repository[];
|
|
repositoriesLoading: boolean;
|
|
onRepositoryChange: (value: string | "all") => void;
|
|
}) {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<DropdownMenuLabel className="px-0 text-foreground">Repository</DropdownMenuLabel>
|
|
<Select
|
|
value={repositoryValue}
|
|
onValueChange={(value) => onRepositoryChange(value as string | "all")}
|
|
disabled={repositories.length === 0}
|
|
>
|
|
<SelectTrigger data-testid="display-repository-filter" className="w-full border-border">
|
|
<SelectValue
|
|
placeholder={getRepositoryPlaceholder(repositoriesLoading, repositories.length === 0)}
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All repositories</SelectItem>
|
|
{repositories.map((repo: Repository) => (
|
|
<SelectItem key={repo.id} value={repo.id}>
|
|
{repo.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function KanbanDisplayDropdown({ triggerSize = "icon" }: KanbanDisplayDropdownProps) {
|
|
const {
|
|
workspaces,
|
|
workflows,
|
|
activeWorkspaceId,
|
|
activeWorkflowId,
|
|
repositories,
|
|
repositoriesLoading,
|
|
allRepositoriesSelected,
|
|
selectedRepositoryId,
|
|
enablePreviewOnClick,
|
|
onWorkspaceChange,
|
|
onWorkflowChange,
|
|
onRepositoryChange,
|
|
onTogglePreviewOnClick,
|
|
} = useKanbanDisplaySettings();
|
|
|
|
const repositoryValue = allRepositoriesSelected ? "all" : (selectedRepositoryId ?? "all");
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
size={triggerSize}
|
|
data-testid="display-button"
|
|
className="cursor-pointer"
|
|
>
|
|
<IconAdjustmentsHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-[280px] p-3">
|
|
<div className="space-y-3">
|
|
<WorkspaceSection
|
|
activeWorkspaceId={activeWorkspaceId}
|
|
workspaces={workspaces}
|
|
onWorkspaceChange={onWorkspaceChange}
|
|
/>
|
|
<DropdownMenuSeparator />
|
|
<WorkflowSection
|
|
activeWorkflowId={activeWorkflowId}
|
|
workflows={workflows}
|
|
onWorkflowChange={onWorkflowChange}
|
|
/>
|
|
<DropdownMenuSeparator />
|
|
<RepositorySection
|
|
repositoryValue={repositoryValue}
|
|
repositories={repositories}
|
|
repositoriesLoading={repositoriesLoading}
|
|
onRepositoryChange={onRepositoryChange}
|
|
/>
|
|
<DropdownMenuSeparator />
|
|
<div className="space-y-1.5">
|
|
<DropdownMenuLabel className="px-0 text-foreground">Preview Panel</DropdownMenuLabel>
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|
<Checkbox
|
|
checked={enablePreviewOnClick ?? false}
|
|
onCheckedChange={(checked) => {
|
|
onTogglePreviewOnClick?.(!!checked);
|
|
}}
|
|
/>
|
|
<span className="text-sm text-foreground">Open preview on click</span>
|
|
</label>
|
|
<p className="text-xs text-muted-foreground pl-6">
|
|
When enabled, clicking a task opens the preview panel. When disabled, clicking
|
|
navigates directly to the session.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|