{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "top-sheet",
  "type": "registry:ui",
  "title": "Top Sheet",
  "description": "A customizable component for creating elegant, slide-out panels from the top of the screen.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/top-sheet.tsx",
      "content": "'use client';\nimport React, {\n  createContext,\n  useContext,\n  useState,\n  useRef,\n  useCallback,\n  useEffect,\n  forwardRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n  motion,\n  useAnimation,\n  PanInfo,\n  useMotionValue,\n  useTransform,\n} from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ninterface TopSheetContextValue {\n  isOpen: boolean;\n  onOpenChange: (open: boolean) => void;\n  contentProps: {\n    height: string;\n    className: string;\n    closeThreshold: number;\n  };\n}\n\nconst TopSheetContext = createContext<TopSheetContextValue | null>(null);\n\nconst useTopSheetContext = () => {\n  const context = useContext(TopSheetContext);\n  if (!context) {\n    throw new Error(\n      'TopSheet compound components must be used within TopSheet',\n    );\n  }\n  return context;\n};\n\ninterface TopSheetRootProps {\n  children: React.ReactNode;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  defaultOpen?: boolean;\n  className?: string;\n}\n\nconst TopSheetRoot = ({\n  children,\n  open,\n  onOpenChange,\n  defaultOpen,\n  className,\n}: TopSheetRootProps) => {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);\n\n  const isControlled = open !== undefined;\n  const isOpen = isControlled ? open : internalOpen;\n\n  const handleOpenChange = useCallback(\n    (newOpen: boolean) => {\n      if (onOpenChange) {\n        onOpenChange(newOpen);\n      }\n      if (!isControlled) {\n        setInternalOpen(newOpen);\n      }\n    },\n    [onOpenChange, isControlled],\n  );\n\n  const contentProps = {\n    height: '55vh',\n    className: className || '',\n    closeThreshold: 0.3,\n  };\n\n  return (\n    <TopSheetContext.Provider\n      value={{ isOpen, onOpenChange: handleOpenChange, contentProps }}\n    >\n      {children}\n    </TopSheetContext.Provider>\n  );\n};\n\ninterface TopSheetPortalProps {\n  children: React.ReactNode;\n  container?: HTMLElement;\n  className?: string;\n}\n\nconst TopSheetPortal = ({\n  children,\n  container,\n  className,\n}: TopSheetPortalProps) => {\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    Promise.resolve().then(() => setMounted(true));\n  }, []);\n\n  if (!mounted || typeof document === 'undefined') {\n    return null;\n  }\n\n  const portalContent = className ? (\n    <div className={className}>{children}</div>\n  ) : (\n    children\n  );\n\n  return createPortal(portalContent, container || document.body);\n};\n\ninterface TopSheetOverlayProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string;\n}\n\nconst TopSheetOverlay = forwardRef<HTMLDivElement, TopSheetOverlayProps>(\n  ({ className, onClick, ...props }, ref) => {\n    const { isOpen, onOpenChange } = useTopSheetContext();\n\n    const handleClick = useCallback(\n      (e: React.MouseEvent<HTMLDivElement>) => {\n        if (e.target === e.currentTarget) {\n          onOpenChange(false);\n        }\n        onClick?.(e);\n      },\n      [onOpenChange, onClick],\n    );\n\n    return (\n      <motion.div\n        ref={ref}\n        initial={{ opacity: 0 }}\n        animate={{ opacity: isOpen ? 1 : 0 }}\n        transition={{ duration: 0.2, ease: 'easeOut' }}\n        onClick={handleClick}\n        className={cn(\n          'absolute inset-0 bg-black/20 backdrop-blur-xs',\n          className,\n        )}\n        style={{ pointerEvents: isOpen ? 'auto' : 'none' }}\n      />\n    );\n  },\n);\nTopSheetOverlay.displayName = 'TopSheetOverlay';\n\ninterface TopSheetTriggerProps {\n  asChild?: boolean;\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetTrigger = ({\n  asChild,\n  children,\n  className,\n}: TopSheetTriggerProps) => {\n  const { onOpenChange } = useTopSheetContext();\n\n  const handleClick = () => {\n    onOpenChange(true);\n  };\n\n  if (asChild && React.isValidElement(children)) {\n    const childProps = children.props as {\n      className?: string;\n      onClick?: (e: React.MouseEvent) => void;\n    };\n\n    return React.cloneElement(children, {\n      className: cn(childProps.className, className),\n      onClick: (e: React.MouseEvent) => {\n        childProps.onClick?.(e);\n        handleClick();\n      },\n    } as Partial<typeof childProps>);\n  }\n\n  return (\n    <button onClick={handleClick} type='button' className={cn('', className)}>\n      {children}\n    </button>\n  );\n};\n\ninterface TopSheetContentProps {\n  children?: React.ReactNode;\n  height?: string;\n  className?: string;\n  closeThreshold?: number;\n}\n\nconst TopSheetContent = ({\n  children,\n  height = '55vh',\n  className = '',\n  closeThreshold = 0.3,\n}: TopSheetContentProps) => {\n  const { isOpen, onOpenChange } = useTopSheetContext();\n  const controls = useAnimation();\n  const y = useMotionValue(0);\n  useTransform(y, [-100, 0], [0, 1]);\n  const overlayRef = useRef<HTMLDivElement>(null);\n  const [sheetHeight, setSheetHeight] = useState(0);\n\n  const onClose = useCallback(() => onOpenChange(false), [onOpenChange]);\n\n  const calculateHeight = useCallback(() => {\n    if (typeof window !== 'undefined') {\n      const vh = window.innerHeight;\n      const vw = window.innerWidth;\n\n      let calculatedHeight;\n      if (vw <= 640) {\n        calculatedHeight = vh * 0.6;\n      } else if (vw <= 1024) {\n        calculatedHeight = vh * 0.55;\n      } else {\n        calculatedHeight = vh * 0.5;\n      }\n\n      if (height.includes('vh')) {\n        calculatedHeight = (parseInt(height) / 100) * vh;\n      } else if (height.includes('px')) {\n        calculatedHeight = parseInt(height);\n      }\n\n      return Math.min(calculatedHeight, vh * 0.8);\n    }\n    return 400;\n  }, [height]);\n\n  useEffect(() => {\n    const updateHeight = () => {\n      setSheetHeight(calculateHeight());\n    };\n\n    updateHeight();\n    window.addEventListener('resize', updateHeight);\n\n    return () => window.removeEventListener('resize', updateHeight);\n  }, [calculateHeight]);\n\n  useEffect(() => {\n    if (isOpen) {\n      document.body.style.overflow = 'hidden';\n      controls.start({\n        y: 0,\n        transition: {\n          type: 'spring',\n          stiffness: 400,\n          damping: 40,\n          mass: 0.8,\n        },\n      });\n    } else {\n      document.body.style.overflow = '';\n      controls.start({\n        y: -(sheetHeight + 50),\n        transition: {\n          type: 'tween',\n          ease: [0.25, 0.46, 0.45, 0.94],\n          duration: 0.3,\n        },\n      });\n    }\n    return () => {\n      document.body.style.overflow = '';\n    };\n  }, [isOpen, controls, sheetHeight]);\n\n  useEffect(() => {\n    const handleEscape = (e: KeyboardEvent) => {\n      if (e.key === 'Escape' && isOpen) {\n        onClose();\n      }\n    };\n    if (isOpen) {\n      document.addEventListener('keydown', handleEscape);\n    }\n    return () => {\n      document.removeEventListener('keydown', handleEscape);\n    };\n  }, [isOpen, onClose]);\n\n  const handleDragEnd = useCallback(\n    (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {\n      const shouldClose =\n        info.offset.y < -(sheetHeight * closeThreshold) ||\n        info.velocity.y < -800;\n      if (shouldClose) {\n        onClose();\n      } else {\n        controls.start({\n          y: 0,\n          transition: {\n            type: 'spring',\n            stiffness: 500,\n            damping: 40,\n          },\n        });\n      }\n    },\n    [controls, onClose, closeThreshold, sheetHeight],\n  );\n\n  const handleOverlayClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === overlayRef.current) {\n        onClose();\n      }\n    },\n    [onClose],\n  );\n\n  if (sheetHeight === 0) return null;\n\n  return (\n    <TopSheetPortal>\n      <div\n        className={cn('fixed inset-0 z-999', !isOpen && 'pointer-events-none')}\n      >\n        <motion.div\n          ref={overlayRef}\n          initial={{ opacity: 0 }}\n          animate={{ opacity: isOpen ? 1 : 0 }}\n          transition={{ duration: 0.2, ease: 'easeOut' }}\n          onClick={handleOverlayClick}\n          className='absolute inset-0 bg-black/20 backdrop-blur-xs'\n          style={{ pointerEvents: isOpen ? 'auto' : 'none' }}\n        />\n        <motion.div\n          drag='y'\n          dragConstraints={{ top: -sheetHeight, bottom: 0 }}\n          dragElastic={{ top: 0.1, bottom: 0 }}\n          dragMomentum={false}\n          onDragEnd={handleDragEnd}\n          animate={controls}\n          initial={{ y: -(sheetHeight + 50) }}\n          className={cn(\n            'absolute left-0 right-0 top-0 w-full bg-white dark:bg-[#0A0A0A] shadow-2xl',\n            className,\n          )}\n          style={{\n            height: sheetHeight,\n            borderBottomLeftRadius: '16px',\n            borderBottomRightRadius: '16px',\n            display: 'flex',\n            flexDirection: 'column',\n          }}\n          onDrag={undefined}\n        >\n          <div className='flex-1 overflow-hidden'>\n            <div\n              className='h-full overflow-y-auto px-4 pt-6 pb-10 scrollbar-hide'\n              style={{\n                scrollbarWidth: 'none',\n                msOverflowStyle: 'none',\n              }}\n            >\n              {children}\n            </div>\n          </div>\n\n          <div className='flex justify-center pb-4 pt-1'>\n            <div className='h-2 w-16 rounded-full bg-gray-300 dark:bg-gray-600 cursor-grab active:cursor-grabbing' />\n          </div>\n        </motion.div>\n      </div>\n    </TopSheetPortal>\n  );\n};\n\ninterface TopSheetHeaderProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetHeader = ({ children, className }: TopSheetHeaderProps) => {\n  return (\n    <div\n      className={cn(\n        'flex flex-col space-y-1.5 text-center sm:text-center pb-4',\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\ninterface TopSheetTitleProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetTitle = ({ children, className }: TopSheetTitleProps) => {\n  return (\n    <h3\n      className={cn(\n        'text-lg font-semibold leading-none tracking-tight',\n        className,\n      )}\n    >\n      {children}\n    </h3>\n  );\n};\n\ninterface TopSheetDescriptionProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetDescription = ({\n  children,\n  className,\n}: TopSheetDescriptionProps) => {\n  return (\n    <p className={cn('text-sm text-gray-600 dark:text-gray-400', className)}>\n      {children}\n    </p>\n  );\n};\n\ninterface TopSheetFooterProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetFooter = ({ children, className }: TopSheetFooterProps) => {\n  return (\n    <div\n      className={cn(\n        'flex flex-col-reverse sm:flex-row sm:justify-center sm:space-x-2 pt-4',\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\ninterface TopSheetCloseProps {\n  asChild?: boolean;\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TopSheetClose = ({\n  asChild,\n  children,\n  className,\n}: TopSheetCloseProps) => {\n  const { onOpenChange } = useTopSheetContext();\n\n  const handleClick = () => {\n    onOpenChange(false);\n  };\n\n  if (asChild && React.isValidElement(children)) {\n    const childProps = children.props as {\n      className?: string;\n      onClick?: (e: React.MouseEvent) => void;\n    };\n\n    return React.cloneElement(children, {\n      className: cn(childProps.className, className),\n      onClick: (e: React.MouseEvent) => {\n        childProps.onClick?.(e);\n        handleClick();\n      },\n    } as Partial<typeof childProps>);\n  }\n\n  return (\n    <button onClick={handleClick} type='button' className={cn('', className)}>\n      {children}\n    </button>\n  );\n};\n\nconst TopSheet = TopSheetRoot;\n\nexport {\n  TopSheet,\n  TopSheetPortal,\n  TopSheetOverlay,\n  TopSheetTrigger,\n  TopSheetClose,\n  TopSheetContent,\n  TopSheetHeader,\n  TopSheetFooter,\n  TopSheetTitle,\n  TopSheetDescription,\n};\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
