"use client"; import { cva, type VariantProps } from "class-variance-authority"; import { motion, MotionValue, useMotionValue, useSpring, useTransform, } from "motion/react"; import type { MotionProps } from "motion/react"; import React, { PropsWithChildren, useRef } from "react"; import { cn } from "@/lib/utils"; export interface DockProps extends VariantProps { className?: string; iconSize?: number; iconMagnification?: number; disableMagnification?: boolean; iconDistance?: number; direction?: "top" | "middle" | "bottom"; children: React.ReactNode; } const DEFAULT_SIZE = 40; const DEFAULT_MAGNIFICATION = 60; const DEFAULT_DISTANCE = 140; const DEFAULT_DISABLEMAGNIFICATION = false; const dockVariants = cva( "supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max items-center justify-center gap-2 rounded-2xl border p-2 backdrop-blur-md", ); const Dock = React.forwardRef( ( { className, children, iconSize = DEFAULT_SIZE, iconMagnification = DEFAULT_MAGNIFICATION, disableMagnification = DEFAULT_DISABLEMAGNIFICATION, iconDistance = DEFAULT_DISTANCE, direction = "middle", ...props }, ref, ) => { const mouseX = useMotionValue(Infinity); const renderChildren = () => { return React.Children.map(children, (child) => { if ( React.isValidElement(child) && child.type === DockIcon ) { return React.cloneElement(child, { ...child.props, mouseX: mouseX, size: iconSize, magnification: iconMagnification, disableMagnification: disableMagnification, distance: iconDistance, }); } return child; }); }; return ( mouseX.set(e.pageX)} onMouseLeave={() => mouseX.set(Infinity)} {...props} className={cn(dockVariants({ className }), { "items-start": direction === "top", "items-center": direction === "middle", "items-end": direction === "bottom", })} > {renderChildren()} ); }, ); Dock.displayName = "Dock"; export interface DockIconProps extends Omit, "children"> { size?: number; magnification?: number; disableMagnification?: boolean; distance?: number; mouseX?: MotionValue; className?: string; children?: React.ReactNode; props?: PropsWithChildren; } const DockIcon = ({ size = DEFAULT_SIZE, magnification = DEFAULT_MAGNIFICATION, disableMagnification, distance = DEFAULT_DISTANCE, mouseX, className, children, ...props }: DockIconProps) => { const ref = useRef(null); const padding = Math.max(6, size * 0.2); const defaultMouseX = useMotionValue(Infinity); const distanceCalc = useTransform(mouseX ?? defaultMouseX, (val: number) => { const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; return val - bounds.x - bounds.width / 2; }); const targetSize = disableMagnification ? size : magnification; const sizeTransform = useTransform( distanceCalc, [-distance, 0, distance], [size, targetSize, size], ); const scaleSize = useSpring(sizeTransform, { mass: 0.1, stiffness: 150, damping: 12, }); return (
{children}
); }; DockIcon.displayName = "DockIcon"; export { Dock, DockIcon, dockVariants };