{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "linear-modal",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "./components/ui/linear-modal.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport { XIcon } from 'lucide-react';\nimport { AnimatePresence, MotionConfig, type Transition, type Variant, motion } from 'motion/react';\nimport React, { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\n\ninterface DialogContextType {\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  uniqueId: string;\n  triggerRef: React.RefObject<HTMLDivElement>;\n}\n\nconst DialogContext = React.createContext<DialogContextType | null>(null);\n\nfunction useDialog() {\n  const context = useContext(DialogContext);\n  if (!context) {\n    throw new Error('useDialog must be used within a DialogProvider');\n  }\n  return context;\n}\n\ntype DialogProviderProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction DialogProvider({ children, transition }: DialogProviderProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const uniqueId = useId();\n  const triggerRef = useRef<HTMLDivElement>(null);\n\n  const contextValue = useMemo(\n    () => ({ isOpen, setIsOpen, uniqueId, triggerRef }),\n    [isOpen, uniqueId]\n  );\n\n  return (\n    <DialogContext.Provider\n      //@ts-expect-error\n      value={contextValue}\n    >\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </DialogContext.Provider>\n  );\n}\n\ntype DialogProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction Dialog({ children, transition }: DialogProps) {\n  return (\n    <DialogProvider>\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </DialogProvider>\n  );\n}\n\ntype DialogTriggerProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n  triggerRef?: React.RefObject<HTMLDivElement>;\n};\n\nfunction DialogTrigger({ children, className, style, triggerRef }: DialogTriggerProps) {\n  const { setIsOpen, isOpen, uniqueId } = useDialog();\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.div\n      ref={triggerRef}\n      layoutId={`dialog-${uniqueId}`}\n      className={cn('relative cursor-pointer', className)}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      style={style}\n      role='button'\n      aria-haspopup='dialog'\n      aria-expanded={isOpen}\n      aria-controls={`dialog-content-${uniqueId}`}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\ntype DialogContent = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogContent({ children, className, style }: DialogContent) {\n  const { setIsOpen, isOpen, uniqueId, triggerRef } = useDialog();\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [firstFocusableElement, setFirstFocusableElement] = useState<HTMLElement | null>(null);\n  const [lastFocusableElement, setLastFocusableElement] = 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.style.overflow = 'hidden';\n\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(focusableElements[focusableElements.length - 1] as HTMLElement);\n        // Delay focus slightly to allow animation to start\n        requestAnimationFrame(() => {\n          (focusableElements[0] as HTMLElement).focus();\n        });\n      }\n\n      if (containerRef.current) {\n        containerRef.current.scrollTop = 0;\n      }\n    } else {\n      document.body.style.overflow = '';\n      triggerRef.current?.focus();\n    }\n  }, [isOpen, triggerRef]);\n\n  return (\n    <>\n      <motion.div\n        ref={containerRef}\n        layoutId={`dialog-${uniqueId}`}\n        className={cn('overflow-hidden', className)}\n        style={{\n          ...style,\n          willChange: 'transform, opacity', // GPU acceleration\n        }}\n        role='dialog'\n        aria-modal='true'\n        aria-labelledby={`dialog-title-${uniqueId}`}\n        aria-describedby={`dialog-description-${uniqueId}`}\n        initial={{ scale: 0.95, opacity: 0 }}\n        animate={{ scale: 1, opacity: 1 }}\n        exit={{ scale: 0.95, opacity: 0 }}\n        transition={{\n          type: 'spring',\n          damping: 25,\n          stiffness: 300,\n          mass: 0.8,\n        }}\n      >\n        {children}\n      </motion.div>\n    </>\n  );\n}\ntype DialogContainerProps = {\n  children: React.ReactNode;\n  className?: string;\n  overlayClassName?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogContainer({ children, className, overlayClassName }: DialogContainerProps) {\n  const { isOpen, setIsOpen, uniqueId } = useDialog();\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    const drawerWrapper = document.querySelectorAll('[drawer-wrapper]');\n\n    if (isOpen) {\n      document.body.classList.add('overflow-hidden');\n      drawerWrapper.forEach((wrapper) => wrapper?.classList.add('open'));\n    } else {\n      document.body.classList.remove('overflow-hidden');\n      drawerWrapper.forEach((wrapper) => wrapper?.classList.remove('open'));\n    }\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') {\n        setIsOpen(false);\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [isOpen]);\n\n  useEffect(() => {\n    setMounted(true);\n    return () => {\n      setMounted(false);\n    };\n  }, []);\n\n  if (!mounted) return null;\n\n  return createPortal(\n    <AnimatePresence initial={false} mode='wait'>\n      {isOpen && (\n        <>\n          <motion.div\n            key={`backdrop-${uniqueId}`}\n            data-lenis-prevent\n            className={cn(\n              'fixed inset-0 h-full z-50 w-full backdrop-blur-xl dark:bg-[radial-gradient(125%_125%_at_50%_10%,#050505_40%,#243aff_100%)] bg-[radial-gradient(125%_125%_at_50%_10%,#ffffff_40%,#243aff_100%)]',\n              overlayClassName\n            )}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{\n              duration: 0.2,\n              ease: [0.4, 0.0, 0.4, 1],\n            }}\n            onClick={() => setIsOpen(false)}\n          ></motion.div>\n          <motion.div\n            className={cn(`fixed inset-0 z-50 w-fit mx-auto`, className)}\n            style={{ willChange: 'transform' }} // GPU acceleration for transforms\n          >\n            {children}\n          </motion.div>\n        </>\n      )}\n    </AnimatePresence>,\n    document.body\n  );\n}\ntype DialogTitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogTitle({ children, className, style }: DialogTitleProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.h1\n      layoutId={`dialog-title-container-${uniqueId}`}\n      className={className}\n      style={style}\n      layout\n    >\n      {children}\n    </motion.h1>\n  );\n}\n\ntype DialogSubtitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogSubtitle({ children, className, style }: DialogSubtitleProps) {\n  const { uniqueId } = useDialog();\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\ntype DialogDescriptionProps = {\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 DialogDescription({\n  children,\n  className,\n  variants,\n  disableLayoutAnimation,\n}: DialogDescriptionProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.div\n      key={`dialog-description-${uniqueId}`}\n      layoutId={disableLayoutAnimation ? undefined : `dialog-description-content-${uniqueId}`}\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\ntype DialogImageProps = {\n  src: string;\n  alt: string;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogImage({ src, alt, className, style }: DialogImageProps) {\n  const { uniqueId } = useDialog();\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\ntype DialogCloseProps = {\n  children?: React.ReactNode;\n  className?: string;\n  variants?: {\n    initial: Variant;\n    animate: Variant;\n    exit: Variant;\n  };\n};\n\nfunction DialogClose({ children, className, variants }: DialogCloseProps) {\n  const { setIsOpen, uniqueId } = useDialog();\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 right-6 top-6 text-white', 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  Dialog,\n  DialogClose,\n  DialogContainer,\n  DialogContent,\n  DialogDescription,\n  DialogImage,\n  DialogSubtitle,\n  DialogTitle,\n  DialogTrigger,\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}