Dock Menu

Preview

Code

"use client";

import { type FC, useRef } from "react";
import { motion, useMotionValue, useTransform, useSpring } from "framer-motion";
import { Code, Home, Settings, Mail, BarChart2 } from 'lucide-react';

interface DockMenuProps {
  magnification?: number;
  disabled?: boolean;
}

const icons = [
  <Home key="home" />,
  <Mail key="mail" />,
  <BarChart2 key="analytics" />,
  <Code key="code" />,
  <Settings key="settings" />,
];

const DockMenu: FC<DockMenuProps> = ({ magnification = 50, disabled = false }) => {
  const mouseX = useMotionValue(Infinity);

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (disabled) return;
    mouseX.set(e.pageX);
  };

  const handleMouseLeave = () => {
    mouseX.set(Infinity);
  };

  return (
    <motion.div
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      className="flex items-end justify-center h-16 gap-3 pb-3 px-4 rounded-full border border-white/10 bg-black/30"
    >
      {icons.map((icon, i) => (
        <DockIcon key={i} mouseX={mouseX} magnification={magnification}>
          {icon}
        </DockIcon>
      ))}
    </motion.div>
  );
};

interface DockIconProps {
  mouseX: ReturnType<typeof useMotionValue>;
  magnification: number;
  children: React.ReactNode;
}

const DockIcon: FC<DockIconProps> = ({ mouseX, magnification, children }) => {
  const ref = useRef<HTMLDivElement>(null);

  const distance = useTransform(mouseX, (val) => {
    const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
    return val - bounds.x - bounds.width / 2;
  });

  const widthSync = useTransform(distance, [-150, 0, 150], [1, 1 + magnification / 100, 1]);
  
  const scale = useSpring(widthSync, {
    mass: 0.1,
    stiffness: 150,
    damping: 12,
  });

  return (
    <motion.div
      ref={ref}
      style={{ scale }}
      className="p-3 rounded-full bg-white/10 cursor-pointer"
    >
      {children}
    </motion.div>
  );
};

export default DockMenu;