{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "type": "registry:ui",
  "title": "Timeline",
  "description": "Ordered steps with status for runs and audit trails.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@wibo/utils"
  ],
  "files": [
    {
      "path": "src/components/timeline.tsx",
      "type": "registry:ui",
      "content": "import type { ComponentProps, ReactNode } from \"react\";\nimport { Circle, CircleCheck, CircleMinus, CircleX, LoaderCircle, TriangleAlert } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype TimelineStatus = \"pending\" | \"running\" | \"success\" | \"warning\" | \"error\" | \"skipped\";\n\ninterface TimelineStep {\n  id: string;\n  title: ReactNode;\n  description?: ReactNode;\n  /** Right-aligned meta, for example a duration or a timestamp. */\n  meta?: ReactNode;\n  status: TimelineStatus;\n  /** Extra content under the step (output preview, error detail). */\n  content?: ReactNode;\n}\n\nconst statusIcon = {\n  pending: Circle,\n  running: LoaderCircle,\n  success: CircleCheck,\n  warning: TriangleAlert,\n  error: CircleX,\n  skipped: CircleMinus,\n} as const;\n\nconst statusClass: Record<TimelineStatus, string> = {\n  pending: \"text-muted-foreground\",\n  running: \"text-accent animate-spin\",\n  success: \"text-success\",\n  warning: \"text-warning\",\n  error: \"text-danger\",\n  skipped: \"text-muted-foreground\",\n};\n\nconst defaultStatusLabels: Record<TimelineStatus, string> = {\n  pending: \"Pending\",\n  running: \"Running\",\n  success: \"Completed\",\n  warning: \"Completed with warnings\",\n  error: \"Failed\",\n  skipped: \"Skipped\",\n};\n\ninterface TimelineProps extends ComponentProps<\"ol\"> {\n  steps: TimelineStep[];\n  statusLabels?: Partial<Record<TimelineStatus, string>>;\n}\n\n/** Ordered steps with status: agent runs, workflows, audit trails. */\nfunction Timeline({ steps, statusLabels, className, ...props }: TimelineProps) {\n  const labels = { ...defaultStatusLabels, ...statusLabels };\n  return (\n    <ol data-slot=\"timeline\" className={cn(\"flex flex-col\", className)} {...props}>\n      {steps.map((step, index) => {\n        const Icon = statusIcon[step.status];\n        const last = index === steps.length - 1;\n        return (\n          <li\n            key={step.id}\n            data-status={step.status}\n            aria-current={step.status === \"running\" ? \"step\" : undefined}\n            className=\"grid grid-cols-[1rem_minmax(0,1fr)] gap-x-3\"\n          >\n            <div className=\"flex flex-col items-center\">\n              <Icon aria-hidden=\"true\" className={cn(\"mt-0.5 size-4 shrink-0\", statusClass[step.status])} />\n              {last ? null : <span aria-hidden=\"true\" className=\"my-1 w-px flex-1 bg-border\" />}\n            </div>\n            <div className={cn(\"min-w-0\", last ? \"pb-0\" : \"pb-4\")}>\n              <div className=\"flex items-baseline justify-between gap-3\">\n                <div className=\"min-w-0 text-sm font-medium text-foreground\">\n                  {step.title}\n                  <span className=\"sr-only\">, {labels[step.status]}</span>\n                </div>\n                {step.meta ? (\n                  <div className=\"shrink-0 font-mono text-xs text-muted-foreground tabular-nums\">{step.meta}</div>\n                ) : null}\n              </div>\n              {step.description ? (\n                <p className=\"mt-0.5 text-sm text-muted-foreground\">{step.description}</p>\n              ) : null}\n              {step.content ? <div className=\"mt-2\">{step.content}</div> : null}\n            </div>\n          </li>\n        );\n      })}\n    </ol>\n  );\n}\n\nexport { Timeline };\nexport type { TimelineProps, TimelineStatus, TimelineStep };\n"
    }
  ]
}
