{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-nav",
  "title": "Floating Nav",
  "author": "mrap10",
  "description": "A floating navbar that scales items on hover, inspired by macos floating dock.",
  "dependencies": ["motion"],
  "files": [
    {
      "path": "registry/default/pop-ups/floating-nav.tsx",
      "content": "\"use client\";\n\nimport {\n  AnimatePresence,\n  motion,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\nimport { useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport Link from \"next/link\";\n\nexport interface NavItem {\n  label: string;\n  icon: React.ReactNode;\n  href: string;\n  asButton?: boolean;\n  onClick?: () => void;\n  active?: boolean;\n}\n\nexport interface NavSeparator {\n  type: \"separator\";\n}\n\nexport type NavEntry = NavItem | NavSeparator;\n\nexport interface FloatingNavProps {\n  items: NavEntry[];\n  iconSize?: number;\n  maxScale?: number;\n  showTooltip?: boolean;\n  className?: string;\n}\n\nconst SPRING = { mass: 0.1, stiffness: 150, damping: 12 };\n\nfunction isSeparator(entry: NavEntry): entry is NavSeparator {\n  return \"type\" in entry && entry.type === \"separator\";\n}\n\nexport function FloatingNav({\n  items,\n  iconSize = 44,\n  maxScale = 1.5,\n  showTooltip = true,\n  className,\n}: FloatingNavProps) {\n  const mouseX = useMotionValue(Infinity);\n\n  return (\n    <nav\n      aria-label=\"floating navigation\"\n      className=\"fixed bottom-3 left-1/2 z-50 -translate-x-1/2\"\n    >\n      <motion.div\n        initial={{ opacity: 0, y: 12, scale: 0.95 }}\n        animate={{ opacity: 1, y: 0, scale: 1 }}\n        transition={{\n          type: \"spring\",\n          stiffness: 260,\n          damping: 24,\n          delay: 0.05,\n        }}\n        onMouseMove={(e) => mouseX.set(e.clientX)}\n        onMouseLeave={() => mouseX.set(Infinity)}\n        style={{ height: iconSize + 16 }}\n        className={cn(\n          \"flex items-end gap-1.5 rounded-full px-3 py-2\",\n          \"border border-neutral-200/70 bg-white/60 dark:border-neutral-700/50 dark:bg-neutral-900/70\",\n          \"shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl dark:shadow-[0_4px_24px_rgba(0,0,0,0.4)]\",\n          className\n        )}\n      >\n        {items.map((entry, index) =>\n          isSeparator(entry) ? (\n            <div\n              key={`sep-${index}`}\n              role=\"separator\"\n              aria-orientation=\"vertical\"\n              className=\"mx-0.5 h-6 w-px shrink-0 self-center bg-neutral-200 dark:bg-neutral-700\"\n            />\n          ) : (\n            <DockItem\n              key={entry.label}\n              entry={entry}\n              mouseX={mouseX}\n              iconSize={iconSize}\n              maxScale={maxScale}\n              showTooltip={showTooltip}\n            />\n          )\n        )}\n      </motion.div>\n    </nav>\n  );\n}\n\nfunction DockItem({\n  entry,\n  mouseX,\n  iconSize,\n  maxScale,\n  showTooltip,\n}: {\n  entry: NavItem;\n  mouseX: ReturnType<typeof useMotionValue<number>>;\n  iconSize: number;\n  maxScale: number;\n  showTooltip: boolean;\n}) {\n  const ref = useRef<HTMLDivElement>(null);\n  const [hovered, setHovered] = useState(false);\n\n  const distance = useTransform(mouseX, (x) => {\n    const bounds = ref.current?.getBoundingClientRect() ?? {\n      left: 0,\n      width: 0,\n    };\n    return x - bounds.left - bounds.width / 2;\n  });\n\n  const sizeTransform = useTransform(\n    distance,\n    [-120, 0, 120],\n    [iconSize, iconSize * maxScale, iconSize]\n  );\n  const iconSizeTransform = useTransform(\n    distance,\n    [-120, 0, 120],\n    [iconSize * 0.45, iconSize * maxScale * 0.45, iconSize * 0.45]\n  );\n\n  const size = useSpring(sizeTransform, SPRING);\n  const innerSize = useSpring(iconSizeTransform, SPRING);\n\n  const sharedProps = {\n    onMouseEnter: () => setHovered(true),\n    onMouseLeave: () => setHovered(false),\n    onClick: entry.onClick,\n    \"aria-label\": entry.label,\n    \"aria-current\": entry.active ? (\"page\" as const) : undefined,\n    className: \"relative flex items-end justify-center outline-none\",\n  };\n\n  const content = (\n    <div ref={ref} className=\"flex items-end justify-center\">\n      <motion.div\n        style={{ width: size, height: size }}\n        className={cn(\n          \"relative flex items-center justify-center rounded-xl will-change-transform\",\n          \"border border-neutral-200/80 bg-white text-neutral-500 shadow-sm\",\n          \"dark:border-neutral-700/60 dark:bg-neutral-900 dark:text-neutral-400\",\n          entry.active &&\n            \"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50\"\n        )}\n      >\n        <motion.div\n          style={{ width: innerSize, height: innerSize }}\n          className=\"flex items-center justify-center\"\n        >\n          {entry.icon}\n        </motion.div>\n\n        {entry.active && (\n          <span className=\"pointer-events-none absolute -bottom-1.5 size-1 rounded-full bg-neutral-800 dark:bg-neutral-200\" />\n        )}\n\n        {showTooltip && (\n          <AnimatePresence>\n            {hovered && (\n              <motion.span\n                initial={{ opacity: 0, y: 4, scale: 0.95 }}\n                animate={{ opacity: 1, y: 0, scale: 1 }}\n                exit={{ opacity: 0, y: 4, scale: 0.95 }}\n                transition={{ duration: 0.14, ease: [0.23, 1, 0.32, 1] }}\n                className={cn(\n                  \"pointer-events-none absolute bottom-full left-1/2 z-10 mb-2 -translate-x-1/2 rounded-md px-2 py-1 whitespace-nowrap\",\n                  \"text-[11px] leading-none font-medium text-neutral-800 dark:text-neutral-100\",\n                  \"border border-neutral-200/80 bg-white shadow-sm dark:border-neutral-700/60 dark:bg-neutral-900\"\n                )}\n                role=\"tooltip\"\n              >\n                {entry.label}\n              </motion.span>\n            )}\n          </AnimatePresence>\n        )}\n      </motion.div>\n    </div>\n  );\n\n  return entry.asButton ? (\n    <button {...sharedProps} type=\"button\">\n      {content}\n    </button>\n  ) : (\n    <Link {...sharedProps} href={entry.href}>\n      {content}\n    </Link>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": ["pop-ups"],
  "type": "registry:component"
}
