{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["lucide-react","motion","radix-ui"],"description":"Animated Dialog and AlertDialog components for SmoothUI.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport {\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport {\n  DialogClose,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { cn } from \"@/lib/utils\";\nimport { X } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport {\n  AlertDialog as AlertDialogRadix,\n  Dialog as DialogRadix,\n} from \"radix-ui\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/* ------------------------------------------------------------------ */\n/*  Shared animation constants                                        */\n/* ------------------------------------------------------------------ */\n\nconst SPRING_PANEL = {\n  bounce: 0.05,\n  duration: 0.25,\n  type: \"spring\" as const,\n};\n\nconst BACKDROP_DURATION = 0.2;\n\nconst STAGGER_DELAY = 0.06;\n\n/* ------------------------------------------------------------------ */\n/*  Types                                                              */\n/* ------------------------------------------------------------------ */\n\nexport interface DialogProps {\n  /** Dialog content */\n  children?: React.ReactNode;\n  /** Additional CSS class names for the content */\n  className?: string;\n  /** Description displayed below the title */\n  description?: string;\n  /** Footer content */\n  footer?: React.ReactNode;\n  /** Callback when the open state changes */\n  onOpenChange?: (open: boolean) => void;\n  /** Whether the dialog is open */\n  open?: boolean;\n  /** Whether to show the close button */\n  showCloseButton?: boolean;\n  /** Title displayed in the dialog header */\n  title?: string;\n  /** Trigger element that opens the dialog */\n  trigger?: React.ReactNode;\n}\n\nexport interface AlertDialogProps {\n  /** Alert dialog content */\n  children?: React.ReactNode;\n  /** Additional CSS class names for the content */\n  className?: string;\n  /** Description displayed below the title */\n  description?: string;\n  /** Footer content (typically AlertDialogAction + AlertDialogCancel) */\n  footer?: React.ReactNode;\n  /** Callback when the open state changes */\n  onOpenChange?: (open: boolean) => void;\n  /** Whether the alert dialog is open */\n  open?: boolean;\n  /** Title displayed in the alert dialog header */\n  title?: string;\n  /** Trigger element that opens the alert dialog */\n  trigger?: React.ReactNode;\n}\n\n/* ------------------------------------------------------------------ */\n/*  Stagger wrapper — animates children in sequence                   */\n/* ------------------------------------------------------------------ */\n\nconst StaggerChild = ({\n  children,\n  index,\n  shouldReduceMotion,\n}: {\n  children: React.ReactNode;\n  index: number;\n  shouldReduceMotion: boolean | null;\n}) => (\n  <motion.div\n    animate={\n      shouldReduceMotion\n        ? { opacity: 1 }\n        : { opacity: 1, transform: \"translateY(0px)\" }\n    }\n    exit={\n      shouldReduceMotion\n        ? { opacity: 0, transition: { duration: 0 } }\n        : {\n            opacity: 0,\n            transform: \"translateY(4px)\",\n            transition: { duration: 0.12 },\n          }\n    }\n    initial={\n      shouldReduceMotion\n        ? { opacity: 1 }\n        : { opacity: 0, transform: \"translateY(8px)\" }\n    }\n    transition={\n      shouldReduceMotion\n        ? { duration: 0 }\n        : {\n            bounce: 0,\n            delay: index * STAGGER_DELAY,\n            duration: 0.25,\n            type: \"spring\" as const,\n          }\n    }\n  >\n    {children}\n  </motion.div>\n);\n\n/* ------------------------------------------------------------------ */\n/*  Animated close button with rotate on hover                        */\n/* ------------------------------------------------------------------ */\n\nconst AnimatedCloseButton = ({\n  onClick,\n  shouldReduceMotion,\n}: {\n  onClick: () => void;\n  shouldReduceMotion: boolean | null;\n}) => (\n  <motion.button\n    aria-label=\"Close dialog\"\n    className=\"absolute top-4 right-4 cursor-pointer rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none\"\n    onClick={onClick}\n    transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}\n    type=\"button\"\n    whileHover={shouldReduceMotion ? {} : { rotate: 90 }}\n  >\n    <X aria-hidden=\"true\" className=\"pointer-events-none size-4 shrink-0\" />\n    <span className=\"sr-only\">Close</span>\n  </motion.button>\n);\n\n/* ------------------------------------------------------------------ */\n/*  Dialog                                                             */\n/* ------------------------------------------------------------------ */\n\nexport default function Dialog({\n  open,\n  onOpenChange,\n  title,\n  description,\n  showCloseButton = true,\n  className,\n  children,\n  trigger,\n  footer,\n}: DialogProps) {\n  const shouldReduceMotion = useReducedMotion();\n  const [internalOpen, setInternalOpen] = useState(false);\n  const isControlled = open !== undefined;\n  const isOpen = isControlled ? open : internalOpen;\n\n  const handleOpenChange = useCallback(\n    (next: boolean) => {\n      if (!isControlled) {\n        setInternalOpen(next);\n      }\n      onOpenChange?.(next);\n    },\n    [isControlled, onOpenChange]\n  );\n\n  // Track mount state for AnimatePresence exit animations\n  const [showContent, setShowContent] = useState(false);\n  const prevOpenRef = useRef(false);\n\n  useEffect(() => {\n    if (isOpen && !prevOpenRef.current) {\n      setShowContent(true);\n    }\n    prevOpenRef.current = !!isOpen;\n  }, [isOpen]);\n\n  const handleAnimationComplete = useCallback(() => {\n    if (!isOpen) {\n      setShowContent(false);\n    }\n  }, [isOpen]);\n\n  return (\n    <DialogRadix.Root\n      onOpenChange={handleOpenChange}\n      open={isOpen || showContent}\n    >\n      {trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}\n\n      <AnimatePresence onExitComplete={() => setShowContent(false)}>\n        {isOpen ? (\n          <DialogRadix.Portal forceMount>\n            {/* Backdrop with blur + opacity fade */}\n            <DialogRadix.Overlay asChild forceMount>\n              <motion.div\n                animate={{ opacity: 1 }}\n                className=\"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm\"\n                data-slot=\"dialog-overlay\"\n                exit={{ opacity: 0 }}\n                initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0 }}\n                transition={{\n                  duration: shouldReduceMotion ? 0 : BACKDROP_DURATION,\n                }}\n              />\n            </DialogRadix.Overlay>\n\n            {/* Content panel — spring scale + translateY entrance */}\n            <DialogRadix.Content asChild forceMount>\n              <motion.div\n                animate={\n                  shouldReduceMotion\n                    ? { opacity: 1 }\n                    : {\n                        opacity: 1,\n                        transform: \"translate(-50%, -50%) scale(1)\",\n                      }\n                }\n                className={cn(\n                  \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border bg-background p-6 shadow-lg sm:max-w-lg\",\n                  className\n                )}\n                data-slot=\"dialog-content\"\n                exit={\n                  shouldReduceMotion\n                    ? { opacity: 0, transition: { duration: 0 } }\n                    : {\n                        opacity: 0,\n                        transform: \"translate(-50%, -50%) scale(0.95)\",\n                        transition: { duration: 0.15 },\n                      }\n                }\n                initial={\n                  shouldReduceMotion\n                    ? {\n                        opacity: 1,\n                        transform: \"translate(-50%, -50%) scale(1)\",\n                      }\n                    : {\n                        opacity: 0,\n                        transform: \"translate(-50%, -48%) scale(0.95)\",\n                      }\n                }\n                onAnimationComplete={handleAnimationComplete}\n                transition={shouldReduceMotion ? { duration: 0 } : SPRING_PANEL}\n              >\n                {/* Staggered content sections */}\n                {title || description ? (\n                  <StaggerChild\n                    index={0}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    <DialogHeader>\n                      {title ? <DialogTitle>{title}</DialogTitle> : null}\n                      {description ? (\n                        <DialogDescription>{description}</DialogDescription>\n                      ) : null}\n                    </DialogHeader>\n                  </StaggerChild>\n                ) : null}\n\n                {children ? (\n                  <StaggerChild\n                    index={title || description ? 1 : 0}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    {children}\n                  </StaggerChild>\n                ) : null}\n\n                {footer ? (\n                  <StaggerChild\n                    index={(title || description ? 1 : 0) + (children ? 1 : 0)}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    <DialogFooter>{footer}</DialogFooter>\n                  </StaggerChild>\n                ) : null}\n\n                {showCloseButton ? (\n                  <AnimatedCloseButton\n                    onClick={() => handleOpenChange(false)}\n                    shouldReduceMotion={shouldReduceMotion}\n                  />\n                ) : null}\n              </motion.div>\n            </DialogRadix.Content>\n          </DialogRadix.Portal>\n        ) : null}\n      </AnimatePresence>\n    </DialogRadix.Root>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/*  AlertDialog                                                        */\n/* ------------------------------------------------------------------ */\n\nexport function AlertDialog({\n  open,\n  onOpenChange,\n  title,\n  description,\n  className,\n  children,\n  trigger,\n  footer,\n}: AlertDialogProps) {\n  const shouldReduceMotion = useReducedMotion();\n  const [internalOpen, setInternalOpen] = useState(false);\n  const isControlled = open !== undefined;\n  const isOpen = isControlled ? open : internalOpen;\n\n  const handleOpenChange = useCallback(\n    (next: boolean) => {\n      if (!isControlled) {\n        setInternalOpen(next);\n      }\n      onOpenChange?.(next);\n    },\n    [isControlled, onOpenChange]\n  );\n\n  const [showContent, setShowContent] = useState(false);\n  const prevOpenRef = useRef(false);\n\n  useEffect(() => {\n    if (isOpen && !prevOpenRef.current) {\n      setShowContent(true);\n    }\n    prevOpenRef.current = !!isOpen;\n  }, [isOpen]);\n\n  const handleAnimationComplete = useCallback(() => {\n    if (!isOpen) {\n      setShowContent(false);\n    }\n  }, [isOpen]);\n\n  return (\n    <AlertDialogRadix.Root\n      onOpenChange={handleOpenChange}\n      open={isOpen || showContent}\n    >\n      {trigger ? (\n        <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>\n      ) : null}\n\n      <AnimatePresence onExitComplete={() => setShowContent(false)}>\n        {isOpen ? (\n          <AlertDialogRadix.Portal forceMount>\n            {/* Backdrop */}\n            <AlertDialogRadix.Overlay asChild forceMount>\n              <motion.div\n                animate={{ opacity: 1 }}\n                className=\"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm\"\n                data-slot=\"alert-dialog-overlay\"\n                exit={{ opacity: 0 }}\n                initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0 }}\n                transition={{\n                  duration: shouldReduceMotion ? 0 : BACKDROP_DURATION,\n                }}\n              />\n            </AlertDialogRadix.Overlay>\n\n            {/* Content panel */}\n            <AlertDialogRadix.Content asChild forceMount>\n              <motion.div\n                animate={\n                  shouldReduceMotion\n                    ? { opacity: 1 }\n                    : {\n                        opacity: 1,\n                        transform: \"translate(-50%, -50%) scale(1)\",\n                      }\n                }\n                className={cn(\n                  \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-lg border bg-background p-6 shadow-lg sm:max-w-lg\",\n                  className\n                )}\n                data-slot=\"alert-dialog-content\"\n                exit={\n                  shouldReduceMotion\n                    ? { opacity: 0, transition: { duration: 0 } }\n                    : {\n                        opacity: 0,\n                        transform: \"translate(-50%, -50%) scale(0.95)\",\n                        transition: { duration: 0.15 },\n                      }\n                }\n                initial={\n                  shouldReduceMotion\n                    ? {\n                        opacity: 1,\n                        transform: \"translate(-50%, -50%) scale(1)\",\n                      }\n                    : {\n                        opacity: 0,\n                        transform: \"translate(-50%, -48%) scale(0.95)\",\n                      }\n                }\n                onAnimationComplete={handleAnimationComplete}\n                transition={shouldReduceMotion ? { duration: 0 } : SPRING_PANEL}\n              >\n                {title || description ? (\n                  <StaggerChild\n                    index={0}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    <AlertDialogHeader>\n                      {title ? (\n                        <AlertDialogTitle>{title}</AlertDialogTitle>\n                      ) : null}\n                      {description ? (\n                        <AlertDialogDescription>\n                          {description}\n                        </AlertDialogDescription>\n                      ) : null}\n                    </AlertDialogHeader>\n                  </StaggerChild>\n                ) : null}\n\n                {children ? (\n                  <StaggerChild\n                    index={title || description ? 1 : 0}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    {children}\n                  </StaggerChild>\n                ) : null}\n\n                {footer ? (\n                  <StaggerChild\n                    index={(title || description ? 1 : 0) + (children ? 1 : 0)}\n                    shouldReduceMotion={shouldReduceMotion}\n                  >\n                    <AlertDialogFooter>{footer}</AlertDialogFooter>\n                  </StaggerChild>\n                ) : null}\n              </motion.div>\n            </AlertDialogRadix.Content>\n          </AlertDialogRadix.Portal>\n        ) : null}\n      </AnimatePresence>\n    </AlertDialogRadix.Root>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/*  Re-exports                                                         */\n/* ------------------------------------------------------------------ */\n\nexport {\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n  DialogClose,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n};\n","path":"index.tsx","target":"components/smoothui/dialog/index.tsx","type":"registry:ui"}],"name":"dialog","registryDependencies":["alert-dialog","dialog"],"title":"Dialog","type":"registry:ui"}