"use client" import React, { PropsWithChildren, useRef } from "react" import { cva, type VariantProps } from "class-variance-authority" import { motion, MotionValue, useMotionValue, useSpring, useTransform, } from "framer-motion" import type { MotionProps } from "framer-motion" 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< MotionProps & React.HTMLAttributes, "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 }