{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-dock",
  "type": "registry:ui",
  "title": "Floating dock",
  "description": "Scales nearby destinations with restrained spring response to pointer distance.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "navigation"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/floating-dock/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "light",
    "signature": "Distance-aware spring dock",
    "sceneFamily": "product-mono",
    "motionRole": "ui",
    "primaryState": "Floating dock with a focused destination",
    "assetProvenance": "self-contained"
  },
  "files": [
    {
      "path": "src/registry/components/floating-dock.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/floating-dock.tsx",
      "content": "\"use client\";\n\nimport { useRef, type ReactNode } from \"react\";\nimport { motion, useMotionValue, useReducedMotion, useSpring, useTransform } from \"motion/react\";\n\nconst DISTANT = 10_000;\nconst FOLLOW = { stiffness: 520, damping: 34, mass: 0.4 } as const;\n\nexport type FloatingDockItem = {\n  id: string;\n  label: string;\n  icon: ReactNode;\n  onSelect: () => void;\n};\n\nexport type FloatingDockProps = {\n  items: readonly FloatingDockItem[];\n  label: string;\n  emptyLabel?: string;\n  activeId?: string;\n  magnification?: number;\n  distance?: number;\n  className?: string;\n};\n\nfunction DockItem({ item, pointerX, pointerY, reduced, active, magnification, distance }: {\n  item: FloatingDockItem;\n  pointerX: ReturnType<typeof useMotionValue<number>>;\n  pointerY: ReturnType<typeof useMotionValue<number>>;\n  reduced: boolean;\n  active: boolean;\n  magnification: number;\n  distance: number;\n}) {\n  const root = useRef<HTMLButtonElement>(null);\n  const tooltip = useRef<HTMLSpanElement>(null);\n  const positionTooltip = () => {\n    const node = tooltip.current;\n    if (!node) return;\n    node.style.transform = \"\";\n    const box = node.getBoundingClientRect();\n    const viewportPadding = 8;\n    const shift = box.left < viewportPadding\n      ? viewportPadding - box.left\n      : box.right > window.innerWidth - viewportPadding\n        ? window.innerWidth - viewportPadding - box.right\n        : 0;\n    if (shift !== 0) node.style.transform = `translateX(${shift}px)`;\n  };\n  const raw = useTransform([pointerX, pointerY], ([positionX, positionY]) => {\n    const box = root.current?.getBoundingClientRect();\n    if (!box || reduced || positionX === DISTANT || positionY === DISTANT) return 1;\n    const delta = Math.hypot(\n      Number(positionX) - (box.left + box.width / 2),\n      Number(positionY) - (box.top + box.height / 2),\n    );\n    const influence = Math.max(0, 1 - delta / distance);\n    return 1 + influence * (magnification - 1);\n  });\n  const scale = useSpring(raw, FOLLOW);\n  const transform = useTransform(scale, (value) => `scale(${value}) translateZ(0)`);\n\n  return (\n    <div\n      className=\"group relative grid w-11 shrink-0 place-items-center\"\n      onPointerEnter={positionTooltip}\n    >\n      <motion.button\n        ref={root}\n        type=\"button\"\n        aria-label={item.label}\n        aria-current={active ? \"page\" : undefined}\n        onFocus={() => {\n          const box = root.current?.getBoundingClientRect();\n          if (box && !reduced) {\n            pointerX.set(box.left + box.width / 2);\n            pointerY.set(box.top + box.height / 2);\n          }\n          positionTooltip();\n        }}\n        onBlur={() => {\n          pointerX.set(DISTANT);\n          pointerY.set(DISTANT);\n          if (tooltip.current) tooltip.current.style.transform = \"\";\n        }}\n        onClick={item.onSelect}\n        className={`relative grid size-11 place-items-center rounded-lg border outline-none focus-visible:ring-2 focus-visible:ring-blue-600 ${active ? \"border-neutral-950 bg-neutral-950 text-white dark:border-neutral-100 dark:bg-neutral-100 dark:text-neutral-950\" : \"border-neutral-200 bg-white text-neutral-600 dark:border-white/15 dark:bg-[#202020] dark:text-neutral-300\"}`}\n        style={{ transform, transformOrigin: \"50% 100%\", touchAction: \"manipulation\" }}\n      >\n        <span aria-hidden>\n          {item.icon}\n        </span>\n        {active ? <span aria-hidden className=\"absolute -bottom-1 size-1 rounded-full bg-neutral-950 dark:bg-neutral-50\" /> : null}\n      </motion.button>\n      <span\n        ref={tooltip}\n        role=\"tooltip\"\n        className=\"pointer-events-none absolute bottom-[calc(100%+10px)] w-max max-w-[min(240px,calc(100vw-16px))] whitespace-normal rounded-[7px] bg-neutral-900 px-2 py-1 text-center text-[10px] leading-[1.3] text-white opacity-0 transition-opacity duration-150 [overflow-wrap:anywhere] group-focus-within:opacity-100 [@media(hover:hover)_and_(pointer:fine)]:group-hover:opacity-100 dark:bg-neutral-100 dark:text-neutral-950\"\n      >\n        {item.label}\n      </span>\n    </div>\n  );\n}\n\nexport function FloatingDock({\n  items,\n  label,\n  emptyLabel = \"No dock items\",\n  activeId,\n  magnification = 1.48,\n  distance = 108,\n  className = \"\",\n}: FloatingDockProps) {\n  const pointerX = useMotionValue(DISTANT);\n  const pointerY = useMotionValue(DISTANT);\n  const reduced = useReducedMotion() === true;\n  const resolvedMagnification = Number.isFinite(magnification) ? Math.max(1, magnification) : 1.48;\n  const resolvedDistance = Number.isFinite(distance) ? Math.max(1, distance) : 108;\n\n  return (\n    <nav\n      aria-label={label}\n      onPointerMove={(event) => {\n        if (event.pointerType !== \"touch\" && !reduced) {\n          pointerX.set(event.clientX);\n          pointerY.set(event.clientY);\n        }\n      }}\n      onPointerLeave={() => {\n        pointerX.set(DISTANT);\n        pointerY.set(DISTANT);\n      }}\n      className={`flex min-h-[66px] max-w-full flex-wrap items-end justify-center gap-px rounded-[10px] border border-neutral-200 bg-white px-1.5 pb-2 pt-3 dark:border-white/15 dark:bg-[#1e1e1e] ${className}`}\n    >\n      {items.length === 0 ? (\n        <span role=\"status\" className=\"grid min-h-11 place-items-center px-3 text-center text-[12px] text-neutral-600 dark:text-neutral-300\">\n          {emptyLabel}\n        </span>\n      ) : items.map((item) => (\n          <DockItem key={item.id} item={item} pointerX={pointerX} pointerY={pointerY} reduced={reduced} active={item.id === activeId} magnification={resolvedMagnification} distance={resolvedDistance} />\n        ))}\n    </nav>\n  );\n}\n"
    }
  ]
}
