{ "$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(null);\n const triggerRef = useRef(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(\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 \n
\n \n 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 \n Open Bottom Modal\n \n \n\n \n {isModalOpen && (\n <>\n {/* Backdrop */}\n setIsModalOpen(false)}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n \n
\n {/* Gradient overlay */}\n
\n\n {/* Header */}\n
\n \n Hello\n \n 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 \n \n
\n\n {/* Content */}\n
\n \n

\n Welcome to the Modal!\n

\n \n This is a cute bottom-centered modal with smooth\n animations powered by Framer Motion.\n

\n \n\n \n setIsModalOpen(false)}\n >\n Cancel\n \n setIsModalOpen(false)}\n >\n Got it!\n \n \n
\n
\n \n \n )}\n \n
\n \n );\n}\n", "type": "registry:component", "target": "components/uitripled/bottom-modal.tsx" } ] }