{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "upgrade-dialog",
  "title": "UpgradeDialog",
  "description": "Pricing dialog with drawn-check feature list and a processing upgrade action.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/dialog.json"
  ],
  "files": [
    {
      "path": "src/components/upgrade-dialog/upgrade-dialog.tsx",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface UpgradePlan {\n  /** Plan name, e.g. \"Launch\". */\n  name: string;\n  /** Monthly price in dollars. */\n  price: number;\n  /** What the plan unlocks, in display order. */\n  features: string[];\n  /** One quiet line under the action, e.g. billing terms. */\n  note?: string;\n}\n\nexport interface UpgradeDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  /** Starts the upgrade; drive isProcessing while it flies. */\n  onUpgrade: () => void;\n  plan: UpgradePlan;\n  /** Locks the dialog and shimmers the action label. */\n  isProcessing?: boolean;\n  /** Structured failure spoken through the description slot. */\n  error?: string | null;\n  title?: string;\n  description?: ReactNode;\n}\n\n/* ─────────────────────────────────────────────────────────\n * UPGRADE STORYBOARD\n *\n *  open        the panel rises; the price answers first\n *              (number large, cadence mono), then the\n *              checklist introduces itself one line at a\n *              time, 50ms apart, each check drawing in\n *  action      the CTA arrives already charged — neon fill\n *              and the house glow; this is the one place\n *              the color was always going\n *  processing  everything locks; the label shimmers\n *              (\"Upgrading…\") until the flight lands and\n *              the parent closes the dialog\n *  error       the description slot speaks the failure in\n *              destructive — same row, zero shift\n * ───────────────────────────────────────────────────────── */\nconst FEATURE_STAGGER_MS = 50;\n\nconst RISE =\n  \"fill-mode-backwards fade-in-0 slide-in-from-bottom-2 animate-in duration-500 motion-reduce:animate-none\";\n\n/* The charged CTA speaks through color and the sweep alone — no glow. */\nconst CTA_GLOW = \"\";\n\nconst FeatureRow = ({ feature, index }: { feature: string; index: number }) => (\n  <li\n    className={cn(\"flex items-center gap-2.5 text-foreground/90 text-sm\", RISE)}\n    style={{ animationDelay: `${index * FEATURE_STAGGER_MS}ms` }}\n  >\n    <svg\n      aria-hidden=\"true\"\n      className=\"size-3.5 shrink-0 text-primary\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"2\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path\n        className=\"neon-check-draw\"\n        d=\"M4 12.5 10 18.5 20 6\"\n        pathLength={1}\n      />\n    </svg>\n    {feature}\n  </li>\n);\n\nexport const UpgradeDialog = ({\n  description,\n  error = null,\n  isProcessing = false,\n  onOpenChange,\n  onUpgrade,\n  open,\n  plan,\n  title,\n}: UpgradeDialogProps) => {\n  const heading = title ?? `Upgrade to ${plan.name}`;\n\n  const handleOpenChange = (next: boolean) => {\n    if (isProcessing) {\n      return;\n    }\n\n    onOpenChange(next);\n  };\n\n  return (\n    <Dialog onOpenChange={handleOpenChange} open={open}>\n      <DialogContent data-slot=\"upgrade-dialog\">\n        <DialogHeader>\n          <DialogTitle>{heading}</DialogTitle>\n          <DialogDescription\n            className={cn(error && \"text-destructive\")}\n            data-slot={error ? \"upgrade-dialog-error\" : undefined}\n            key={error ?? \"description\"}\n            role={error ? \"alert\" : undefined}\n          >\n            {error ??\n              description ??\n              \"Your app moves to a dedicated Neon project with more compute.\"}\n          </DialogDescription>\n        </DialogHeader>\n\n        <p\n          className=\"flex items-baseline gap-1.5\"\n          data-slot=\"upgrade-dialog-price\"\n        >\n          <span className=\"font-semibold text-3xl text-foreground tabular-nums tracking-tight\">\n            ${plan.price}\n          </span>\n          <span className=\"font-mono text-[10px] text-muted-foreground\">\n            / month\n          </span>\n        </p>\n\n        <ul className=\"flex flex-col gap-2\" data-slot=\"upgrade-dialog-features\">\n          {plan.features.map((feature, index) => (\n            <FeatureRow feature={feature} index={index} key={feature} />\n          ))}\n        </ul>\n\n        <div className=\"flex flex-col gap-2\">\n          <Button\n            className={cn(\n              \"w-full active:scale-[0.98] disabled:opacity-100 motion-reduce:active:scale-100\",\n              CTA_GLOW\n            )}\n            disabled={isProcessing}\n            onClick={onUpgrade}\n          >\n            <span key={String(isProcessing)}>\n              <span\n                className={cn(\"block\", {\n                  \"shimmer shimmer-duration-2400\": isProcessing,\n                })}\n              >\n                {isProcessing ? \"Upgrading…\" : `Upgrade to ${plan.name}`}\n              </span>\n            </span>\n          </Button>\n          <Button\n            className=\"w-full\"\n            disabled={isProcessing}\n            onClick={() => handleOpenChange(false)}\n            variant=\"ghost\"\n          >\n            Not now\n          </Button>\n        </div>\n\n        {plan.note ? (\n          <p className=\"text-pretty text-center text-[10px] text-muted-foreground/70\">\n            {plan.note}\n          </p>\n        ) : null}\n      </DialogContent>\n    </Dialog>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}