Files
Codewalkers/apps/web/src/components/DependencyChip.tsx
Lukas May 6a9d9e3452 feat: Redesign task and phase dependency display in plans tab
Replace plain text dependency indicators with visual, status-aware components:
- New DependencyChip/PhaseNumberBadge components with status-colored styling
- Sidebar shows compact numbered circles for phase deps instead of text
- Detail panel uses bordered cards with phase badges and status indicators
- Task dependency callout bars with resolved/total counters
- Collapse mechanism for tasks with 3+ dependencies (+N more button)
- Full dark mode support via semantic status tokens
2026-03-04 05:28:11 +01:00

98 lines
3.1 KiB
TypeScript

import { X } from "lucide-react";
import { StatusDot, mapEntityStatus, type StatusVariant } from "@/components/StatusDot";
import { cn } from "@/lib/utils";
// ---------------------------------------------------------------------------
// PhaseNumberBadge — small numbered circle colored by phase status
// ---------------------------------------------------------------------------
const badgeStyles: Record<StatusVariant, string> = {
active: "bg-status-active-bg text-status-active-fg border-status-active-border",
success: "bg-status-success-bg text-status-success-fg border-status-success-border",
warning: "bg-status-warning-bg text-status-warning-fg border-status-warning-border",
error: "bg-status-error-bg text-status-error-fg border-status-error-border",
neutral: "bg-muted text-muted-foreground border-border",
urgent: "bg-status-urgent-bg text-status-urgent-fg border-status-urgent-border",
};
interface PhaseNumberBadgeProps {
index: number;
status: string;
className?: string;
}
export function PhaseNumberBadge({ index, status, className }: PhaseNumberBadgeProps) {
const variant = mapEntityStatus(status);
return (
<span
className={cn(
"inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full border px-1 font-mono text-[9px] font-bold leading-none",
badgeStyles[variant],
className,
)}
title={`Phase ${index} (${status})`}
>
{index}
</span>
);
}
// ---------------------------------------------------------------------------
// DependencyChip — compact pill showing dependency name + status dot
// ---------------------------------------------------------------------------
const chipBorderColor: Record<StatusVariant, string> = {
active: "border-status-active-border/60",
success: "border-status-success-border/60",
warning: "border-status-warning-border/60",
error: "border-status-error-border/60",
neutral: "border-border",
urgent: "border-status-urgent-border/60",
};
interface DependencyChipProps {
name: string;
status: string;
size?: "xs" | "sm";
className?: string;
onRemove?: () => void;
}
export function DependencyChip({
name,
status,
size = "sm",
className,
onRemove,
}: DependencyChipProps) {
const variant = mapEntityStatus(status);
return (
<span
className={cn(
"inline-flex items-center gap-1 rounded-full border bg-background/80",
chipBorderColor[variant],
size === "xs" ? "px-1.5 py-px text-[10px]" : "px-2 py-0.5 text-xs",
className,
)}
>
<StatusDot status={status} size="sm" />
<span className={cn("truncate", size === "xs" ? "max-w-[80px]" : "max-w-[140px]")}>
{name}
</span>
{onRemove && (
<button
className="ml-0.5 rounded-full p-px text-muted-foreground transition-colors hover:text-destructive"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
title="Remove dependency"
>
<X className="h-2.5 w-2.5" />
</button>
)}
</span>
);
}