{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "dependencies": [
    "@base-ui/react@1.8.0"
  ],
  "description": "A side panel that slides from the left, right or bottom edge, on Base UI Dialog.",
  "docs": "Two steps after installing: import the copied stylesheet once at your app entry — it lands at `styles/liquefy-ui.css` — or `@import` it from your global CSS *before* `tailwindcss`, so Tailwind utilities keep winning over component styles. Then wrap the tree in `<LiquefyProvider>`, which is where every component reads its tokens from. Every copied file already carries its own 'use client' directive. Full documentation: https://liquefy-ui.com/#/docs/installation",
  "files": [
    {
      "content": "'use client'\n\nimport { Dialog } from '@base-ui/react/dialog'\nimport { useRef, type HTMLAttributes, type PointerEvent, type ReactNode } from 'react'\nimport { XGlyph } from '@/lib/internal-glyphs'\nimport { LiquidSurface } from '@/components/ui/liquid-surface'\nimport { useLiquefyPortalContainer } from '@/lib/provider'\nimport { useLiquidStyles, type LiquidStyleProps } from '@/lib/styles-prop'\n\n// The old version drove a native <dialog> and hand-timed the exit animation off\n// an animationend listener with a 700ms fallback. Base UI keeps the popup mounted\n// for as long as the CSS animation runs, so the panel slides out on its own and\n// the focus trap, scroll lock and Escape handling come with it.\n\nexport type LiquidDrawerProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & LiquidStyleProps & {\n  children: ReactNode\n  closeLabel?: string\n  onOpenChange: (open: boolean) => void\n  open: boolean\n  side?: 'left' | 'right' | 'bottom'\n  /** Flick the panel towards its own edge to dismiss it. Touch and pen only. */\n  swipeToClose?: boolean\n  title?: ReactNode\n}\n\ntype Point = { x: number; y: number }\n\n/** How far the finger travels before a drag counts as a dismissing flick. */\nconst swipeDistance = 56\n\n/**\n * True when something under the finger can still scroll the way the flick would\n * scroll it. A sheet is dismissed from the top of its content, not from halfway\n * down it, and a row of chips that scrolls sideways owns its own drags.\n */\nconst canScrollAway = (target: Node, root: HTMLElement, side: 'left' | 'right' | 'bottom') => {\n  let node: Element | null = target instanceof Element ? target : target.parentElement\n\n  while (node && root.contains(node)) {\n    if (side === 'bottom' && node.scrollTop > 0) return true\n    if (side === 'right' && node.scrollLeft > 0) return true\n    if (side === 'left' && node.scrollLeft < node.scrollWidth - node.clientWidth) return true\n    node = node.parentElement\n  }\n\n  return false\n}\n\nexport const LiquidDrawer = ({\n  children,\n  className,\n  closeLabel = 'Close',\n  onOpenChange,\n  onPointerCancel,\n  onPointerDown,\n  onPointerUp,\n  open,\n  side = 'right',\n  style,\n  styles,\n  swipeToClose = true,\n  title,\n  ...props\n}: LiquidDrawerProps) => {\n  const portalContainer = useLiquefyPortalContainer()\n  const popupRef = useRef<HTMLDivElement>(null)\n  const origin = useRef<Point | null>(null)\n  const root = useLiquidStyles('lq-drawer', { className, style, styles })\n\n  // The gesture rides along with whatever the caller already listens for, rather\n  // than replacing it: `{...props}` would otherwise let one of the two win.\n  const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n    onPointerDown?.(event)\n    origin.current = null\n\n    // A mouse has the close button and the backdrop; dragging one across a panel\n    // is how text gets selected, not how a sheet gets dismissed.\n    if (!swipeToClose || event.pointerType === 'mouse' || !(event.target instanceof Node)) return\n\n    // Anything the panel opens — a select's listbox, a menu, a tooltip — is\n    // portaled out of the panel but still bubbles its events back through the\n    // React tree, so scrolling a list of options would otherwise read as a flick\n    // on the panel itself and throw away whatever was half filled in.\n    const panel = popupRef.current\n    if (!panel?.contains(event.target) || canScrollAway(event.target, panel, side)) return\n\n    origin.current = { x: event.clientX, y: event.clientY }\n  }\n\n  const handlePointerUp = (event: PointerEvent<HTMLDivElement>) => {\n    onPointerUp?.(event)\n\n    const start = origin.current\n    origin.current = null\n    if (!start) return\n\n    const x = event.clientX - start.x\n    const y = event.clientY - start.y\n    const towardsEdge = side === 'bottom' ? y : side === 'right' ? x : -x\n    const across = side === 'bottom' ? Math.abs(x) : Math.abs(y)\n\n    if (towardsEdge > swipeDistance && towardsEdge > across) onOpenChange(false)\n  }\n\n  return (\n    <Dialog.Root onOpenChange={(next) => onOpenChange(next)} open={open}>\n      <Dialog.Portal container={portalContainer ?? undefined}>\n        <Dialog.Backdrop className=\"lq-drawer__backdrop\" />\n        <Dialog.Popup\n          className={root.className}\n          data-side={side}\n          onPointerCancel={(event) => {\n            onPointerCancel?.(event)\n            origin.current = null\n          }}\n          onPointerDown={handlePointerDown}\n          onPointerUp={handlePointerUp}\n          ref={popupRef}\n          style={root.style}\n          {...props}\n        >\n          <LiquidSurface\n            className=\"lq-drawer__surface\"\n            interactive={false}\n            // The lens refracts what is behind the panel without hiding it, and\n            // it lands as an inline `backdrop-filter` the stylesheet cannot\n            // frost over — so the softening has to happen inside the lens. This\n            // is the same figure as `--lq-blur` on the panel, which is what the\n            // browsers without a lens fall back to.\n            lensBlur={20}\n            radius={side === 'bottom' ? '26px 26px 0 0' : 0}\n          >\n            <div className=\"lq-drawer__header\">\n              {title && <Dialog.Title>{title}</Dialog.Title>}\n              <Dialog.Close aria-label={closeLabel} className=\"lq-drawer__close\">\n                <XGlyph size={14} />\n              </Dialog.Close>\n            </div>\n            <div className=\"lq-drawer__body\">{children}</div>\n          </LiquidSurface>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  )\n}\n",
      "path": "registry/liquid-drawer.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "liquid-drawer",
  "registryDependencies": [
    "https://liquefy-ui.com/r/liquefy-ui.json",
    "https://liquefy-ui.com/r/internal-glyphs.json",
    "https://liquefy-ui.com/r/liquid-surface.json",
    "https://liquefy-ui.com/r/provider.json",
    "https://liquefy-ui.com/r/styles-prop.json"
  ],
  "title": "LiquidDrawer",
  "type": "registry:ui"
}
