{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "provisioning-status",
  "title": "ProvisioningStatus",
  "description": "Provisioning, waking, and error panel states composing NeonLoader with a live status detail line.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/neon-loader.json",
    "https://ui.neon.com/r/animated-wash.json"
  ],
  "files": [
    {
      "path": "src/components/provisioning-status/provisioning-status.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { useState } from \"react\";\n\nimport { AnimatedWash } from \"@/components/animated-wash/animated-wash\";\nimport { NeonLoader } from \"@/components/neon-loader/neon-loader\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ProvisioningState = \"provisioning\" | \"waking\" | \"error\";\n\nexport type ProvisioningStatusProps = Omit<\n  ComponentProps<\"div\">,\n  \"title\" | \"children\"\n> & {\n  /** Lifecycle state the panel narrates. */\n  state?: ProvisioningState;\n  /** Override the state's default headline. */\n  title?: string;\n  /**\n   * The status detail line, e.g. \"Creating Neon project…\". Swapping it\n   * crossfades in place — feed it live progress from your API.\n   */\n  detail?: string;\n  /** Renders the retry action in the error state. */\n  onRetry?: () => void;\n  /** Label for the retry action. */\n  retryLabel?: string;\n  /**\n   * \"panel\" draws the house surface; \"bare\" renders naked for\n   * embedding inside an already-bordered pane.\n   */\n  variant?: \"panel\" | \"bare\";\n  /** Vertical breathing room. */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** Extra content under the detail line, e.g. a cancel link. */\n  footer?: ReactNode;\n};\n\n/* ─────────────────────────────────────────────────────────\n * PANEL STORYBOARD\n *\n * One quiet stage, three acts. The frame and rhythm never\n * change between states — only the voice does.\n *\n *  provisioning  the NeonLoader resolves out of grain, the\n *                headline holds foreground, and the detail\n *                line shimmers; swap `detail` as your API\n *                reports progress and the old step rolls\n *                out the top as the new one rises in\n *                (300ms, same row, zero shift)\n *  waking        identical staging, wake copy — the\n *                sandbox exists, it's just cold\n *  ambience      the AnimatedWash breathes under\n *                everything at low intensity — primary\n *                green while working, destructive and\n *                dimmer for the error act (the panel\n *                cools, it doesn't shout); always well\n *                below the loader's grain\n *  error         the loader yields to the mono error tag +\n *                foreground sentence, the detail cools to\n *                muted, and a retry action appears where\n *                the shimmer was\n * ───────────────────────────────────────────────────────── */\nconst COPY: Record<ProvisioningState, { title: string; detail: string }> = {\n  error: {\n    detail: \"The last attempt didn't complete.\",\n    title: \"Something went wrong\",\n  },\n  provisioning: {\n    detail: \"Creating Neon project…\",\n    title: \"Provisioning your app\",\n  },\n  waking: {\n    detail: \"Restoring compute…\",\n    title: \"Waking sandbox\",\n  },\n};\n\nconst SIZE_PADDING: Record<\n  NonNullable<ProvisioningStatusProps[\"size\"]>,\n  string\n> = {\n  lg: \"px-8 py-20\",\n  md: \"px-6 py-14\",\n  sm: \"px-4 py-8\",\n};\n\nexport const ProvisioningStatus = ({\n  className,\n  detail,\n  footer,\n  onRetry,\n  retryLabel = \"Try again\",\n  size = \"md\",\n  state = \"provisioning\",\n  title,\n  variant = \"panel\",\n  ...props\n}: ProvisioningStatusProps) => {\n  const copy = COPY[state];\n  const heading = title ?? copy.title;\n  const line = detail ?? copy.detail;\n  const failed = state === \"error\";\n  // Remember the outgoing line so it can roll out while the new one\n  // rolls in.\n  const [roll, setRoll] = useState<{\n    current: string;\n    previous: string | null;\n  }>({ current: line, previous: null });\n\n  if (roll.current !== line) {\n    setRoll({ current: line, previous: roll.current });\n  }\n\n  const { previous } = roll;\n\n  return (\n    <div\n      className={cn(\n        \"relative isolate flex flex-col items-center justify-center gap-4 overflow-hidden text-center\",\n        variant === \"panel\" && \"rounded-lg border border-border/60 bg-card\",\n        SIZE_PADDING[size],\n        className\n      )}\n      data-slot=\"provisioning-status\"\n      data-state={state}\n      {...props}\n    >\n      {/* Same wash grammar as AppCard's shader slot: default shader at\n          native intensity, status tint, bottom band. Only the error act\n          dims it — the panel cools, it doesn't shout. */}\n      <AnimatedWash\n        aria-hidden=\"true\"\n        className={cn(\n          \"-z-10 pointer-events-none absolute inset-x-0 bottom-0 h-32\",\n          failed ? \"text-destructive opacity-50\" : \"text-primary\"\n        )}\n        data-slot=\"provisioning-status-wash\"\n        key={state}\n      />\n      {failed ? (\n        <p\n          className=\"fade-in-0 flex animate-in items-baseline gap-2 text-sm duration-300 motion-reduce:animate-none\"\n          role=\"alert\"\n        >\n          <span className=\"font-mono text-destructive text-xs\">error</span>\n          <span className=\"font-medium text-foreground\">{heading}</span>\n        </p>\n      ) : (\n        <>\n          <NeonLoader decorative size=\"md\" />\n          <p\n            className=\"fade-in-0 animate-in font-medium text-foreground text-sm duration-300 motion-reduce:animate-none\"\n            key={heading}\n          >\n            {heading}\n          </p>\n        </>\n      )}\n      {/* The detail roll: old step exits up, new step rises in —\n          both live in one grid cell so the row never moves. */}\n      <p\n        aria-live=\"polite\"\n        className=\"grid text-muted-foreground\"\n        data-slot=\"provisioning-status-detail\"\n      >\n        {previous ? (\n          <span\n            aria-hidden=\"true\"\n            className=\"fade-out-0 slide-out-to-top-3 col-start-1 row-start-1 animate-out fill-mode-forwards text-xs duration-200 ease-in motion-reduce:animate-none\"\n            key={`out-${previous}`}\n          >\n            {previous}\n          </span>\n        ) : null}\n        <span\n          className=\"fade-in-0 slide-in-from-bottom-3 col-start-1 row-start-1 animate-in text-xs duration-300 motion-reduce:animate-none\"\n          key={line}\n        >\n          <span\n            className={cn(\"block\", !failed && \"shimmer shimmer-duration-2400\")}\n          >\n            {line}\n          </span>\n        </span>\n      </p>\n      {failed && onRetry ? (\n        <Button\n          className=\"mt-1 active:scale-[0.98]\"\n          onClick={onRetry}\n          size=\"sm\"\n          variant=\"outline\"\n        >\n          {retryLabel}\n        </Button>\n      ) : null}\n      {footer}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}