Files
kandev/apps/web/components/workflow-selector-row.tsx
T
2026-02-16 13:48:58 +00:00

101 lines
3.6 KiB
TypeScript

'use client';
import { Fragment, memo, useMemo, useState } from 'react';
import { IconCheck, IconChevronDown, IconLogicBuffer } from '@tabler/icons-react';
import { Popover, PopoverContent, PopoverTrigger } from '@kandev/ui/popover';
import { Button } from '@kandev/ui/button';
import type { WorkflowSnapshotData } from '@/lib/state/slices/kanban/types';
type StepItem = { id: string; title: string; color: string; position: number };
function InlineSteps({ steps }: { steps: StepItem[] }) {
if (steps.length === 0) return null;
return (
<div className="flex items-center gap-1.5 text-xs text-muted-foreground whitespace-nowrap">
{steps.map((s, i) => (
<Fragment key={s.id}>
{i > 0 && <span className="text-muted-foreground/40">→</span>}
<span className="flex items-center gap-1">
<span
className="h-1.5 w-1.5 rounded-full shrink-0"
style={{ backgroundColor: s.color || 'hsl(var(--muted-foreground))' }}
/>
{s.title}
</span>
</Fragment>
))}
</div>
);
}
type WorkflowSelectorRowProps = {
workflows: Array<{ id: string; name: string; description?: string | null }>;
snapshots: Record<string, WorkflowSnapshotData>;
selectedWorkflowId: string | null;
onWorkflowChange: (workflowId: string) => void;
};
export const WorkflowSelectorRow = memo(function WorkflowSelectorRow({
workflows,
snapshots,
selectedWorkflowId,
onWorkflowChange,
}: WorkflowSelectorRowProps) {
const [open, setOpen] = useState(false);
const selectedWorkflow = useMemo(
() => workflows.find((w) => w.id === selectedWorkflowId),
[workflows, selectedWorkflowId]
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
type="button"
variant="ghost"
className="w-auto justify-between cursor-pointer"
>
<IconLogicBuffer className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">{selectedWorkflow?.name ?? 'Select workflow'}</span>
<IconChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto min-w-[300px] max-w-none p-1" align="start">
<div className="text-muted-foreground px-2 py-1.5 text-xs border-b">Workflow</div>
{workflows.map((wf) => {
const isSelected = wf.id === selectedWorkflowId;
const snapshot = snapshots[wf.id];
const steps = snapshot
? [...snapshot.steps].sort((a, b) => a.position - b.position)
: [];
return (
<button
key={wf.id}
type="button"
onClick={() => {
onWorkflowChange(wf.id);
setOpen(false);
}}
className="w-full flex flex-col gap-1 px-2 py-1.5 rounded-sm hover:bg-muted transition-colors cursor-pointer text-left relative pr-8"
>
<div className="flex items-center gap-2">
<IconLogicBuffer className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="text-sm">{wf.name}</span>
</div>
{steps.length > 0 && (
<div className="pl-[calc(0.875rem+0.5rem)]">
<InlineSteps steps={steps} />
</div>
)}
{isSelected && (
<IconCheck className="absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4" />
)}
</button>
);
})}
</PopoverContent>
</Popover>
);
});