import { useState } from "react";
import type { IssueBlockerAttention } from "@paperclipai/shared";
import { cn } from "../lib/utils";
import { StatusGlyph, type StatusGlyphSize } from "./StatusGlyph";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { resolveTaskStatusPresentation, taskStatusLabel, type TaskStatusPresentationInput } from "../lib/task-status-presentation";
const allStatuses = ["backlog", "todo", "in_progress", "in_review", "done", "cancelled", "blocked"];
interface StatusIconProps extends TaskStatusPresentationInput {
onChange?: (status: string) => void;
className?: string;
/** Optional layout wrapper around the glyph. Does not change glyph dimensions. */
glyphContainerClassName?: string;
showLabel?: boolean;
/** Glyph size (PAP-243a). Default `md` (16px); lists/detail/mentions use `lg` (20px). */
size?: StatusGlyphSize;
}
/** Shared accessible/tooltip copy, including waiting and covered-blocker states. */
export function getStatusIconLabel(status: string, externalConversationState?: "active" | "waiting" | null, blockerAttention?: IssueBlockerAttention | null, presentationStatus?: string | null): string {
return resolveTaskStatusPresentation({ status, displayStatus: presentationStatus, externalConversationState, blockerAttention }).ariaLabel;
}
/**
* Task/issue status indicator — renders the unified, color-blind-safe
* {@link StatusGlyph} (one distinct shape per status). With `onChange` it also
* acts as a status picker (popover). This one component drives every standalone
* status surface: list, kanban, detail header, properties row + picker flyout,
* sub-task / blocked-by pills, blocked inbox, quicklook, sibling nav, filters,
* search, columns, dashboard.
*
* A "covered" blocked task (waiting on active work) maps to the `in_queue`
* glyph — the blocked shape recoloured blue — while the full blocked reason
* still rides on the accessible label.
*/
export function StatusIcon({ status, displayStatus: presentationStatus, externalConversationState, blockerAttention, onChange, className, glyphContainerClassName, showLabel, size = "md" }: StatusIconProps) {
const [open, setOpen] = useState(false);
const presentation = { status, displayStatus: presentationStatus, externalConversationState, blockerAttention };
const { glyphStatus, label, ariaLabel } = resolveTaskStatusPresentation(presentation);
const glyphIcon = (
);
const glyph = glyphContainerClassName ? (
{glyphIcon}
) : glyphIcon;
if (!onChange) {
return showLabel ? (
{glyph}
{label}
) : (
glyph
);
}
const trigger = showLabel ? (
) : (
);
return (
{trigger}
{allStatuses.map((s) => (
))}
);
}