{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "run-card",
  "title": "Run Card",
  "description": "A document run card: a file thumbnail with a status pill, a heading, and a result slot. Composes file-thumbnail; the result rendering is left to its children.",
  "dependencies": [
    "lucide-react@^0.514.0"
  ],
  "registryDependencies": [
    "@retab/utils",
    "@retab/file-thumbnail"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/run-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  Ban,\n  CheckCircle2,\n  Clock,\n  Loader2,\n  UserCheck,\n  XCircle,\n  type LucideIcon,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  FileThumbnail,\n  type FileThumbnailProps,\n} from \"@/components/ui/file-thumbnail\";\n\n// Run lifecycle, mirroring Retab's backend `RunLifecycleKind`\n// (pending/running/completed/error/awaiting_review/cancelled). `queued` is a\n// generic pre-execution alias for `pending`; `failed` renders the backend's\n// `error` state under a clearer label.\nexport type RunStatus =\n  | \"queued\"\n  | \"pending\"\n  | \"running\"\n  | \"awaiting_review\"\n  | \"completed\"\n  | \"failed\"\n  | \"cancelled\";\n\nconst RUN_STATUS: Record<\n  RunStatus,\n  { label: string; icon: LucideIcon; tone: string; spin?: boolean }\n> = {\n  queued: { label: \"Queued\", icon: Clock, tone: \"text-muted-foreground\" },\n  pending: { label: \"Pending\", icon: Clock, tone: \"text-muted-foreground\" },\n  running: {\n    label: \"Running\",\n    icon: Loader2,\n    tone: \"text-amber-600 dark:text-amber-500\",\n    spin: true,\n  },\n  awaiting_review: {\n    label: \"Awaiting review\",\n    icon: UserCheck,\n    tone: \"text-yellow-600 dark:text-yellow-500\",\n  },\n  completed: {\n    label: \"Completed\",\n    icon: CheckCircle2,\n    tone: \"text-emerald-600 dark:text-emerald-500\",\n  },\n  failed: {\n    label: \"Failed\",\n    icon: XCircle,\n    tone: \"text-red-600 dark:text-red-500\",\n  },\n  cancelled: {\n    label: \"Cancelled\",\n    icon: Ban,\n    tone: \"text-orange-600 dark:text-orange-500\",\n  },\n};\n\nexport interface RunCardProps\n  extends Pick<\n    FileThumbnailProps,\n    | \"previewContent\"\n    | \"previewImageUrl\"\n    | \"previewAspectRatio\"\n    | \"previewClassName\"\n    | \"state\"\n  > {\n  file: NonNullable<FileThumbnailProps[\"file\"]>;\n  /**\n   * Replaces the single `FileThumbnail` with custom media in the same framed,\n   * status-pilled slot — e.g. a bundle of per-subdocument thumbnails. The frame\n   * still honors `previewAspectRatio`.\n   */\n  media?: React.ReactNode;\n  /** Lifecycle of the run — shown as a pill over the thumbnail. */\n  status?: RunStatus;\n  /** Heading line; defaults to the file name. */\n  title?: React.ReactNode;\n  /** Right-aligned metadata in the heading (e.g. the run kind or a timestamp). */\n  meta?: React.ReactNode;\n  /** The run's result, rendered in the card body. */\n  children?: React.ReactNode;\n  /** Makes the whole card a button. */\n  onClick?: () => void;\n  className?: string;\n}\n\n/**\n * A run card: a document thumbnail with a status pill, a heading, and a slot for\n * the run's result. It composes {@link FileThumbnail} for the preview surface —\n * pass a rendered page through `previewContent`, an image URL through\n * `previewImageUrl`, or let it fall back to the file-type placeholder — and\n * leaves the result rendering to its `children`, so the same card frames a\n * classification, a split, an extraction, or anything else.\n */\nexport function RunCard({\n  file,\n  previewContent,\n  previewImageUrl,\n  previewAspectRatio,\n  previewClassName,\n  state,\n  media,\n  status,\n  title,\n  meta,\n  children,\n  onClick,\n  className,\n}: RunCardProps) {\n  const interactive = typeof onClick === \"function\";\n  // The body is optional — a card can be just media + overlays.\n  const hasFooter = title != null || meta != null || children != null;\n\n  return (\n    <div\n      data-slot=\"run-card\"\n      role={interactive ? \"button\" : undefined}\n      tabIndex={interactive ? 0 : undefined}\n      onClick={onClick}\n      onKeyDown={\n        interactive\n          ? (event) => {\n              if (event.key === \"Enter\" || event.key === \" \") {\n                event.preventDefault();\n                onClick?.();\n              }\n            }\n          : undefined\n      }\n      className={cn(\n        \"group/run-card bg-card text-card-foreground flex flex-col overflow-hidden rounded-xl border transition-colors\",\n        interactive &&\n          \"hover:border-foreground/20 focus-visible:ring-ring cursor-pointer outline-none focus-visible:ring-2\",\n        className,\n      )}\n    >\n      <div className=\"relative\">\n        {media ? (\n          <div\n            className={cn(\"overflow-hidden\", hasFooter && \"border-b\")}\n            style={{ aspectRatio: String(previewAspectRatio ?? 16 / 10) }}\n          >\n            {media}\n          </div>\n        ) : (\n          <FileThumbnail\n            file={file}\n            previewContent={previewContent}\n            previewImageUrl={previewImageUrl}\n            previewClassName={previewClassName}\n            previewAspectRatio={previewAspectRatio ?? 16 / 10}\n            state={state}\n            className={cn(\"rounded-none border-0\", hasFooter && \"border-b\")}\n          />\n        )}\n        {status ? (\n          <div className=\"absolute top-2 right-2\">\n            <RunStatusBadge status={status} />\n          </div>\n        ) : null}\n      </div>\n\n      {hasFooter ? (\n        <div className=\"flex flex-col gap-2 p-3\">\n          {title != null || meta != null ? (\n            <div className=\"flex min-w-0 items-center gap-2\">\n              <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">\n                {title ?? file.name}\n              </span>\n              {meta ? (\n                <span className=\"text-muted-foreground shrink-0 text-xs tabular-nums\">\n                  {meta}\n                </span>\n              ) : null}\n            </div>\n          ) : null}\n          {children}\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\n/** The status pill used in the corner of a {@link RunCard}. */\nexport function RunStatusBadge({\n  status,\n  className,\n}: {\n  status: RunStatus;\n  className?: string;\n}) {\n  const { label, icon: Icon, tone, spin } = RUN_STATUS[status];\n  return (\n    <span\n      className={cn(\n        \"bg-background/85 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium backdrop-blur\",\n        tone,\n        className,\n      )}\n    >\n      <Icon className={cn(\"size-3\", spin && \"animate-spin\")} aria-hidden />\n      {label}\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/run-card.tsx"
    }
  ],
  "type": "registry:ui"
}