{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-sheet",
  "title": "Glass Sheet",
  "description": "A glass panel that slides in from an edge. Bottom sheets drag down to dismiss.",
  "dependencies": [
    "@radix-ui/react-dialog",
    "clsx"
  ],
  "registryDependencies": [
    "@glassyui/glass-panel"
  ],
  "files": [
    {
      "path": "registry/glassyui/glass-sheet.tsx",
      "content": "'use client';\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport clsx from 'clsx';\nimport { useRef } from 'react';\nimport { GlassPanel } from './glass-panel';\n\nexport const GlassSheet = DialogPrimitive.Root;\n\nexport const GlassSheetTrigger = DialogPrimitive.Trigger;\n\nexport const GlassSheetClose = DialogPrimitive.Close;\n\nexport const GlassSheetTitle = DialogPrimitive.Title;\n\nexport const GlassSheetDescription = DialogPrimitive.Description;\n\nexport type GlassSheetSide = 'top' | 'right' | 'bottom' | 'left';\n\nexport type GlassSheetContentProps = React.ComponentPropsWithoutRef<\n  typeof DialogPrimitive.Content\n> & {\n  closeLabel?: string;\n  side?: GlassSheetSide;\n  /** How thick the glass is, as a multiple of regular. See GlassSurface. */\n  thickness?: number;\n};\n\ntype SheetDrag = {\n  lastTime: number;\n  lastY: number;\n  offset: number;\n  pointerId: number;\n  startY: number;\n  velocity: number;\n};\n\n// A downward fling faster than this, in pixels per millisecond, dismisses the sheet.\nconst FLING_VELOCITY = 0.5;\n\nexport function GlassSheetContent({\n  children,\n  className,\n  closeLabel = 'Close sheet',\n  side = 'bottom',\n  ...props\n}: GlassSheetContentProps) {\n  const contentRef = useRef<HTMLDivElement>(null);\n  const closeRef = useRef<HTMLButtonElement>(null);\n  const dragRef = useRef<SheetDrag | null>(null);\n\n  const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n    if (event.button !== 0) return;\n\n    event.currentTarget.setPointerCapture(event.pointerId);\n    dragRef.current = {\n      lastTime: event.timeStamp,\n      lastY: event.clientY,\n      offset: 0,\n      pointerId: event.pointerId,\n      startY: event.clientY,\n      velocity: 0,\n    };\n  };\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    const drag = dragRef.current;\n    const content = contentRef.current;\n\n    if (!drag || !content || drag.pointerId !== event.pointerId) return;\n\n    const distance = event.clientY - drag.startY;\n    const elapsed = Math.max(1, event.timeStamp - drag.lastTime);\n\n    // Upward drags meet growing resistance instead of lifting the sheet off its edge.\n    drag.offset = distance >= 0 ? distance : -Math.sqrt(-distance) * 2;\n    drag.velocity = (event.clientY - drag.lastY) / elapsed;\n    drag.lastTime = event.timeStamp;\n    drag.lastY = event.clientY;\n    content.style.transform = `translate3d(0, ${drag.offset}px, 0)`;\n  };\n\n  const handlePointerEnd = (event: React.PointerEvent<HTMLDivElement>) => {\n    const drag = dragRef.current;\n    const content = contentRef.current;\n\n    if (!drag || !content || drag.pointerId !== event.pointerId) return;\n\n    dragRef.current = null;\n\n    const threshold = Math.min(120, content.offsetHeight * 0.25);\n\n    if (drag.offset > threshold || drag.velocity > FLING_VELOCITY) {\n      // The exit animation continues from where the finger let go.\n      closeRef.current?.click();\n\n      return;\n    }\n\n    content.style.transform = '';\n    content.animate(\n      [{ transform: `translate3d(0, ${drag.offset}px, 0)` }, { transform: 'translate3d(0, 0, 0)' }],\n      { duration: 420, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' }\n    );\n  };\n\n  return (\n    <DialogPrimitive.Portal>\n      <DialogPrimitive.Overlay className=\"lg-dialog-overlay\" />\n      <DialogPrimitive.Content {...props} asChild>\n        <GlassPanel\n          ref={contentRef}\n          data-side={side}\n          elevation=\"raised\"\n          lens\n          preset=\"prominent\"\n          refraction=\"none\"\n          variant=\"panel\"\n          className={clsx('lg-sheet-content', className)}\n        >\n          {side === 'bottom' ? (\n            <div\n              aria-hidden\n              className=\"lg-sheet-handle\"\n              onLostPointerCapture={handlePointerEnd}\n              onPointerCancel={handlePointerEnd}\n              onPointerDown={handlePointerDown}\n              onPointerMove={handlePointerMove}\n              onPointerUp={handlePointerEnd}\n            />\n          ) : null}\n          {children}\n          <DialogPrimitive.Close\n            ref={closeRef}\n            aria-label={closeLabel}\n            className=\"absolute right-4 top-4 flex h-9 w-9 items-center justify-center rounded-full border border-[var(--lg-border)] bg-[var(--lg-control-surface)] text-[var(--lg-muted-foreground)] hover:bg-[var(--lg-control-surface-hover)] hover:text-[var(--lg-foreground)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--lg-focus-ring)]\"\n          >\n            <svg viewBox=\"0 0 20 20\" className=\"h-4 w-4\" aria-hidden>\n              <path\n                d=\"M5 5l10 10m0-10L5 15\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.6\"\n                strokeLinecap=\"round\"\n              />\n            </svg>\n          </DialogPrimitive.Close>\n        </GlassPanel>\n      </DialogPrimitive.Content>\n    </DialogPrimitive.Portal>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-sheet.tsx"
    }
  ],
  "meta": {
    "sourceModules": [
      "sheet"
    ]
  },
  "type": "registry:ui"
}