{
  "name": "morphing-popover-custom-transition-variants",
  "type": "registry:ui",
  "componentName": "morphing-popover",
  "description": "Morphing popover with custom transition variants and animations.",
  "files": [
    {
      "path": "morphing-popover-custom-transition-variants.tsx",
      "content": "import {\n  MorphingPopover,\n  MorphingPopoverContent,\n  MorphingPopoverTrigger,\n} from '@/components/core/morphing-popover';\nimport { Button } from '@/components/ui/button';\nimport { Label } from '@/components/ui/label';\nimport { Input } from '@/components/ui/input';\nimport * as motion from 'motion/react-client';\n\nexport function MorphingPopoverCustomTransitionVariants() {\n  return (\n    <MorphingPopover\n      variants={{\n        initial: { opacity: 0, filter: 'blur(10px)' },\n        animate: { opacity: 1, filter: 'blur(0px)' },\n        exit: { opacity: 0, filter: 'blur(10px)' },\n      }}\n      transition={{\n        duration: 0.25,\n        ease: 'easeOut',\n      }}\n    >\n      <MorphingPopoverTrigger asChild>\n        <Button variant='outline'>\n          <motion.span\n            layoutId='morphing-popover-custom-transition-variants-label'\n            layout='position'\n            className='inline-block'\n          >\n            Open popover\n          </motion.span>\n        </Button>\n      </MorphingPopoverTrigger>\n      <MorphingPopoverContent className='w-80 p-4 shadow-sm'>\n        <div className='grid gap-4'>\n          <div className='space-y-2'>\n            <motion.h4\n              layoutId='morphing-popover-custom-transition-variants-label'\n              layout='position'\n              className='leading-none font-medium'\n            >\n              Dimensions\n            </motion.h4>\n            <p className='text-muted-foreground text-sm'>\n              Set the dimensions for the layer.\n            </p>\n          </div>\n          <div className='grid gap-2'>\n            <div className='grid grid-cols-3 items-center gap-4'>\n              <Label htmlFor='width'>Width</Label>\n              <Input\n                id='width'\n                defaultValue='100%'\n                className='col-span-2 h-8'\n                autoFocus\n              />\n            </div>\n            <div className='grid grid-cols-3 items-center gap-4'>\n              <Label htmlFor='maxWidth'>Max. width</Label>\n              <Input\n                id='maxWidth'\n                defaultValue='300px'\n                className='col-span-2 h-8'\n              />\n            </div>\n            <div className='grid grid-cols-3 items-center gap-4'>\n              <Label htmlFor='height'>Height</Label>\n              <Input\n                id='height'\n                defaultValue='25px'\n                className='col-span-2 h-8'\n              />\n            </div>\n            <div className='grid grid-cols-3 items-center gap-4'>\n              <Label htmlFor='maxHeight'>Max. height</Label>\n              <Input\n                id='maxHeight'\n                defaultValue='none'\n                className='col-span-2 h-8'\n              />\n            </div>\n          </div>\n        </div>\n      </MorphingPopoverContent>\n    </MorphingPopover>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/core/morphing-popover.tsx",
      "content": "'use client';\n\nimport {\n  useState,\n  useId,\n  useRef,\n  useEffect,\n  useCallback,\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: 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<MorphingPopoverContextValue | null>(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 = useCallback(() => {\n    if (controlledOpen === undefined) {\n      setUncontrolledOpen(true);\n    }\n    onOpenChange?.(true);\n  }, [controlledOpen, onOpenChange]);\n\n  const close = useCallback(() => {\n    if (controlledOpen === undefined) {\n      setUncontrolledOpen(false);\n    }\n    onOpenChange?.(false);\n  }, [controlledOpen, onOpenChange]);\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    <MorphingPopoverContext.Provider value={{ ...popoverLogic, variants }}>\n      <MotionConfig transition={transition}>\n        <div\n          className={cn('relative flex items-center justify-center', className)}\n          key={popoverLogic.uniqueId}\n          {...props}\n        >\n          {children}\n        </div>\n      </MotionConfig>\n    </MorphingPopoverContext.Provider>\n  );\n}\n\nexport type MorphingPopoverTriggerProps = {\n  asChild?: boolean;\n  children: React.ReactNode;\n  className?: string;\n} & React.ComponentProps<typeof motion.button>;\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<any>\n    );\n    const childProps = children.props as Record<string, unknown>;\n\n    return (\n      <MotionComponent\n        {...childProps}\n        onClick={context.open}\n        layoutId={`popover-trigger-${context.uniqueId}`}\n        className={childProps.className}\n        key={context.uniqueId}\n        aria-expanded={context.isOpen}\n        aria-controls={`popover-content-${context.uniqueId}`}\n      />\n    );\n  }\n\n  return (\n    <motion.div\n      key={context.uniqueId}\n      layoutId={`popover-trigger-${context.uniqueId}`}\n      onClick={context.open}\n    >\n      <motion.button\n        {...props}\n        layoutId={`popover-label-${context.uniqueId}`}\n        key={context.uniqueId}\n        className={className}\n        aria-expanded={context.isOpen}\n        aria-controls={`popover-content-${context.uniqueId}`}\n      >\n        {children}\n      </motion.button>\n    </motion.div>\n  );\n}\n\nexport type MorphingPopoverContentProps = {\n  children: React.ReactNode;\n  className?: string;\n} & React.ComponentProps<typeof motion.div>;\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 { close, isOpen, uniqueId, variants } = context;\n  const ref = useRef<HTMLDivElement>(null);\n  useClickOutside(ref, close);\n\n  useEffect(() => {\n    if (!isOpen) return;\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') close();\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => document.removeEventListener('keydown', handleKeyDown);\n  }, [close, isOpen]);\n\n  return (\n    <AnimatePresence>\n      {isOpen && (\n        <>\n          <motion.div\n            {...props}\n            ref={ref}\n            layoutId={`popover-trigger-${uniqueId}`}\n            key={uniqueId}\n            id={`popover-content-${uniqueId}`}\n            role='dialog'\n            aria-modal='true'\n            className={cn(\n              'absolute overflow-hidden rounded-md border border-zinc-950/10 bg-white p-2 text-zinc-950 shadow-md dark:border-zinc-50/10 dark:bg-zinc-700 dark:text-zinc-50',\n              className\n            )}\n            initial='initial'\n            animate='animate'\n            exit='exit'\n            variants={variants}\n          >\n            {children}\n          </motion.div>\n        </>\n      )}\n    </AnimatePresence>\n  );\n}\n\nexport { MorphingPopover, MorphingPopoverTrigger, MorphingPopoverContent };\n",
      "type": "registry:ui"
    },
    {
      "path": "components/core/hooks/useClickOutside.tsx",
      "content": "import { RefObject, useEffect } from 'react';\n\nfunction useClickOutside<T extends HTMLElement>(\n  ref: RefObject<T | null>,\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"
    }
  ]
}