Files
kandev/apps/web/components/kanban-display-dropdown.tsx
T
José Almeida 68c94dfa3a feat: refine workbench chrome (#761)
* 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
2026-05-01 21:02:41 +01:00

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>
);
}