{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "morphing-card",
  "type": "registry:ui",
  "title": "Card",
  "description": "A Card component for displaying content in a card format.",
  "files": [
    {
      "path": "src/registry/ui/morphing-card.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useId, useState } from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type MorphingCardProps = Omit<React.ComponentProps<\"button\">, \"title\" | \"children\"> & {\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  image?: string;\n  imageAlt?: string;\n  /** Content shown only in the expanded dialog. */\n  children?: React.ReactNode;\n  /** Rendered at the bottom of the expanded dialog, e.g. a call-to-action button. */\n  action?: React.ReactNode;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Accessible label for the close button. */\n  closeLabel?: string;\n  contentClassName?: string;\n};\n\nconst spring = { type: \"spring\", bounce: 0.15, duration: 0.5 } as const;\n\nexport function MorphingCard({\n  title,\n  description,\n  image,\n  imageAlt = \"\",\n  children,\n  action,\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  closeLabel = \"Close\",\n  className,\n  contentClassName,\n  ...props\n}: MorphingCardProps) {\n  const id = useId();\n  const reduceMotion = useReducedMotion();\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const isOpen = open ?? uncontrolledOpen;\n\n  const setOpen = (next: boolean) => {\n    if (open === undefined) setUncontrolledOpen(next);\n    onOpenChange?.(next);\n  };\n\n  // With reduced motion nothing morphs; the dialog simply fades.\n  const layoutId = (part: string) => (reduceMotion ? undefined : `${id}-${part}`);\n  const transition = reduceMotion ? { duration: 0.15 } : spring;\n\n  const cardImage = image && (\n    // eslint-disable-next-line @next/next/no-img-element -- plain <img> keeps the component framework-agnostic\n    <img src={image} alt={imageAlt} className=\"block aspect-video w-full object-cover\" />\n  );\n  const cardText = (titleNode: React.ReactNode) => (\n    <span className=\"block p-4\">\n      {titleNode}\n      {description && (\n        <span className=\"mt-1 line-clamp-2 text-sm text-muted-foreground\">{description}</span>\n      )}\n    </span>\n  );\n\n  return (\n    <DialogPrimitive.Root open={isOpen} onOpenChange={setOpen}>\n      <DialogPrimitive.Trigger asChild>\n        <button\n          type=\"button\"\n          data-slot=\"morphing-card\"\n          className={cn(\n            \"group relative block w-full rounded-xl text-left text-card-foreground outline-none\",\n            \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n            className\n          )}\n          {...props}\n        >\n          {isOpen ? (\n            // Placeholder that keeps the grid slot sized while the card lives in the dialog.\n            <span aria-hidden className=\"invisible block overflow-hidden rounded-xl border\">\n              {cardImage}\n              {cardText(<span className=\"block text-lg font-semibold\">{title}</span>)}\n            </span>\n          ) : (\n            <motion.span\n              layoutId={layoutId(\"card\")}\n              transition={transition}\n              className=\"block overflow-hidden rounded-xl border bg-card transition-colors group-hover:border-foreground/20\"\n            >\n              {cardImage && (\n                <motion.span layoutId={layoutId(\"image\")} transition={transition} className=\"block\">\n                  {cardImage}\n                </motion.span>\n              )}\n              {cardText(\n                <motion.span\n                  layoutId={layoutId(\"title\")}\n                  layout=\"position\"\n                  transition={transition}\n                  className=\"block text-lg font-semibold\"\n                >\n                  {title}\n                </motion.span>\n              )}\n            </motion.span>\n          )}\n        </button>\n      </DialogPrimitive.Trigger>\n\n      <AnimatePresence>\n        {isOpen && (\n          <DialogPrimitive.Portal forceMount>\n            <DialogPrimitive.Overlay asChild forceMount>\n              <motion.div\n                className=\"fixed inset-0 z-50 bg-black/50\"\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: 0.2 }}\n              />\n            </DialogPrimitive.Overlay>\n            <div className=\"pointer-events-none fixed inset-0 z-50 grid place-items-center p-4\">\n              <DialogPrimitive.Content\n                asChild\n                forceMount\n                // Opt out of Radix's missing-description warning only when there is none.\n                {...(description ? {} : { \"aria-describedby\": undefined })}\n              >\n                <motion.div\n                  layoutId={layoutId(\"card\")}\n                  transition={transition}\n                  initial={reduceMotion ? { opacity: 0 } : undefined}\n                  animate={reduceMotion ? { opacity: 1 } : undefined}\n                  exit={reduceMotion ? { opacity: 0 } : undefined}\n                  className={cn(\n                    \"pointer-events-auto relative max-h-[calc(100dvh-2rem)] w-full max-w-2xl overflow-y-auto rounded-xl border bg-card text-card-foreground outline-none\",\n                    contentClassName\n                  )}\n                >\n                  {cardImage && (\n                    <motion.div layoutId={layoutId(\"image\")} transition={transition}>\n                      {cardImage}\n                    </motion.div>\n                  )}\n                  <div className=\"space-y-4 p-6\">\n                    <div className=\"space-y-1.5 pr-8\">\n                      <DialogPrimitive.Title asChild>\n                        <motion.h2\n                          layoutId={layoutId(\"title\")}\n                          layout=\"position\"\n                          transition={transition}\n                          className=\"text-xl font-semibold\"\n                        >\n                          {title}\n                        </motion.h2>\n                      </DialogPrimitive.Title>\n                      {description && (\n                        <DialogPrimitive.Description className=\"text-sm text-muted-foreground\">\n                          {description}\n                        </DialogPrimitive.Description>\n                      )}\n                    </div>\n                    <motion.div\n                      initial={{ opacity: 0 }}\n                      animate={{ opacity: 1, transition: { delay: reduceMotion ? 0 : 0.15 } }}\n                      exit={{ opacity: 0, transition: { duration: 0.1 } }}\n                      className=\"space-y-4\"\n                    >\n                      {children && <div className=\"text-sm leading-relaxed\">{children}</div>}\n                      {action && <div className=\"flex justify-end gap-2\">{action}</div>}\n                    </motion.div>\n                  </div>\n                  <DialogPrimitive.Close\n                    aria-label={closeLabel}\n                    className={cn(\n                      \"absolute top-3 right-3 flex size-8 items-center justify-center rounded-md border bg-background text-foreground outline-none transition-colors\",\n                      \"hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring\"\n                    )}\n                  >\n                    <X className=\"size-4\" aria-hidden />\n                  </DialogPrimitive.Close>\n                </motion.div>\n              </DialogPrimitive.Content>\n            </div>\n          </DialogPrimitive.Portal>\n        )}\n      </AnimatePresence>\n    </DialogPrimitive.Root>\n  );\n}\n"
    }
  ],
  "dependencies": [
    "@radix-ui/react-dialog",
    "lucide-react",
    "motion"
  ]
}
