import React from 'react'; import { cn } from '@/lib/utils'; import { Icon } from '@/components/icon/Icon'; import { useUIStore } from '@/stores/useUIStore'; import type { IconName } from '@/components/icon/icons'; /** * Row/section vocabulary for the work-status panel. * * Every readout is a labelled row — icon, name, trailing value — so a glance * answers "what is this number" without hovering. Sections carry a heading and * are separated by a hairline; the panel itself stays chrome-less, since it is * an object inside the chat rather than a docked pane. */ /** * Sections are direct siblings inside the panel (fragments add no DOM nodes), * so the separator is a first-child CSS rule. Passing "am I first?" down as a * prop would mean every group tracking what the groups above it decided to * render. */ const SECTION_CLASS = cn( 'flex flex-col', '[&:not(:first-child)]:mt-3 [&:not(:first-child)]:border-t', '[&:not(:first-child)]:border-[var(--interactive-border)] [&:not(:first-child)]:pt-3', ); const HEADING_CLASS = 'text-xs font-normal text-muted-foreground'; export const WorkStatusSection: React.FC<{ title: string; /** Aggregate for the whole section; belongs on the heading, not on a row. */ summary?: React.ReactNode; children: React.ReactNode; }> = ({ title, summary, children }) => (

{title}

{summary !== undefined && summary !== null ? ( {summary} ) : null}
{children}
); /** * Section whose body folds away. The chevron swaps on expand exactly as the * transcript's tool blocks do, so the two collapsibles read as the same * control rather than two conventions in one window. * * Expanded state lives in the persisted UI store, not in component state: the * panel unmounts whenever the context panel opens, and local state would * silently discard the user's arrangement every time. */ export const WorkStatusCollapsibleSection: React.FC<{ /** Stable key for persisting expanded state. */ id: string; title: string; icon?: IconName; /** For glyphs that live outside the sprite, such as the MCP mark. */ iconNode?: React.ReactNode; iconColor?: string; /** Shown on the header while collapsed and expanded alike. */ summary?: React.ReactNode; /** An independent header action, such as refreshing this section's data. */ action?: React.ReactNode; defaultExpanded?: boolean; children: React.ReactNode; }> = ({ id, title, icon, iconNode, iconColor, summary, action, defaultExpanded = false, children }) => { const stored = useUIStore( React.useCallback((state) => state.workStatusExpandedSections[id], [id]), ); const setExpandedInStore = useUIStore((state) => state.setWorkStatusSectionExpanded); const expanded = stored ?? defaultExpanded; return (
{action}
{expanded ? children : null}
); }; type RowProps = { icon?: IconName; iconColor?: string; leading?: React.ReactNode; label: React.ReactNode; value?: React.ReactNode; muted?: boolean; /** Turns the row into a button; the caller decides what it opens. */ onClick?: () => void; ariaLabel?: string; className?: string; }; /** * A single readout. `value` sits hard right; `label` truncates before it, so a * long branch name never pushes its own ahead/behind counts out of view. */ export const WorkStatusRow: React.FC = ({ icon, iconColor, leading, label, value, muted, onClick, ariaLabel, className, }) => { const body = ( <> {leading ?? (icon ? ( ) : null)} {label} {value !== undefined && value !== null ? ( {value} ) : null} ); const shared = cn('flex h-7 w-full items-center gap-2 rounded-md px-1 text-left', className); if (!onClick) return
{body}
; return ( ); }; type WorkStatusTone = 'default' | 'muted' | 'success' | 'error' | 'warning' | 'info'; const TONE_COLOR: Record, string> = { success: 'var(--status-success)', error: 'var(--status-error)', warning: 'var(--status-warning)', info: 'var(--status-info)', }; export const WorkStatusValue: React.FC<{ children: React.ReactNode; tone?: WorkStatusTone; }> = ({ children, tone = 'default' }) => ( {children} ); /** * Trailing control shaped like the PR badge: a status that is also the thing * you press. Used where the state itself is the affordance — an MCP server * asking for sign-in, a goal waiting to be resumed. */ export const WorkStatusRowAction: React.FC<{ children: React.ReactNode; onClick: () => void; tone?: 'default' | 'warning' | 'error' | 'info'; disabled?: boolean; ariaLabel?: string; }> = ({ children, onClick, tone = 'default', disabled, ariaLabel }) => { const color = tone === 'default' ? undefined : TONE_COLOR[tone]; return ( ); }; export const WorkStatusPill: React.FC<{ children: React.ReactNode; color?: string; background?: string; }> = ({ children, color, background }) => ( {children} ); /** Full-width callout for states that block the branch (merge, rebase, …). */ export const WorkStatusCallout: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
); /** Context-window fill, drawn under its row rather than inside it. */ export const WorkStatusMeter: React.FC<{ percent: number; color: string }> = ({ percent, color }) => (
);