UNPKG

known-ui

Version:

A CLI tool for integrating Known UI components into your Next.js projects.

137 lines (122 loc) 4.61 kB
/* 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;