{
  "name": "floating-dock",
  "type": "registry:ui",
  "dependencies": [
    "@tabler/icons-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/floating-dock.tsx",
      "content": "\"use client\";\n/**\n * Note: Use position fixed according to your needs\n * Desktop navbar is better positioned at the bottom\n * Mobile navbar is better positioned at bottom right.\n **/\n\nimport { cn } from \"@/lib/utils\";\nimport { IconLayoutNavbarCollapse } from \"@tabler/icons-react\";\nimport {\n  AnimatePresence,\n  MotionValue,\n  motion,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\n\nimport { useRef, useState } from \"react\";\n\nexport const FloatingDock = ({\n  items,\n  desktopClassName,\n  mobileClassName,\n}: {\n  items: { title: string; icon: React.ReactNode; href: string }[];\n  desktopClassName?: string;\n  mobileClassName?: string;\n}) => {\n  return (\n    <>\n      <FloatingDockDesktop items={items} className={desktopClassName} />\n      <FloatingDockMobile items={items} className={mobileClassName} />\n    </>\n  );\n};\n\nconst FloatingDockMobile = ({\n  items,\n  className,\n}: {\n  items: { title: string; icon: React.ReactNode; href: string }[];\n  className?: string;\n}) => {\n  const [open, setOpen] = useState(false);\n  return (\n    <div className={cn(\"relative block md:hidden\", className)}>\n      <AnimatePresence>\n        {open && (\n          <motion.div\n            layoutId=\"nav\"\n            className=\"absolute inset-x-0 bottom-full mb-2 flex flex-col gap-2\"\n          >\n            {items.map((item, idx) => (\n              <motion.div\n                key={item.title}\n                initial={{ opacity: 0, y: 10 }}\n                animate={{\n                  opacity: 1,\n                  y: 0,\n                }}\n                exit={{\n                  opacity: 0,\n                  y: 10,\n                  transition: {\n                    delay: idx * 0.05,\n                  },\n                }}\n                transition={{ delay: (items.length - 1 - idx) * 0.05 }}\n              >\n                <a\n                  href={item.href}\n                  key={item.title}\n                  className=\"flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 dark:bg-neutral-900\"\n                >\n                  <div className=\"h-4 w-4\">{item.icon}</div>\n                </a>\n              </motion.div>\n            ))}\n          </motion.div>\n        )}\n      </AnimatePresence>\n      <button\n        onClick={() => setOpen(!open)}\n        className=\"flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 dark:bg-neutral-800\"\n      >\n        <IconLayoutNavbarCollapse className=\"h-5 w-5 text-neutral-500 dark:text-neutral-400\" />\n      </button>\n    </div>\n  );\n};\n\nconst FloatingDockDesktop = ({\n  items,\n  className,\n}: {\n  items: { title: string; icon: React.ReactNode; href: string }[];\n  className?: string;\n}) => {\n  let mouseX = useMotionValue(Infinity);\n  return (\n    <motion.div\n      onMouseMove={(e) => mouseX.set(e.pageX)}\n      onMouseLeave={() => mouseX.set(Infinity)}\n      className={cn(\n        \"mx-auto hidden h-16 items-end gap-4 rounded-2xl bg-gray-50 px-4 pb-3 md:flex dark:bg-neutral-900\",\n        className,\n      )}\n    >\n      {items.map((item) => (\n        <IconContainer mouseX={mouseX} key={item.title} {...item} />\n      ))}\n    </motion.div>\n  );\n};\n\nfunction IconContainer({\n  mouseX,\n  title,\n  icon,\n  href,\n}: {\n  mouseX: MotionValue;\n  title: string;\n  icon: React.ReactNode;\n  href: string;\n}) {\n  let ref = useRef<HTMLDivElement>(null);\n\n  let distance = useTransform(mouseX, (val) => {\n    let bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };\n\n    return val - bounds.x - bounds.width / 2;\n  });\n\n  let widthTransform = useTransform(distance, [-150, 0, 150], [40, 80, 40]);\n  let heightTransform = useTransform(distance, [-150, 0, 150], [40, 80, 40]);\n\n  let widthTransformIcon = useTransform(distance, [-150, 0, 150], [20, 40, 20]);\n  let heightTransformIcon = useTransform(\n    distance,\n    [-150, 0, 150],\n    [20, 40, 20],\n  );\n\n  let width = useSpring(widthTransform, {\n    mass: 0.1,\n    stiffness: 150,\n    damping: 12,\n  });\n  let height = useSpring(heightTransform, {\n    mass: 0.1,\n    stiffness: 150,\n    damping: 12,\n  });\n\n  let widthIcon = useSpring(widthTransformIcon, {\n    mass: 0.1,\n    stiffness: 150,\n    damping: 12,\n  });\n  let heightIcon = useSpring(heightTransformIcon, {\n    mass: 0.1,\n    stiffness: 150,\n    damping: 12,\n  });\n\n  const [hovered, setHovered] = useState(false);\n\n  return (\n    <a href={href}>\n      <motion.div\n        ref={ref}\n        style={{ width, height }}\n        onMouseEnter={() => setHovered(true)}\n        onMouseLeave={() => setHovered(false)}\n        className=\"relative flex aspect-square items-center justify-center rounded-full bg-gray-200 dark:bg-neutral-800\"\n      >\n        <AnimatePresence>\n          {hovered && (\n            <motion.div\n              initial={{ opacity: 0, y: 10, x: \"-50%\" }}\n              animate={{ opacity: 1, y: 0, x: \"-50%\" }}\n              exit={{ opacity: 0, y: 2, x: \"-50%\" }}\n              className=\"absolute -top-8 left-1/2 w-fit rounded-md border border-gray-200 bg-gray-100 px-2 py-0.5 text-xs whitespace-pre text-neutral-700 dark:border-neutral-900 dark:bg-neutral-800 dark:text-white\"\n            >\n              {title}\n            </motion.div>\n          )}\n        </AnimatePresence>\n        <motion.div\n          style={{ width: widthIcon, height: heightIcon }}\n          className=\"flex items-center justify-center\"\n        >\n          {icon}\n        </motion.div>\n      </motion.div>\n    </a>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/floating-dock.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Floating Dock"
}