{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-dialog",
  "type": "registry:ui",
  "title": "Animated Dialog",
  "description": "A composable, accessible dialog modal built on Base UI with controlled state, direction-aware enter and exit motion, and reduced-motion support.",
  "files": [
    {
      "path": "registry/sonaui/animated-dialog/animated-dialog.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Dialog } from \"@base-ui/react/dialog\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { createContext, type ReactNode, useContext, useState } from \"react\";\nimport { motionTransition } from \"@/lib/sona-motion\";\nimport { cn } from \"@/lib/sona-utils\";\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface DialogContextValue {\n  open: boolean;\n  setOpen: (open: boolean) => void;\n}\n\nconst DialogContext = createContext<DialogContextValue | null>(null);\n\nfunction useDialogContext() {\n  const ctx = useContext(DialogContext);\n  if (!ctx) {\n    throw new Error(\n      \"AnimatedDialog subcomponents must be used within <AnimatedDialog>\",\n    );\n  }\n  return ctx;\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AnimatedDialogProps {\n  children: ReactNode;\n  open?: boolean;\n  /** Initial open state for uncontrolled usage. @default false */\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Whether the dialog traps interaction inside the modal. @default true */\n  modal?: boolean;\n}\n\nexport interface AnimatedDialogContentProps {\n  children: ReactNode;\n  /**\n   * Direction the dialog slides in from.\n   * @default \"bottom\"\n   */\n  from?: \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\";\n  /**\n   * Direction the dialog slides out to. Defaults to reversing the entrance.\n   */\n  exitTo?: \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\";\n  className?: string;\n  /**\n   * Class name for the backdrop overlay.\n   */\n  backdropClassName?: string;\n}\n\n// ─── Root ─────────────────────────────────────────────────────────────────────\n\nexport function AnimatedDialog({\n  children,\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  modal = true,\n}: AnimatedDialogProps) {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const isControlled = controlledOpen !== undefined;\n  const open = isControlled ? controlledOpen : internalOpen;\n\n  const setOpen = (nextOpen: boolean) => {\n    if (!isControlled) {\n      setInternalOpen(nextOpen);\n    }\n    onOpenChange?.(nextOpen);\n  };\n\n  return (\n    <DialogContext.Provider value={{ open, setOpen }}>\n      <Dialog.Root\n        open={open}\n        defaultOpen={defaultOpen}\n        modal={modal}\n        onOpenChange={setOpen}\n      >\n        {children}\n      </Dialog.Root>\n    </DialogContext.Provider>\n  );\n}\n\n// ─── Trigger ──────────────────────────────────────────────────────────────────\n\nexport function AnimatedDialogTrigger({\n  children,\n  className,\n  ...props\n}: Dialog.Trigger.Props) {\n  return (\n    <Dialog.Trigger\n      className={cn(\n        \"inline-flex items-center justify-center rounded-lg px-4 py-2\",\n        \"bg-foreground text-background text-sm font-medium\",\n        \"hover:bg-foreground/90 hover:cursor-pointer transition-colors duration-150\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Dialog.Trigger>\n  );\n}\n\n// ─── Content ──────────────────────────────────────────────────────────────────\n\nconst motionVariants = {\n  initial: (from: string) => {\n    switch (from) {\n      case \"top\":\n        return { y: -24, opacity: 0, scale: 0.97 };\n      case \"bottom\":\n        return { y: 24, opacity: 0, scale: 0.97 };\n      case \"left\":\n        return { x: -24, opacity: 0, scale: 0.97 };\n      case \"right\":\n        return { x: 24, opacity: 0, scale: 0.97 };\n      default:\n        return { scale: 0.95, opacity: 0 };\n    }\n  },\n  animate: {\n    x: 0,\n    y: 0,\n    scale: 1,\n    opacity: 1,\n  },\n  exit: (exitTo: string) => {\n    switch (exitTo) {\n      case \"top\":\n        return { y: -20, opacity: 0, scale: 0.97 };\n      case \"bottom\":\n        return { y: 20, opacity: 0, scale: 0.97 };\n      case \"left\":\n        return { x: -20, opacity: 0, scale: 0.97 };\n      case \"right\":\n        return { x: 20, opacity: 0, scale: 0.97 };\n      default:\n        return { scale: 0.95, opacity: 0 };\n    }\n  },\n};\n\nexport function AnimatedDialogContent({\n  children,\n  from = \"bottom\",\n  exitTo,\n  className,\n  backdropClassName,\n}: AnimatedDialogContentProps) {\n  const { open } = useDialogContext();\n  const shouldReduceMotion = useReducedMotion();\n  const resolvedExitTo = exitTo ?? from;\n\n  return (\n    <Dialog.Portal>\n      <AnimatePresence custom={resolvedExitTo}>\n        {open && (\n          <>\n            {/* Backdrop Overlay */}\n            <Dialog.Backdrop\n              render={\n                <motion.div\n                  initial={{ opacity: 0 }}\n                  animate={{ opacity: 1 }}\n                  exit={{ opacity: 0 }}\n                  transition={\n                    shouldReduceMotion\n                      ? motionTransition.reduced\n                      : motionTransition.enter\n                  }\n                  className={cn(\n                    \"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm\",\n                    backdropClassName,\n                  )}\n                />\n              }\n            />\n\n            {/* Positioner centering the popup */}\n            <div className=\"pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4\">\n              <Dialog.Popup\n                className=\"pointer-events-auto\"\n                render={\n                  <motion.div\n                    custom={from}\n                    variants={shouldReduceMotion ? {} : motionVariants}\n                    initial=\"initial\"\n                    animate=\"animate\"\n                    exit=\"exit\"\n                    transition={\n                      shouldReduceMotion\n                        ? motionTransition.reduced\n                        : motionTransition.enter\n                    }\n                    className={cn(\n                      \"w-full max-w-md overflow-hidden rounded-2xl p-6\",\n                      \"bg-popover text-popover-foreground shadow-2xl\",\n                      \"border border-border/80\",\n                      className,\n                    )}\n                  >\n                    {children}\n                  </motion.div>\n                }\n              />\n            </div>\n          </>\n        )}\n      </AnimatePresence>\n    </Dialog.Portal>\n  );\n}\n\n// ─── Title ────────────────────────────────────────────────────────────────────\n\nexport function AnimatedDialogTitle({\n  className,\n  ...props\n}: Dialog.Title.Props) {\n  return (\n    <Dialog.Title\n      className={cn(\n        \"text-lg font-semibold tracking-tight text-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n// ─── Description ──────────────────────────────────────────────────────────────\n\nexport function AnimatedDialogDescription({\n  className,\n  ...props\n}: Dialog.Description.Props) {\n  return (\n    <Dialog.Description\n      className={cn(\"mt-1.5 text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n// ─── Close ────────────────────────────────────────────────────────────────────\n\nexport function AnimatedDialogClose({\n  className,\n  ...props\n}: Dialog.Close.Props) {\n  return (\n    <Dialog.Close\n      className={cn(\n        \"inline-flex items-center justify-center rounded-lg px-4 py-2\",\n        \"bg-secondary text-secondary-foreground text-sm font-medium\",\n        \"hover:bg-muted transition-colors duration-150\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n",
      "target": "components/ui/animated-dialog/animated-dialog.tsx"
    }
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils",
    "@sona-ui/sona-motion"
  ]
}