known-ui
Version:
A CLI tool for integrating Known UI components into your Next.js projects.
137 lines (122 loc) • 4.61 kB
JSX
/* eslint-disable react-hooks/rules-of-hooks */
import { useRef, useState } from "react";
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
import clsx from "clsx";
import { twMerge } from "tailwind-merge";
import Link from "next/link";
const cn = (...args) => {
return twMerge(clsx(args));
};
const Dock = ({ className = "", items = [], position = "bottom" }) => {
let mouseX = useMotionValue(Infinity);
let mouseY = useMotionValue(Infinity);
const containerStyles = {
left: "left-0 ml-2 top-1/2 transform -translate-y-1/2",
right: "right-0 mr-2 top-1/2 transform -translate-y-1/2",
top: "top-0 mt-2 left-1/2 transform -translate-x-1/2",
bottom: "bottom-0 mb-2 left-1/2 transform -translate-x-1/2",
}[position];
const dockLayout =
position === "top" || position === "bottom" ? "flex-row" : "flex-col";
const tooltipVariants = {
top: { opacity: 1, y: 0 },
bottom: { opacity: 1, y: 0 },
left: { opacity: 1, x: 0 },
right: { opacity: 1, x: 0 },
};
const tooltipInitial = {
top: { opacity: 0, y: -10 },
bottom: { opacity: 0, y: 10 },
left: { opacity: 0, x: -10 },
right: { opacity: 0, x: 10 },
};
const tooltipTransition = { duration: 0.3, ease: "easeOut" };
return (
<div
onMouseMove={(e) => {
mouseX.set(e.pageX);
mouseY.set(e.pageY);
}}
onMouseLeave={() => {
mouseX.set(Infinity);
mouseY.set(Infinity);
}}
className={cn(
`flex ${dockLayout} items-center gap-2 sm:gap-3 md:gap-4 rounded-full bg-zinc-800/70 border border-white border-opacity-5 absolute ${containerStyles}`,
className
)}
>
{items.map((item, index) => {
let ref = useRef(null);
const [hovered, setHovered] = useState(false);
let distance;
if (position === "top" || position === "bottom") {
distance = useTransform(mouseX, (val) => {
let bounds = ref.current?.getBoundingClientRect() ?? {
x: 0,
width: 0,
};
return val - bounds.x - bounds.width / 2;
});
} else {
distance = useTransform(mouseY, (val) => {
let bounds = ref.current?.getBoundingClientRect() ?? {
y: 0,
height: 0,
};
return val - bounds.y - bounds.height / 2;
});
}
let scaleSync = useTransform(distance, [-150, 0, 150], [0.8, 1.2, 0.8]);
let scale = useSpring(scaleSync, {
mass: 0.1,
stiffness: 150,
damping: 12,
});
const tooltipPositionClasses = {
top: "top-full mt-2 -left-2 transform -translate-x-1/2",
bottom: "bottom-full mb-2 -left-2 transform -translate-x-1/2",
left: "left-full ml-2 top-2 transform -translate-y-1/2",
right: "right-full mr-2 top-2 transform -translate-y-1/2",
};
return (
<div key={index} className="relative">
{item.tooltip && (
<motion.div
initial={tooltipInitial[position]}
animate={
hovered ? tooltipVariants[position] : tooltipInitial[position]
}
transition={tooltipTransition}
className={`absolute whitespace-nowrap bg-zinc-800 text-white text-xs px-2 py-1 rounded transition-opacity duration-300 ${tooltipPositionClasses[position]}`}
style={{ visibility: hovered ? "visible" : "hidden" }}
>
{item.tooltip}
</motion.div>
)}
<motion.div
ref={ref}
style={{ scale }}
className={`aspect-square h-10 w-10 rounded-full flex items-center justify-center transition-colors duration-300 ${
hovered
? item.hoverBgColor || "bg-zinc-700"
: item.defaultBgColor || "bg-zinc-700"
}`}
onHoverStart={() => setHovered(true)}
onHoverEnd={() => setHovered(false)}
>
<Link
href={item.link || "#"}
target={item.target || "_self"}
className="flex items-center justify-center w-10 h-10"
>
{item.Icon}
</Link>
</motion.div>
</div>
);
})}
</div>
);
};
export default Dock;