{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu",
  "title": "Context Menu",
  "description": "A right-click menu with proximity highlight and keyboard navigation.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/context-menu.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\nimport {\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ReactNode,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { cn } from '@/lib/utils'\nimport { springs } from '@/lib/motion-tokens'\nimport { useProximityHighlight, ProximityHighlight } from '@/lib/hooks/use-proximity-highlight'\n\nexport interface ContextMenuItem {\n  label: string\n  icon?: ReactNode\n  shortcut?: string\n  onSelect?: () => void\n  destructive?: boolean\n  disabled?: boolean\n  separatorAfter?: boolean\n}\n\ninterface ContextMenuContextValue {\n  open: boolean\n  setOpen: (next: boolean) => void\n  items: ContextMenuItem[]\n  onOpenChange?: (open: boolean) => void\n}\n\nconst ContextMenuContext = createContext<ContextMenuContextValue | null>(null)\n\nfunction useContextMenuCtx(componentName: string) {\n  const ctx = useContext(ContextMenuContext)\n  if (!ctx) throw new Error(`${componentName} must be used within ContextMenu`)\n  return ctx\n}\n\nexport interface ContextMenuProps {\n  children: ReactNode\n  items: ContextMenuItem[]\n  disabled?: boolean\n  onOpenChange?: (open: boolean) => void\n  className?: string\n  menuClassName?: string\n}\n\nexport const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(\n  ({ children, items, disabled = false, onOpenChange, className, menuClassName }, ref) => {\n    const [open, setOpen] = useState(false)\n    const [position, setPosition] = useState({ x: 0, y: 0 })\n    const triggerRef = useRef<HTMLDivElement>(null)\n    const contentRef = useRef<HTMLDivElement>(null)\n    const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n    const longPressMovedRef = useRef(false)\n    const previousFocusRef = useRef<HTMLElement | null>(null)\n\n    const setOpenState = useCallback(\n      (next: boolean) => {\n        setOpen(next)\n        onOpenChange?.(next)\n      },\n      [onOpenChange],\n    )\n\n    const handleContextMenu = useCallback(\n      (e: React.MouseEvent) => {\n        if (disabled) return\n        e.preventDefault()\n        previousFocusRef.current = document.activeElement as HTMLElement | null\n        setPosition({ x: e.clientX, y: e.clientY })\n        setOpenState(true)\n      },\n      [disabled, setOpenState],\n    )\n\n    const handleTouchStart = useCallback(\n      (e: React.TouchEvent) => {\n        if (disabled) return\n        longPressMovedRef.current = false\n        const touch = e.touches[0]\n        longPressTimerRef.current = setTimeout(() => {\n          if (!longPressMovedRef.current) {\n            previousFocusRef.current = document.activeElement as HTMLElement | null\n            setPosition({ x: touch.clientX, y: touch.clientY })\n            setOpenState(true)\n          }\n        }, 500)\n      },\n      [disabled, setOpenState],\n    )\n\n    const handleTouchMove = useCallback(() => {\n      longPressMovedRef.current = true\n      if (longPressTimerRef.current) {\n        clearTimeout(longPressTimerRef.current)\n        longPressTimerRef.current = null\n      }\n    }, [])\n\n    const handleTouchEnd = useCallback(() => {\n      if (longPressTimerRef.current) {\n        clearTimeout(longPressTimerRef.current)\n        longPressTimerRef.current = null\n      }\n    }, [])\n\n    const handleKeyDown = useCallback(\n      (e: React.KeyboardEvent) => {\n        if (disabled) return\n        if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n          e.preventDefault()\n          previousFocusRef.current = document.activeElement as HTMLElement | null\n          const rect = triggerRef.current?.getBoundingClientRect()\n          if (rect) {\n            setPosition({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 })\n          }\n          setOpenState(true)\n        }\n      },\n      [disabled, setOpenState],\n    )\n\n    useEffect(() => {\n      if (!open) return\n      const handlePointerDown = (e: PointerEvent) => {\n        if (contentRef.current && !contentRef.current.contains(e.target as Node)) {\n          setOpenState(false)\n        }\n      }\n      const handleEscape = (e: KeyboardEvent) => {\n        if (e.key === 'Escape') {\n          e.preventDefault()\n          setOpenState(false)\n          previousFocusRef.current?.focus()\n        }\n      }\n      document.addEventListener('pointerdown', handlePointerDown)\n      document.addEventListener('keydown', handleEscape)\n      return () => {\n        document.removeEventListener('pointerdown', handlePointerDown)\n        document.removeEventListener('keydown', handleEscape)\n      }\n    }, [open, setOpenState])\n\n    useEffect(() => {\n      return () => {\n        if (longPressTimerRef.current) {\n          clearTimeout(longPressTimerRef.current)\n        }\n      }\n    }, [])\n\n    const ctx = useMemo<ContextMenuContextValue>(\n      () => ({ open, setOpen: setOpenState, items, onOpenChange }),\n      [open, setOpenState, items, onOpenChange],\n    )\n\n    return (\n      <ContextMenuContext.Provider value={ctx}>\n        <div\n          ref={(node) => {\n            ;(triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = node\n            if (typeof ref === 'function') ref(node)\n            else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n          }}\n          onContextMenu={handleContextMenu}\n          onTouchStart={handleTouchStart}\n          onTouchMove={handleTouchMove}\n          onTouchEnd={handleTouchEnd}\n          onKeyDown={handleKeyDown}\n          className={cn('inline-block', className)}\n        >\n          {children}\n        </div>\n        {open && (\n          <ContextMenuPanel ref={contentRef} position={position} className={menuClassName} />\n        )}\n      </ContextMenuContext.Provider>\n    )\n  },\n)\nContextMenu.displayName = 'ContextMenu'\n\ninterface ContextMenuPanelProps {\n  position: { x: number; y: number }\n  className?: string\n}\n\nconst ContextMenuPanel = forwardRef<HTMLDivElement, ContextMenuPanelProps>(\n  ({ position, className }, ref) => {\n    const { items, setOpen } = useContextMenuCtx('ContextMenuPanel')\n    const panelRef = useRef<HTMLDivElement | null>(null)\n    const reduceMotion = useReducedMotion()\n    const menuId = useId()\n\n    const { registerItem, handlers, highlightX, highlightSize, highlightOpacity, axis } =\n      useProximityHighlight(panelRef, { axis: 'y' })\n\n    const indexRef = useRef(0)\n    const nextIndex = useCallback(() => {\n      const idx = indexRef.current\n      indexRef.current += 1\n      return idx\n    }, [])\n\n    const [adjustedPos, setAdjustedPos] = useState(position)\n    useEffect(() => {\n      const panel = panelRef.current\n      if (!panel) return\n      const rect = panel.getBoundingClientRect()\n      const vw = window.innerWidth\n      const vh = window.innerHeight\n      const margin = 8\n\n      let x = position.x\n      let y = position.y\n\n      if (x + rect.width > vw - margin) x = vw - rect.width - margin\n      if (y + rect.height > vh - margin) y = vh - rect.height - margin\n      if (x < margin) x = margin\n      if (y < margin) y = margin\n\n      setAdjustedPos({ x, y })\n    }, [position])\n\n    const transformOrigin = useMemo(() => {\n      if (typeof window === 'undefined') return 'top left'\n      const cx = position.x\n      const cy = position.y\n      const vw = window.innerWidth\n      const vh = window.innerHeight\n      const vCenterX = vw / 2\n      const vCenterY = vh / 2\n\n      const horizontal = cx < vCenterX ? 'left' : 'right'\n      const vertical = cy < vCenterY ? 'top' : 'bottom'\n      return `${vertical} ${horizontal}`\n    }, [position])\n\n    useEffect(() => {\n      const panel = panelRef.current\n      if (!panel) return\n\n      const firstItem = panel.querySelector<HTMLElement>('[role=\"menuitem\"]:not([data-disabled])')\n      firstItem?.focus()\n\n      const handleKeyDown = (e: KeyboardEvent) => {\n        if (!panel.contains(document.activeElement)) return\n        const menuItems = panel.querySelectorAll<HTMLElement>(\n          '[role=\"menuitem\"]:not([data-disabled])',\n        )\n        if (!menuItems.length) return\n        const count = menuItems.length\n        const currentIdx = Array.from(menuItems).indexOf(document.activeElement as HTMLElement)\n\n        if (e.key === 'ArrowDown') {\n          e.preventDefault()\n          const next = currentIdx < 0 ? 0 : (currentIdx + 1) % count\n          menuItems[next]?.focus()\n        } else if (e.key === 'ArrowUp') {\n          e.preventDefault()\n          const next = currentIdx < 0 ? count - 1 : (currentIdx - 1 + count) % count\n          menuItems[next]?.focus()\n        } else if (e.key === 'Home') {\n          e.preventDefault()\n          menuItems[0]?.focus()\n        } else if (e.key === 'End') {\n          e.preventDefault()\n          menuItems[count - 1]?.focus()\n        } else if (e.key === 'Tab') {\n          setOpen(false)\n        } else if (e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && e.key.trim()) {\n          const query = e.key.toLowerCase()\n          const match = Array.from(menuItems).findIndex((el) => {\n            const text = (el.getAttribute('data-text-value') ?? el.textContent ?? '').toLowerCase()\n            return text.startsWith(query)\n          })\n          if (match >= 0) {\n            e.preventDefault()\n            menuItems[match]?.focus()\n          }\n        }\n      }\n      document.addEventListener('keydown', handleKeyDown)\n      return () => document.removeEventListener('keydown', handleKeyDown)\n    }, [setOpen])\n\n    const setRefs = useCallback(\n      (el: HTMLDivElement | null) => {\n        ;(panelRef as React.MutableRefObject<HTMLDivElement | null>).current = el\n        if (typeof ref === 'function') ref(el)\n        else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el\n      },\n      [ref],\n    )\n\n    const panelContent = (\n      <motion.div\n        ref={setRefs}\n        id={menuId}\n        role=\"menu\"\n        data-context-menu-content\n        className={cn(\n          'fixed z-300 min-w-48 max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-lg squircle-corners border border-(--color-border) bg-(--color-bg) p-1.5 outline-none',\n          'shadow-[0_14px_34px_-22px_rgba(0,0,0,0.15)]',\n          className,\n        )}\n        style={{\n          left: adjustedPos.x,\n          top: adjustedPos.y,\n          transformOrigin,\n        }}\n        initial={reduceMotion ? { opacity: 1 } : { opacity: 0, scale: 0.95 }}\n        animate={reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1 }}\n        exit={reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.95 }}\n        transition={\n          reduceMotion ? { duration: 0 } : { ...springs.settle, opacity: { duration: 0.12 } }\n        }\n        tabIndex={-1}\n        {...handlers}\n      >\n        <ProximityHighlight\n          highlightX={highlightX}\n          highlightSize={highlightSize}\n          highlightOpacity={highlightOpacity}\n          axis={axis}\n          className=\"mx-1.5 rounded-md squircle-corners bg-(--color-surface-2)\"\n        />\n        {items.map((item, i) => {\n          if (item.separatorAfter) {\n            return (\n              <div key={`item-${i}`}>\n                <ContextMenuItemComponent\n                  item={item}\n                  nextIndex={nextIndex}\n                  registerItem={registerItem}\n                />\n                <div\n                  role=\"separator\"\n                  aria-hidden=\"true\"\n                  className=\"my-1 h-px bg-(--color-border)\"\n                />\n              </div>\n            )\n          }\n          return (\n            <ContextMenuItemComponent\n              key={`item-${i}`}\n              item={item}\n              nextIndex={nextIndex}\n              registerItem={registerItem}\n            />\n          )\n        })}\n      </motion.div>\n    )\n\n    return createPortal(panelContent, document.body)\n  },\n)\nContextMenuPanel.displayName = 'ContextMenuPanel'\n\nfunction ContextMenuItemComponent({\n  item,\n  nextIndex,\n  registerItem,\n}: {\n  item: ContextMenuItem\n  nextIndex: () => number\n  registerItem: (index: number, el: HTMLElement | null) => void\n}) {\n  const { setOpen } = useContextMenuCtx('ContextMenuItem')\n  const itemRef = useRef<HTMLButtonElement | null>(null)\n  const [idx] = useState(() => nextIndex())\n\n  useEffect(() => {\n    const el = itemRef.current\n    if (idx < 0 || !el) return\n    registerItem(idx, el)\n    return () => registerItem(idx, null)\n  }, [idx, registerItem])\n\n  return (\n    <button\n      ref={itemRef}\n      type=\"button\"\n      role=\"menuitem\"\n      data-text-value={item.label}\n      data-disabled={item.disabled ? '' : undefined}\n      tabIndex={-1}\n      disabled={item.disabled}\n      className={cn(\n        'relative flex min-h-11 w-full cursor-default select-none items-center gap-3 rounded-md squircle-corners px-3 py-2.5 text-left text-sm outline-none transition-colors duration-(--motion-dur-fast) motion-reduce:transition-none',\n        'data-disabled:pointer-events-none data-disabled:opacity-50',\n        item.destructive\n          ? 'text-(--color-error) focus-visible:text-(--color-error)'\n          : 'text-(--color-fg)',\n      )}\n      onClick={() => {\n        if (!item.disabled) {\n          item.onSelect?.()\n          setOpen(false)\n        }\n      }}\n    >\n      {item.icon && (\n        <span className=\"relative z-10 shrink-0 [&_svg]:size-4\" aria-hidden=\"true\">\n          {item.icon}\n        </span>\n      )}\n      <span className=\"relative z-10 flex min-w-0 flex-1 items-center truncate\">{item.label}</span>\n      {item.shortcut && (\n        <kbd\n          className=\"relative z-10 shrink-0 rounded border border-(--color-border) bg-(--color-surface) px-1.5 py-0.5 text-[10px] font-medium text-(--color-subtle)\"\n          aria-hidden=\"true\"\n        >\n          {item.shortcut}\n        </kbd>\n      )}\n    </button>\n  )\n}\n\nexport function ContextMenuPreview() {\n  return (\n    <div\n      className=\"flex h-full w-full items-center justify-center p-6\"\n      style={{ backgroundColor: 'var(--color-surface)' }}\n    >\n      <ContextMenu\n        items={[\n          { label: 'Copy', shortcut: '⌘C' },\n          { label: 'Rename', separatorAfter: true },\n          { label: 'Delete', destructive: true },\n        ]}\n      >\n        <div className=\"flex h-32 w-48 items-center justify-center rounded-lg squircle-corners border border-(--color-border) bg-(--color-bg) text-sm text-(--color-muted)\">\n          Right-click me\n        </div>\n      </ContextMenu>\n    </div>\n  )\n}\n"
    },
    {
      "path": "lib/hooks/use-proximity-highlight.tsx",
      "type": "registry:lib",
      "content": "'use client'\n\nimport { useRef, useState, useCallback, useEffect, type RefObject } from 'react'\nimport {\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  type MotionValue,\n  type MotionStyle,\n} from 'motion/react'\nimport { springs } from '@/lib/motion-tokens'\n\nexport interface ItemRect {\n  top: number\n  height: number\n  left: number\n  width: number\n}\n\ninterface UseProximityHighlightOptions {\n  axis?: 'x' | 'y'\n  spring?: { stiffness: number; damping: number; mass: number }\n}\n\ninterface UseProximityHighlightReturn {\n  activeIndex: number | null\n  setActiveIndex: (index: number | null) => void\n  itemRects: ItemRect[]\n  sessionRef: RefObject<number>\n  handlers: {\n    onMouseMove: (e: React.MouseEvent) => void\n    onMouseEnter: () => void\n    onMouseLeave: () => void\n  }\n  registerItem: (index: number, element: HTMLElement | null) => void\n  measureItems: () => void\n  highlightX: ReturnType<typeof useSpring>\n  highlightSize: MotionValue<number>\n  highlightOpacity: ReturnType<typeof useSpring>\n  axis: 'x' | 'y'\n}\n\nexport function useProximityHighlight<T extends HTMLElement>(\n  containerRef: RefObject<T | null>,\n  options: UseProximityHighlightOptions = {},\n): UseProximityHighlightReturn {\n  const { axis = 'y', spring: springConfig } = options\n  const reduceMotion = useReducedMotion()\n\n  const itemsRef = useRef(new Map<number, HTMLElement>())\n  const [activeIndex, setActiveIndexRaw] = useState<number | null>(null)\n  const activeIndexRef = useRef<number | null>(null)\n  const [itemRects, setItemRects] = useState<ItemRect[]>([])\n  const itemRectsRef = useRef<ItemRect[]>([])\n  const sessionRef = useRef(0)\n  const rafIdRef = useRef<number | null>(null)\n  const remeasureRafIdRef = useRef<number | null>(null)\n  const updateHighlightRef = useRef<(index: number | null) => void>(() => {})\n\n  const rawX = useMotionValue(0)\n  const highlightSize = useMotionValue(0)\n  const rawOpacity = useMotionValue(0)\n\n  const springOpts = reduceMotion ? { duration: 0 } : (springConfig ?? springs.settle)\n\n  const highlightX = useSpring(rawX, springOpts)\n  const highlightOpacity = useSpring(rawOpacity, springOpts)\n\n  const measureItems = useCallback(() => {\n    const container = containerRef.current\n    if (!container) return\n    const rects: ItemRect[] = []\n    itemsRef.current.forEach((element, index) => {\n      rects[index] = {\n        top: element.offsetTop,\n        height: element.offsetHeight,\n        left: element.offsetLeft,\n        width: element.offsetWidth,\n      }\n    })\n    itemRectsRef.current = rects\n    setItemRects(rects)\n  }, [containerRef])\n\n  const registerItem = useCallback(\n    (index: number, element: HTMLElement | null) => {\n      if (element) {\n        itemsRef.current.set(index, element)\n      } else {\n        itemsRef.current.delete(index)\n      }\n      if (remeasureRafIdRef.current !== null) {\n        cancelAnimationFrame(remeasureRafIdRef.current)\n      }\n      remeasureRafIdRef.current = requestAnimationFrame(() => {\n        remeasureRafIdRef.current = null\n        measureItems()\n      })\n    },\n    [measureItems],\n  )\n\n  const updateHighlight = useCallback(\n    (index: number | null) => {\n      if (index === null || !itemRectsRef.current[index]) {\n        if (reduceMotion) {\n          rawOpacity.jump(0)\n        } else {\n          rawOpacity.set(0)\n        }\n        return\n      }\n      const r = itemRectsRef.current[index]\n      if (axis === 'y') {\n        if (reduceMotion) {\n          rawX.jump(r.top)\n          highlightSize.jump(r.height)\n          rawOpacity.jump(1)\n        } else {\n          rawX.set(r.top)\n          highlightSize.jump(r.height)\n          rawOpacity.set(1)\n        }\n      } else {\n        if (reduceMotion) {\n          rawX.jump(r.left)\n          highlightSize.jump(r.width)\n          rawOpacity.jump(1)\n        } else {\n          rawX.set(r.left)\n          highlightSize.jump(r.width)\n          rawOpacity.set(1)\n        }\n      }\n    },\n    [axis, reduceMotion, rawX, highlightSize, rawOpacity],\n  )\n\n  useEffect(() => {\n    updateHighlightRef.current = updateHighlight\n  })\n\n  const setActiveIndex = useCallback((index: number | null) => {\n    setActiveIndexRaw(index)\n    updateHighlightRef.current(index)\n  }, [])\n\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent) => {\n      const mouseX = e.clientX\n      const mouseY = e.clientY\n\n      if (rafIdRef.current !== null) {\n        cancelAnimationFrame(rafIdRef.current)\n      }\n\n      rafIdRef.current = requestAnimationFrame(() => {\n        rafIdRef.current = null\n        const container = containerRef.current\n        if (!container) return\n\n        const containerRect = container.getBoundingClientRect()\n        const mousePos = axis === 'x' ? mouseX : mouseY\n\n        let closestIndex: number | null = null\n        let closestDistance = Infinity\n        let containingIndex: number | null = null\n\n        const rects = itemRectsRef.current\n        const scrollOffset = axis === 'x' ? container.scrollLeft : container.scrollTop\n        const borderOffset = axis === 'x' ? container.clientLeft : container.clientTop\n        const containerEdge = axis === 'x' ? containerRect.left : containerRect.top\n\n        const layoutSize = axis === 'x' ? container.offsetWidth : container.offsetHeight\n        const visualSize = axis === 'x' ? containerRect.width : containerRect.height\n        const scale = layoutSize > 0 ? visualSize / layoutSize : 1\n\n        for (let index = 0; index < rects.length; index++) {\n          const r = rects[index]\n          if (!r) continue\n\n          const contentPos = axis === 'x' ? r.left : r.top\n          const itemStart = containerEdge + (borderOffset + contentPos - scrollOffset) * scale\n          const itemSize = (axis === 'x' ? r.width : r.height) * scale\n          const itemEnd = itemStart + itemSize\n\n          if (mousePos >= itemStart && mousePos <= itemEnd) {\n            containingIndex = index\n          }\n\n          const itemCenter = itemStart + itemSize / 2\n          const distance = Math.abs(mousePos - itemCenter)\n\n          if (distance < closestDistance) {\n            closestDistance = distance\n            closestIndex = index\n          }\n        }\n\n        const next = containingIndex ?? closestIndex\n        if (next !== activeIndexRef.current) {\n          activeIndexRef.current = next\n          setActiveIndex(next)\n        }\n      })\n    },\n    [axis, containerRef, setActiveIndex],\n  )\n\n  const handleMouseEnter = useCallback(() => {\n    sessionRef.current += 1\n  }, [])\n\n  const handleMouseLeave = useCallback(() => {\n    if (rafIdRef.current !== null) {\n      cancelAnimationFrame(rafIdRef.current)\n      rafIdRef.current = null\n    }\n    activeIndexRef.current = null\n    setActiveIndex(null)\n  }, [setActiveIndex])\n\n  useEffect(() => {\n    return () => {\n      if (rafIdRef.current !== null) {\n        cancelAnimationFrame(rafIdRef.current)\n      }\n      if (remeasureRafIdRef.current !== null) {\n        cancelAnimationFrame(remeasureRafIdRef.current)\n      }\n    }\n  }, [])\n\n  return {\n    activeIndex,\n    setActiveIndex,\n    itemRects,\n    sessionRef,\n    handlers: {\n      onMouseMove: handleMouseMove,\n      onMouseEnter: handleMouseEnter,\n      onMouseLeave: handleMouseLeave,\n    },\n    registerItem,\n    measureItems,\n    highlightX,\n    highlightSize,\n    highlightOpacity,\n    axis,\n  }\n}\n\nexport interface ProximityHighlightProps {\n  highlightX: ReturnType<typeof useSpring>\n  highlightSize: MotionValue<number>\n  highlightOpacity: ReturnType<typeof useSpring>\n  axis: 'x' | 'y'\n  className?: string\n  style?: MotionStyle\n}\n\nexport function ProximityHighlight({\n  highlightX,\n  highlightSize,\n  highlightOpacity,\n  axis,\n  className,\n  style,\n}: ProximityHighlightProps) {\n  return (\n    <motion.div\n      aria-hidden=\"true\"\n      className={className}\n      style={{\n        ...style,\n        position: 'absolute',\n        inset: 0,\n        pointerEvents: 'none',\n        ...(axis === 'y'\n          ? { y: highlightX, height: highlightSize }\n          : { x: highlightX, width: highlightSize }),\n        opacity: highlightOpacity,\n      }}\n    />\n  )\n}\n\nexport function useRegisterProximityItem(\n  registerItem: (index: number, element: HTMLElement | null) => void,\n  index: number,\n  ref: RefObject<HTMLElement | null>,\n) {\n  useEffect(() => {\n    registerItem(index, ref.current)\n    return () => registerItem(index, null)\n  }, [index, registerItem, ref])\n}\n"
    }
  ],
  "type": "registry:ui"
}
