{ "name": "morphing-popover", "type": "registry:ui", "registryDependencies": [], "dependencies": [ "motion" ], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "files": [ { "path": "morphing-popover.tsx", "content": "'use client';\n\nimport {\n useState,\n useId,\n useRef,\n useEffect,\n createContext,\n useContext,\n isValidElement,\n} from 'react';\nimport {\n AnimatePresence,\n MotionConfig,\n motion,\n Transition,\n Variants,\n} from 'motion/react';\nimport useClickOutside from '@/hooks/useClickOutside';\nimport { cn } from '@/lib/utils';\n\nconst TRANSITION = {\n type: 'spring',\n bounce: 0.1,\n duration: 0.4,\n};\n\ntype MorphingPopoverContextValue = {\n isOpen: boolean;\n open: () => void;\n close: () => void;\n uniqueId: string;\n variants?: Variants;\n};\n\nconst MorphingPopoverContext =\n createContext(null);\n\nfunction usePopoverLogic({\n defaultOpen = false,\n open: controlledOpen,\n onOpenChange,\n}: {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n} = {}) {\n const uniqueId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n\n const isOpen = controlledOpen ?? uncontrolledOpen;\n\n const open = () => {\n if (controlledOpen === undefined) {\n setUncontrolledOpen(true);\n }\n onOpenChange?.(true);\n };\n\n const close = () => {\n if (controlledOpen === undefined) {\n setUncontrolledOpen(false);\n }\n onOpenChange?.(false);\n };\n\n return { isOpen, open, close, uniqueId };\n}\n\nexport type MorphingPopoverProps = {\n children: React.ReactNode;\n transition?: Transition;\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n variants?: Variants;\n className?: string;\n} & React.ComponentProps<'div'>;\n\nfunction MorphingPopover({\n children,\n transition = TRANSITION,\n defaultOpen,\n open,\n onOpenChange,\n variants,\n className,\n ...props\n}: MorphingPopoverProps) {\n const popoverLogic = usePopoverLogic({ defaultOpen, open, onOpenChange });\n\n return (\n \n \n \n {children}\n \n \n \n );\n}\n\nexport type MorphingPopoverTriggerProps = {\n asChild?: boolean;\n children: React.ReactNode;\n className?: string;\n} & React.ComponentProps;\n\nfunction MorphingPopoverTrigger({\n children,\n className,\n asChild = false,\n ...props\n}: MorphingPopoverTriggerProps) {\n const context = useContext(MorphingPopoverContext);\n if (!context) {\n throw new Error(\n 'MorphingPopoverTrigger must be used within MorphingPopover'\n );\n }\n\n if (asChild && isValidElement(children)) {\n const MotionComponent = motion.create(\n children.type as React.ForwardRefExoticComponent\n );\n const childProps = children.props as Record;\n\n return (\n \n );\n }\n\n return (\n \n \n {children}\n \n \n );\n}\n\nexport type MorphingPopoverContentProps = {\n children: React.ReactNode;\n className?: string;\n} & React.ComponentProps;\n\nfunction MorphingPopoverContent({\n children,\n className,\n ...props\n}: MorphingPopoverContentProps) {\n const context = useContext(MorphingPopoverContext);\n if (!context)\n throw new Error(\n 'MorphingPopoverContent must be used within MorphingPopover'\n );\n\n const ref = useRef(null);\n useClickOutside(ref, context.close);\n\n useEffect(() => {\n if (!context.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') context.close();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [context.isOpen, context.close]);\n\n return (\n \n {context.isOpen && (\n <>\n \n {children}\n \n \n )}\n \n );\n}\n\nexport { MorphingPopover, MorphingPopoverTrigger, MorphingPopoverContent };\n", "type": "registry:ui" }, { "path": "hooks/useClickOutside.tsx", "content": "import { RefObject, useEffect } from 'react';\n\nfunction useClickOutside(\n ref: RefObject,\n handler: (event: MouseEvent | TouchEvent) => void\n): void {\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent | TouchEvent) => {\n if (!ref || !ref.current || ref.current.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('touchstart', handleClickOutside);\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('touchstart', handleClickOutside);\n };\n }, [ref, handler]);\n}\n\nexport default useClickOutside;\n", "type": "registry:hook" } ] }