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