{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "update-plan-dialog",
  "title": "Update Plan Dialog",
  "description": "A update plan dialog component",
  "dependencies": ["lucide-react", "motion"],
  "registryDependencies": [
    "button",
    "badge",
    "radio-group",
    "toggle",
    "label",
    "dialog",
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/billingsdk/update-plan-dialog.tsx",
      "content": "\"use client\";\n\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { Label } from \"@/components/ui/label\";\nimport { type Plan } from \"@/lib/billingsdk-config\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { useState, useCallback } from \"react\";\nimport { useTheme } from \"@/contexts/theme-context\";\nimport { getThemeStyles } from \"@/lib/themes\";\n\nexport interface UpdatePlanDialogProps {\n  currentPlan: Plan;\n  plans: Plan[];\n  triggerText: string;\n  onPlanChange: (planId: string) => void;\n  className?: string;\n  title?: string;\n}\n\nconst easing = [0.4, 0, 0.2, 1] as const;\n\nexport function UpdatePlanDialog({\n  currentPlan,\n  plans,\n  onPlanChange,\n  className,\n  title,\n  triggerText,\n}: UpdatePlanDialogProps) {\n  const [isYearly, setIsYearly] = useState(false);\n  const [selectedPlan, setSelectedPlan] = useState<string | undefined>(\n    undefined,\n  );\n  const [isOpen, setIsOpen] = useState(false);\n  const { currentTheme, previewDarkMode } = useTheme();\n  const themeStyles = getThemeStyles(currentTheme, previewDarkMode);\n\n  const getCurrentPrice = useCallback(\n    (plan: Plan) => (isYearly ? `${plan.yearlyPrice}` : `${plan.monthlyPrice}`),\n    [isYearly],\n  );\n\n  const handlePlanChange = useCallback((planId: string) => {\n    setSelectedPlan((prev) => (prev === planId ? undefined : planId));\n  }, []);\n\n  const handleOpenChange = useCallback((open: boolean) => {\n    setIsOpen(open);\n    if (!open) {\n      setSelectedPlan(undefined);\n    }\n  }, []);\n\n  return (\n    <Dialog open={isOpen} onOpenChange={handleOpenChange}>\n      <DialogTrigger asChild>\n        <Button>{triggerText || \"Update Plan\"}</Button>\n      </DialogTrigger>\n      <DialogContent\n        className={cn(\n          \"text-foreground flex max-h-[95vh] flex-col gap-3 sm:max-h-[90vh] sm:gap-4\",\n          \"w-[calc(100vw-2rem)] max-w-2xl sm:w-full\",\n          \"p-4 sm:p-6\",\n          className,\n        )}\n        style={themeStyles}\n      >\n        <DialogHeader className=\"flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:pb-0\">\n          <DialogTitle className=\"text-lg font-semibold sm:text-xl\">\n            {title || \"Upgrade Plan\"}\n          </DialogTitle>\n          <div className=\"flex items-center gap-1.5 text-sm sm:gap-2\">\n            <Toggle\n              size=\"sm\"\n              pressed={!isYearly}\n              onPressedChange={(pressed) => setIsYearly(!pressed)}\n              className=\"h-9 px-3 text-xs sm:h-10 sm:px-4 sm:text-sm\"\n            >\n              Monthly\n            </Toggle>\n            <Toggle\n              size=\"sm\"\n              pressed={isYearly}\n              onPressedChange={(pressed) => setIsYearly(pressed)}\n              className=\"h-9 px-3 text-xs sm:h-10 sm:px-4 sm:text-sm\"\n            >\n              Yearly\n            </Toggle>\n          </div>\n        </DialogHeader>\n        <div\n          className=\"[&::-webkit-scrollbar-thumb]:bg-muted hover:[&::-webkit-scrollbar-thumb]:bg-muted-foreground/20 -mx-4 min-h-0 flex-1 overflow-x-hidden overflow-y-auto px-4 sm:-mx-6 sm:px-6 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-2 [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-track]:bg-transparent\"\n          style={{\n            scrollbarWidth: \"thin\",\n            scrollbarColor: \"hsl(var(--muted)) transparent\",\n          }}\n        >\n          {plans.length === 0 ? (\n            <div className=\"flex items-center justify-center py-12 text-center\">\n              <p className=\"text-muted-foreground text-sm\">\n                No plans available\n              </p>\n            </div>\n          ) : (\n            <RadioGroup value={selectedPlan} onValueChange={handlePlanChange}>\n              <div className=\"space-y-2.5 pr-0.5 pb-2 sm:space-y-3\">\n                {plans.map((plan, index) => (\n                  <motion.div\n                    key={plan.id}\n                    layout\n                    initial={{ opacity: 0, y: 20 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    transition={{\n                      layout: { duration: 0.3, ease: easing },\n                      opacity: {\n                        delay: index * 0.05,\n                        duration: 0.3,\n                        ease: easing,\n                      },\n                      y: { delay: index * 0.05, duration: 0.3, ease: easing },\n                    }}\n                    onClick={() => handlePlanChange(plan.id)}\n                    onKeyDown={(e) => {\n                      if (e.key === \"Enter\" || e.key === \" \") {\n                        e.preventDefault();\n                        handlePlanChange(plan.id);\n                      }\n                    }}\n                    role=\"button\"\n                    tabIndex={0}\n                    aria-pressed={selectedPlan === plan.id}\n                    className={cn(\n                      \"relative cursor-pointer overflow-hidden rounded-lg border transition-all duration-200 sm:rounded-xl\",\n                      \"focus-visible:ring-primary touch-manipulation focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none\",\n                      selectedPlan === plan.id\n                        ? \"border-primary from-muted/60 to-muted/30 bg-gradient-to-br shadow-sm\"\n                        : \"border-border hover:border-primary/50\",\n                    )}\n                  >\n                    <motion.div layout=\"position\" className=\"p-3 sm:p-4\">\n                      <div className=\"flex items-start justify-between gap-2 sm:gap-3\">\n                        <div className=\"flex min-w-0 flex-1 gap-2 sm:gap-3\">\n                          <RadioGroupItem\n                            value={plan.id}\n                            id={plan.id}\n                            className=\"pointer-events-none mt-0.5 flex-shrink-0 sm:mt-1\"\n                          />\n                          <div className=\"min-w-0 flex-1\">\n                            <div className=\"flex flex-wrap items-center gap-1.5 sm:gap-2\">\n                              <Label\n                                htmlFor={plan.id}\n                                className=\"cursor-pointer text-sm leading-tight font-semibold sm:text-base sm:font-medium\"\n                              >\n                                {plan.title}\n                              </Label>\n                              {plan.badge && (\n                                <Badge\n                                  variant=\"secondary\"\n                                  className=\"h-5 flex-shrink-0 px-1.5 py-0 text-[10px] sm:h-auto sm:px-2 sm:py-0.5 sm:text-xs\"\n                                >\n                                  {plan.badge}\n                                </Badge>\n                              )}\n                            </div>\n                            <p className=\"text-muted-foreground mt-1 text-[11px] leading-relaxed sm:text-xs\">\n                              {plan.description}\n                            </p>\n                            {plan.features.length > 0 && (\n                              <div className=\"pt-2 sm:pt-3\">\n                                <div className=\"flex flex-wrap gap-1.5 sm:gap-2\">\n                                  {plan.features.map(\n                                    (feature, featureIndex) => (\n                                      <div\n                                        key={featureIndex}\n                                        className=\"bg-muted/20 border-border/30 flex flex-shrink-0 items-center gap-1.5 rounded-md border px-2 py-1 sm:gap-2 sm:rounded-lg\"\n                                      >\n                                        <div className=\"bg-primary h-1 w-1 flex-shrink-0 rounded-full sm:h-1.5 sm:w-1.5\" />\n                                        <span className=\"text-muted-foreground text-[10px] leading-none whitespace-nowrap sm:text-xs\">\n                                          {feature.name}\n                                        </span>\n                                      </div>\n                                    ),\n                                  )}\n                                </div>\n                              </div>\n                            )}\n                          </div>\n                        </div>\n                        <div className=\"min-w-[60px] flex-shrink-0 text-right sm:min-w-[80px]\">\n                          <div className=\"text-base leading-tight font-bold sm:text-xl sm:font-semibold\">\n                            {parseFloat(getCurrentPrice(plan)) >= 0\n                              ? `${plan.currency}${getCurrentPrice(plan)}`\n                              : getCurrentPrice(plan)}\n                          </div>\n                          <div className=\"text-muted-foreground mt-0.5 text-[10px] sm:text-xs\">\n                            /{isYearly ? \"year\" : \"month\"}\n                          </div>\n                        </div>\n                      </div>\n                    </motion.div>\n\n                    <AnimatePresence initial={false}>\n                      {selectedPlan === plan.id && (\n                        <motion.div\n                          initial={{ height: 0, opacity: 0 }}\n                          animate={{\n                            height: \"auto\",\n                            opacity: 1,\n                            transition: {\n                              height: { duration: 0.3, ease: easing },\n                              opacity: {\n                                duration: 0.25,\n                                delay: 0.05,\n                                ease: easing,\n                              },\n                            },\n                          }}\n                          exit={{\n                            height: 0,\n                            opacity: 0,\n                            transition: {\n                              height: { duration: 0.25, ease: easing },\n                              opacity: { duration: 0.15, ease: easing },\n                            },\n                          }}\n                          className=\"overflow-hidden\"\n                        >\n                          <motion.div\n                            initial={{ y: -8 }}\n                            animate={{\n                              y: 0,\n                              transition: {\n                                duration: 0.25,\n                                delay: 0.05,\n                                ease: easing,\n                              },\n                            }}\n                            exit={{ y: -8 }}\n                            className=\"px-3 pb-3 sm:px-4 sm:pb-4\"\n                          >\n                            <Button\n                              className=\"h-10 w-full touch-manipulation text-sm font-medium sm:h-11 sm:text-base\"\n                              disabled={selectedPlan === currentPlan.id}\n                              onClick={(e) => {\n                                e.stopPropagation();\n                                onPlanChange(plan.id);\n                                handleOpenChange(false);\n                              }}\n                            >\n                              {selectedPlan === currentPlan.id\n                                ? \"Current Plan\"\n                                : \"Upgrade\"}\n                            </Button>\n                          </motion.div>\n                        </motion.div>\n                      )}\n                    </AnimatePresence>\n                  </motion.div>\n                ))}\n              </div>\n            </RadioGroup>\n          )}\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/billingsdk/update-plan-dialog.tsx"
    },
    {
      "path": "src/registry/billingsdk/demo/update-plan-dialog-demo.tsx",
      "content": "\"use client\";\n\nimport { UpdatePlanDialog } from \"@/components/billingsdk/update-plan-dialog\";\nimport { plans } from \"@/lib/billingsdk-config\";\n\nexport function UpdatePlanDialogDemo() {\n  return (\n    <div className=\"mx-auto flex min-h-[300px] flex-1 flex-col justify-center p-4 text-center\">\n      <UpdatePlanDialog\n        currentPlan={plans[1]}\n        plans={plans}\n        onPlanChange={(planId) => {\n          console.log(planId);\n        }}\n        triggerText=\"Update Plan\"\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/update-plan-dialog-demo.tsx"
    },
    {
      "path": "src/registry/lib/billingsdk-config.ts",
      "content": "export interface Plan {\n  id: string;\n  title: string;\n  description: string;\n  highlight?: boolean;\n  type?: \"monthly\" | \"yearly\";\n  currency?: string;\n  monthlyPrice: string;\n  yearlyPrice: string;\n  buttonText: string;\n  badge?: string;\n  features: {\n    name: string;\n    icon: string;\n    iconColor?: string;\n  }[];\n}\n\nexport interface CurrentPlan {\n  plan: Plan;\n  type: \"monthly\" | \"yearly\" | \"custom\";\n  price?: string;\n  nextBillingDate: string;\n  paymentMethod: string;\n  status: \"active\" | \"inactive\" | \"past_due\" | \"cancelled\";\n}\n\nexport const plans: Plan[] = [\n  {\n    id: \"starter\",\n    title: \"Starter\",\n    description: \"For developers testing out Liveblocks locally.\",\n    currency: \"$\",\n    monthlyPrice: \"0\",\n    yearlyPrice: \"0\",\n    buttonText: \"Start today for free\",\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n  {\n    id: \"pro\",\n    title: \"Pro\",\n    description: \"For companies adding collaboration in production.\",\n    currency: \"$\",\n    monthlyPrice: \"20\",\n    yearlyPrice: \"199\",\n    buttonText: \"Sign up\",\n    badge: \"Most popular\",\n    highlight: true,\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n  {\n    id: \"enterprise\",\n    title: \"Enterprise\",\n    description:\n      \"For organizations that need more support and compliance features.\",\n    currency: \"$\",\n    monthlyPrice: \"Custom\",\n    yearlyPrice: \"Custom\",\n    buttonText: \"Contact sales\",\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n];\n",
      "type": "registry:lib",
      "target": "lib/billingsdk-config.ts"
    }
  ],
  "type": "registry:block"
}
