{
  "name": "morphing-dialog-basic-2",
  "type": "registry:ui",
  "componentName": "morphing-dialog-basic-2",
  "description": "Alternative implementation of the morphing dialog with different animations.",
  "files": [
    {
      "path": "morphing-dialog-basic-2.tsx",
      "content": "import {\n  MorphingDialog,\n  MorphingDialogTrigger,\n  MorphingDialogContent,\n  MorphingDialogTitle,\n  MorphingDialogImage,\n  MorphingDialogSubtitle,\n  MorphingDialogClose,\n  MorphingDialogContainer,\n} from '@/components/core/morphing-dialog';\nimport { ScrollArea } from '@/components/website/scroll-area';\n\nexport function MorphingDialogBasicTwo() {\n  return (\n    <MorphingDialog\n      transition={{\n        type: 'spring',\n        stiffness: 200,\n        damping: 24,\n      }}\n    >\n      <MorphingDialogTrigger\n        style={{\n          borderRadius: '4px',\n        }}\n        className='border border-gray-200/60 bg-white'\n      >\n        <div className='flex items-center space-x-3 p-3'>\n          <MorphingDialogImage\n            src='https://m.media-amazon.com/images/I/71skAxiMC2L._AC_UF1000,1000_QL80_.jpg'\n            alt='What I Talk About When I Talk About Running - book cover'\n            className='h-8 w-8 object-cover object-top'\n            style={{\n              borderRadius: '4px',\n            }}\n          />\n          <div className='flex flex-col items-start justify-center space-y-0'>\n            <MorphingDialogTitle className='text-[10px] font-medium text-black sm:text-xs'>\n              What I Talk About When I Talk About Running\n            </MorphingDialogTitle>\n            <MorphingDialogSubtitle className='text-[10px] text-gray-600 sm:text-xs'>\n              Haruki Murakami\n            </MorphingDialogSubtitle>\n          </div>\n        </div>\n      </MorphingDialogTrigger>\n      <MorphingDialogContainer>\n        <MorphingDialogContent\n          style={{\n            borderRadius: '12px',\n          }}\n          className='relative h-auto w-[500px] border border-gray-100 bg-white'\n        >\n          <ScrollArea className='h-[90vh]' type='scroll'>\n            <div className='relative p-6'>\n              <div className='flex justify-center py-10'>\n                <MorphingDialogImage\n                  src='https://m.media-amazon.com/images/I/71skAxiMC2L._AC_UF1000,1000_QL80_.jpg'\n                  alt='What I Talk About When I Talk About Running - book cover'\n                  className='h-auto w-[200px]'\n                />\n              </div>\n              <div className=''>\n                <MorphingDialogTitle className='text-black'>\n                  What I Talk About When I Talk About Running\n                </MorphingDialogTitle>\n                <MorphingDialogSubtitle className='font-light text-gray-400'>\n                  Haruki Murakami\n                </MorphingDialogSubtitle>\n                <div className='mt-4 text-sm text-gray-700'>\n                  <p>\n                    In 1982, having sold his jazz bar to devote himself to\n                    writing, Murakami began running to keep fit. A year later,\n                    he’d completed a solo course from Athens to Marathon, and\n                    now, after dozens of such races, not to mention triathlons\n                    and a dozen critically acclaimed books, he reflects upon the\n                    influence the sport has had on his life and—even more\n                    important—on his writing.\n                  </p>\n                  <p>\n                    Equal parts training log, travelogue, and reminiscence, this\n                    revealing memoir covers his four-month preparation for the\n                    2005 New York City Marathon and takes us to places ranging\n                    from Tokyo’s Jingu Gaien gardens, where he once shared the\n                    course with an Olympian, to the Charles River in Boston\n                    among young women who outpace him. Through this marvelous\n                    lens of sport emerges a panorama of memories and insights:\n                    the eureka moment when he decided to become a writer, his\n                    greatest triumphs and disappointments, his passion for\n                    vintage LPs, and the experience, after fifty, of seeing his\n                    race times improve and then fall back.\n                  </p>\n                  <p>\n                    By turns funny and sobering, playful and philosophical, What\n                    I Talk About When I Talk About Running is rich and\n                    revelatory, both for fans of this masterful yet guardedly\n                    private writer and for the exploding population of athletes\n                    who find similar satisfaction in running.\n                  </p>\n                </div>\n              </div>\n            </div>\n          </ScrollArea>\n          <MorphingDialogClose className='text-zinc-500' />\n        </MorphingDialogContent>\n      </MorphingDialogContainer>\n    </MorphingDialog>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/core/morphing-dialog.tsx",
      "content": "'use client';\n\nimport React, {\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  motion,\n  AnimatePresence,\n  MotionConfig,\n  Transition,\n  Variant,\n} from 'motion/react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '@/lib/utils';\nimport { XIcon } from 'lucide-react';\nimport useClickOutside from '@/hooks/useClickOutside';\n\nexport type MorphingDialogContextType = {\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  uniqueId: string;\n  triggerRef: React.RefObject<HTMLButtonElement | null>;\n};\n\nconst MorphingDialogContext =\n  React.createContext<MorphingDialogContextType | null>(null);\n\nfunction useMorphingDialog() {\n  const context = useContext(MorphingDialogContext);\n  if (!context) {\n    throw new Error(\n      'useMorphingDialog must be used within a MorphingDialogProvider'\n    );\n  }\n  return context;\n}\n\nexport type MorphingDialogProviderProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction MorphingDialogProvider({\n  children,\n  transition,\n}: MorphingDialogProviderProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const uniqueId = useId();\n  const triggerRef = useRef<HTMLButtonElement>(null!);\n\n  const contextValue = useMemo(\n    () => ({\n      isOpen,\n      setIsOpen,\n      uniqueId,\n      triggerRef,\n    }),\n    [isOpen, uniqueId]\n  );\n\n  return (\n    <MorphingDialogContext.Provider value={contextValue}>\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </MorphingDialogContext.Provider>\n  );\n}\n\nexport type MorphingDialogProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction MorphingDialog({ children, transition }: MorphingDialogProps) {\n  return (\n    <MorphingDialogProvider>\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </MorphingDialogProvider>\n  );\n}\n\nexport type MorphingDialogTriggerProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n  triggerRef?: React.RefObject<HTMLButtonElement>;\n};\n\nfunction MorphingDialogTrigger({\n  children,\n  className,\n  style,\n  triggerRef,\n}: MorphingDialogTriggerProps) {\n  const { setIsOpen, isOpen, uniqueId } = useMorphingDialog();\n\n  const handleClick = useCallback(() => {\n    setIsOpen(!isOpen);\n  }, [isOpen, setIsOpen]);\n\n  const handleKeyDown = useCallback(\n    (event: React.KeyboardEvent) => {\n      if (event.key === 'Enter' || event.key === ' ') {\n        event.preventDefault();\n        setIsOpen(!isOpen);\n      }\n    },\n    [isOpen, setIsOpen]\n  );\n\n  return (\n    <motion.button\n      ref={triggerRef}\n      layoutId={`dialog-${uniqueId}`}\n      className={cn('relative cursor-pointer', className)}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      style={style}\n      aria-haspopup='dialog'\n      aria-expanded={isOpen}\n      aria-controls={`motion-ui-morphing-dialog-content-${uniqueId}`}\n      aria-label={`Open dialog ${uniqueId}`}\n    >\n      {children}\n    </motion.button>\n  );\n}\n\nexport type MorphingDialogContentProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction MorphingDialogContent({\n  children,\n  className,\n  style,\n}: MorphingDialogContentProps) {\n  const { setIsOpen, isOpen, uniqueId, triggerRef } = useMorphingDialog();\n  const containerRef = useRef<HTMLDivElement>(null!);\n  const [firstFocusableElement, setFirstFocusableElement] =\n    useState<HTMLElement | null>(null);\n  const [lastFocusableElement, setLastFocusableElement] =\n    useState<HTMLElement | null>(null);\n\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') {\n        setIsOpen(false);\n      }\n      if (event.key === 'Tab') {\n        if (!firstFocusableElement || !lastFocusableElement) return;\n\n        if (event.shiftKey) {\n          if (document.activeElement === firstFocusableElement) {\n            event.preventDefault();\n            lastFocusableElement.focus();\n          }\n        } else {\n          if (document.activeElement === lastFocusableElement) {\n            event.preventDefault();\n            firstFocusableElement.focus();\n          }\n        }\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [setIsOpen, firstFocusableElement, lastFocusableElement]);\n\n  useEffect(() => {\n    if (isOpen) {\n      document.body.classList.add('overflow-hidden');\n      const focusableElements = containerRef.current?.querySelectorAll(\n        'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n      );\n      if (focusableElements && focusableElements.length > 0) {\n        setFirstFocusableElement(focusableElements[0] as HTMLElement);\n        setLastFocusableElement(\n          focusableElements[focusableElements.length - 1] as HTMLElement\n        );\n        (focusableElements[0] as HTMLElement).focus();\n      }\n    } else {\n      document.body.classList.remove('overflow-hidden');\n      triggerRef.current?.focus();\n    }\n  }, [isOpen, triggerRef]);\n\n  useClickOutside(containerRef, () => {\n    if (isOpen) {\n      setIsOpen(false);\n    }\n  });\n\n  return (\n    <motion.div\n      ref={containerRef}\n      layoutId={`dialog-${uniqueId}`}\n      className={cn('overflow-hidden', className)}\n      style={style}\n      role='dialog'\n      aria-modal='true'\n      aria-labelledby={`motion-ui-morphing-dialog-title-${uniqueId}`}\n      aria-describedby={`motion-ui-morphing-dialog-description-${uniqueId}`}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport type MorphingDialogContainerProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction MorphingDialogContainer({ children }: MorphingDialogContainerProps) {\n  const { isOpen, uniqueId } = useMorphingDialog();\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n    return () => setMounted(false);\n  }, []);\n\n  if (!mounted) return null;\n\n  return createPortal(\n    <AnimatePresence initial={false} mode='sync'>\n      {isOpen && (\n        <>\n          <motion.div\n            key={`backdrop-${uniqueId}`}\n            className='fixed inset-0 h-full w-full bg-white/40 backdrop-blur-xs dark:bg-black/40'\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n          />\n          <div className='fixed inset-0 z-50 flex items-center justify-center'>\n            {children}\n          </div>\n        </>\n      )}\n    </AnimatePresence>,\n    document.body\n  );\n}\n\nexport type MorphingDialogTitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction MorphingDialogTitle({\n  children,\n  className,\n  style,\n}: MorphingDialogTitleProps) {\n  const { uniqueId } = useMorphingDialog();\n\n  return (\n    <motion.div\n      layoutId={`dialog-title-container-${uniqueId}`}\n      className={className}\n      style={style}\n      layout\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport type MorphingDialogSubtitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction MorphingDialogSubtitle({\n  children,\n  className,\n  style,\n}: MorphingDialogSubtitleProps) {\n  const { uniqueId } = useMorphingDialog();\n\n  return (\n    <motion.div\n      layoutId={`dialog-subtitle-container-${uniqueId}`}\n      className={className}\n      style={style}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport type MorphingDialogDescriptionProps = {\n  children: React.ReactNode;\n  className?: string;\n  disableLayoutAnimation?: boolean;\n  variants?: {\n    initial: Variant;\n    animate: Variant;\n    exit: Variant;\n  };\n};\n\nfunction MorphingDialogDescription({\n  children,\n  className,\n  variants,\n  disableLayoutAnimation,\n}: MorphingDialogDescriptionProps) {\n  const { uniqueId } = useMorphingDialog();\n\n  return (\n    <motion.div\n      key={`dialog-description-${uniqueId}`}\n      layoutId={\n        disableLayoutAnimation\n          ? undefined\n          : `dialog-description-content-${uniqueId}`\n      }\n      variants={variants}\n      className={className}\n      initial='initial'\n      animate='animate'\n      exit='exit'\n      id={`dialog-description-${uniqueId}`}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport type MorphingDialogImageProps = {\n  src: string;\n  alt: string;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction MorphingDialogImage({\n  src,\n  alt,\n  className,\n  style,\n}: MorphingDialogImageProps) {\n  const { uniqueId } = useMorphingDialog();\n\n  return (\n    <motion.img\n      src={src}\n      alt={alt}\n      className={cn(className)}\n      layoutId={`dialog-img-${uniqueId}`}\n      style={style}\n    />\n  );\n}\n\nexport type MorphingDialogCloseProps = {\n  children?: React.ReactNode;\n  className?: string;\n  variants?: {\n    initial: Variant;\n    animate: Variant;\n    exit: Variant;\n  };\n};\n\nfunction MorphingDialogClose({\n  children,\n  className,\n  variants,\n}: MorphingDialogCloseProps) {\n  const { setIsOpen, uniqueId } = useMorphingDialog();\n\n  const handleClose = useCallback(() => {\n    setIsOpen(false);\n  }, [setIsOpen]);\n\n  return (\n    <motion.button\n      onClick={handleClose}\n      type='button'\n      aria-label='Close dialog'\n      key={`dialog-close-${uniqueId}`}\n      className={cn('absolute top-6 right-6', className)}\n      initial='initial'\n      animate='animate'\n      exit='exit'\n      variants={variants}\n    >\n      {children || <XIcon size={24} />}\n    </motion.button>\n  );\n}\n\nexport {\n  MorphingDialog,\n  MorphingDialogTrigger,\n  MorphingDialogContainer,\n  MorphingDialogContent,\n  MorphingDialogClose,\n  MorphingDialogTitle,\n  MorphingDialogSubtitle,\n  MorphingDialogDescription,\n  MorphingDialogImage,\n};\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"
    }
  ]
}