{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bottom-modal",
  "type": "registry:component",
  "title": "Bottom Modal",
  "description": "Cute bottom-centered modal with smooth slide-up animation and glassmorphism design",
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/modals/bottom-modal.tsx",
      "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AnimatePresence, motion, MotionConfig } from \"framer-motion\";\nimport { Sparkles, X } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport function BottomModal() {\n  const [isModalOpen, setIsModalOpen] = useState(false);\n  const modalRef = useRef<HTMLDivElement>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n\n  // Focus management: trap Tab inside the dialog, close on Escape,\n  // lock body scroll, and restore focus to the trigger on close.\n  useEffect(() => {\n    if (!isModalOpen) return;\n\n    const focusables = modalRef.current?.querySelectorAll<HTMLElement>(\n      'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n    );\n    focusables?.[0]?.focus();\n\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        setIsModalOpen(false);\n        return;\n      }\n      if (e.key !== \"Tab\" || !focusables || focusables.length === 0) return;\n      const first = focusables[0];\n      const last = focusables[focusables.length - 1];\n      if (e.shiftKey && document.activeElement === first) {\n        e.preventDefault();\n        last.focus();\n      } else if (!e.shiftKey && document.activeElement === last) {\n        e.preventDefault();\n        first.focus();\n      }\n    };\n\n    document.addEventListener(\"keydown\", handleKeyDown);\n    const previousOverflow = document.body.style.overflow;\n    document.body.style.overflow = \"hidden\";\n\n    return () => {\n      document.removeEventListener(\"keydown\", handleKeyDown);\n      document.body.style.overflow = previousOverflow;\n      triggerRef.current?.focus();\n    };\n  }, [isModalOpen]);\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div className=\"text-center space-y-8 w-full mx-auto\">\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.4, delay: 0.1 }}\n        >\n          <Button\n            ref={triggerRef}\n            onClick={() => setIsModalOpen(true)}\n            size=\"lg\"\n            aria-haspopup=\"dialog\"\n            aria-expanded={isModalOpen}\n            className=\"gap-2 w-full rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg hover:shadow-xl transition-shadow duration-300\"\n          >\n            <Sparkles className=\"h-5 w-5\" aria-hidden=\"true\" />\n            Open Bottom Modal\n          </Button>\n        </motion.div>\n\n        <AnimatePresence>\n          {isModalOpen && (\n            <>\n              {/* Backdrop */}\n              <motion.div\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: 0.2 }}\n                className=\"fixed inset-0 bg-background/80 backdrop-blur-sm z-40 h-full\"\n                onClick={() => setIsModalOpen(false)}\n                aria-hidden=\"true\"\n              />\n\n              {/* Modal */}\n              <motion.div\n                ref={modalRef}\n                initial={{ y: \"100%\", opacity: 0 }}\n                animate={{ y: 0, opacity: 1 }}\n                exit={{\n                  y: \"100%\",\n                  opacity: 0,\n                  transition: { duration: 0.2, ease: [0.23, 1, 0.32, 1] },\n                }}\n                transition={{\n                  type: \"spring\",\n                  damping: 25,\n                  stiffness: 300,\n                }}\n                className=\"fixed bottom-0 right-0 left-0 mx-auto w-full md:max-w-md z-50\"\n                role=\"dialog\"\n                aria-modal=\"true\"\n                aria-labelledby=\"modal-title\"\n                aria-describedby=\"modal-description\"\n              >\n                <div className=\"group relative overflow-hidden rounded-tl-2xl rounded-tr-2xl border border-border/40 bg-background/60 backdrop-blur shadow-lg\">\n                  {/* Gradient overlay */}\n                  <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-br from-foreground/[0.04] via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 -z-10\" />\n\n                  {/* Header */}\n                  <div className=\"relative flex items-center justify-between p-4 border-b border-border/40\">\n                    <h3\n                      id=\"modal-title\"\n                      className=\"text-lg font-semibold text-foreground\"\n                    >\n                      Hello\n                    </h3>\n                    <button\n                      type=\"button\"\n                      onClick={() => setIsModalOpen(false)}\n                      className=\"p-2 rounded-lg border border-border/40 bg-background/60 backdrop-blur hover:border-border/60 hover:bg-background/70 transition-colors duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n                      aria-label=\"Close modal\"\n                    >\n                      <X\n                        aria-hidden=\"true\"\n                        className=\"h-5 w-5 text-foreground/70 hover:text-foreground\"\n                      />\n                    </button>\n                  </div>\n\n                  {/* Content */}\n                  <div className=\"relative p-6 space-y-4\">\n                    <motion.div\n                      initial={{ opacity: 0, y: 10 }}\n                      animate={{ opacity: 1, y: 0 }}\n                      transition={{ delay: 0.1, duration: 0.3 }}\n                      className=\"text-center space-y-2\"\n                    >\n                      <h4 className=\"text-xl font-semibold text-foreground\">\n                        Welcome to the Modal!\n                      </h4>\n                      <p\n                        id=\"modal-description\"\n                        className=\"text-sm text-foreground/70\"\n                      >\n                        This is a cute bottom-centered modal with smooth\n                        animations powered by Framer Motion.\n                      </p>\n                    </motion.div>\n\n                    <motion.div\n                      initial={{ opacity: 0, y: 10 }}\n                      animate={{ opacity: 1, y: 0 }}\n                      transition={{ delay: 0.2, duration: 0.3 }}\n                      className=\"flex gap-3\"\n                    >\n                      <Button\n                        variant=\"outline\"\n                        className=\"flex-1 rounded-lg border-border/40 bg-background/40 hover:bg-background/60\"\n                        onClick={() => setIsModalOpen(false)}\n                      >\n                        Cancel\n                      </Button>\n                      <Button\n                        className=\"flex-1 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90\"\n                        onClick={() => setIsModalOpen(false)}\n                      >\n                        Got it!\n                      </Button>\n                    </motion.div>\n                  </div>\n                </div>\n              </motion.div>\n            </>\n          )}\n        </AnimatePresence>\n      </div>\n    </MotionConfig>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/uitripled/bottom-modal.tsx"
    }
  ]
}