{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock",
  "type": "registry:ui",
  "description": "macOS-style dock component with hover effects and smooth animations",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/default/ui/dock.tsx",
      "content": "\"use client\"\n\nimport React, {\n  createContext,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\"\nimport {\n  animate,\n  AnimatePresence,\n  motion,\n  MotionValue,\n  useAnimation,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Interface to define the types for our Dock context\ninterface DockContextType {\n  width: number // Width of the dock\n  hovered: boolean // If the dock is hovered\n  setIsZooming: (value: boolean) => void // Function to set zooming state\n  zoomLevel: MotionValue<number> // Motion value for zoom level\n  mouseX: MotionValue<number> // Motion value for mouse X position\n  animatingIndexes: number[] // Array of animating indexes\n  setAnimatingIndexes: (indexes: number[]) => void // Function to set animating indexes\n}\n\n// Initial width for the dock\nconst INITIAL_WIDTH = 48\n\n// Create a context to manage Dock state\nconst DockContext = createContext<DockContextType>({\n  width: 0,\n  hovered: false,\n  setIsZooming: () => {},\n  zoomLevel: null as any,\n  mouseX: null as any,\n  animatingIndexes: [],\n  setAnimatingIndexes: () => {},\n})\n\n// Custom hook to use Dock context\nconst useDock = () => useContext(DockContext)\n\n// Props for the Dock component\ninterface DockProps {\n  className?: string\n  children: ReactNode // React children to be rendered within the dock\n}\n\n// Main Dock component: orchestrating the dock's animation behavior\nfunction Dock({ className, children }: DockProps) {\n  const [hovered, setHovered] = useState(false) // State to track if the dock is hovered. When the mouse hovers over the dock, this state changes to true.\n  const [width, setWidth] = useState(0) // State to track the width of the dock. This dynamically updates based on the dock's current width.\n  const dockRef = useRef<HTMLDivElement>(null) // Reference to the dock element in the DOM. This allows direct manipulation and measurement of the dock.\n  const isZooming = useRef(false) // Reference to track if the zooming animation is active. This prevents conflicting animations.\n  const [animatingIndexes, setAnimatingIndexes] = useState<number[]>([]) // State to track which dock items are currently animating. This array holds the indices of animating items.\n\n  // Callback to toggle the zooming state. This ensures that we don't trigger hover animations while zooming.\n  const setIsZooming = useCallback((value: boolean) => {\n    isZooming.current = value // Update the zooming reference\n    setHovered(!value) // Update the hover state based on zooming\n  }, [])\n\n  const zoomLevel = useMotionValue(1) // Motion value for the zoom level of the dock. This provides a smooth zooming animation.\n\n  // Hook to handle window resize events and update the dock's width accordingly.\n  useWindowResize(() => {\n    setWidth(dockRef.current?.clientWidth || 0) // Set width to the dock's current width or 0 if undefined\n  })\n\n  const mouseX = useMotionValue(Infinity) // Motion value to track the mouse's X position relative to the viewport. Initialized to Infinity to denote no tracking initially.\n\n  return (\n    // Provide the dock's state and control methods to the rest of the application through context.\n    <DockContext.Provider\n      value={{\n        hovered, // Current hover state of the dock\n        setIsZooming, // Method to set the zooming state\n        width, // Current width of the dock\n        zoomLevel, // Current zoom level motion value\n        mouseX, // Current mouse X position motion value\n        animatingIndexes, // Current animating indexes\n        setAnimatingIndexes, // Method to set animating indexes\n      }}\n    >\n      <motion.div\n        ref={dockRef} // Reference to the dock element\n        // className=\"absolute bottom-4 left-1/2 transform -translate-x-1/2 flex items-end h-14 p-2 gap-3 bg-neutral-50 dark:bg-black bg-opacity-90 rounded-xl\"\n        className={cn(\n          \"bg-opacity-90 absolute bottom-4 left-3/4 flex h-14 -translate-x-1/2 transform items-end gap-3 rounded-xl p-2\",\n          \"bg-neutral-50 p-2 no-underline shadow-sm transition-colors hover:bg-neutral-100 dark:bg-neutral-900 dark:hover:bg-neutral-800/80\",\n          \"shadow-[0px_1px_1px_0px_rgba(0,0,0,0.05),0px_1px_1px_0px_rgba(255,252,240,0.5)_inset,0px_0px_0px_1px_hsla(0,0%,100%,0.1)_inset,0px_0px_1px_0px_rgba(28,27,26,0.5)]\",\n          \"shadow-[rgba(17,24,28,0.08)_0_0_0_1px,rgba(17,24,28,0.08)_0_1px_2px_-1px,rgba(17,24,28,0.04)_0_2px_4px]\",\n          \"dark:shadow-[0_1px_0_0_rgba(255,255,255,0.03)_inset,0_0_0_1px_rgba(255,255,255,0.03)_inset,0_0_0_1px_rgba(0,0,0,0.1),0_2px_2px_0_rgba(0,0,0,0.1),0_4px_4px_0_rgba(0,0,0,0.1),0_8px_8px_0_rgba(0,0,0,0.1)]\",\n          className\n        )}\n        // Event handler for mouse movement within the dock\n        onMouseMove={(e) => {\n          mouseX.set(e.pageX) // Update the mouseX motion value to the current mouse position\n          if (!isZooming.current) {\n            // Only set hovered if not zooming\n            setHovered(true) // Set hovered state to true\n          }\n        }}\n        // Event handler for when the mouse leaves the dock\n        onMouseLeave={() => {\n          mouseX.set(Infinity) // Reset mouseX motion value\n          setHovered(false) // Set hovered state to false\n        }}\n        style={{\n          x: \"-50%\", // Center the dock horizontally\n          scale: zoomLevel, // Bind the zoom level to the scale style property\n        }}\n      >\n        {children} {/* Render the dock's children within the motion div */}\n      </motion.div>\n    </DockContext.Provider>\n  )\n}\n\n// Props for the DockCardInner component\ninterface DockCardInnerProps {\n  src: string // Source URL for the image\n  id: string // Unique identifier for the card\n  children?: ReactNode // Optional children for the card\n}\n\n// DockCardInner component to display images and handle animation states\nfunction DockCardInner({ src, id, children }: DockCardInnerProps) {\n  const { animatingIndexes } = useDock() // Access the Dock context to get the animating indexes. This determines which cards are currently animating.\n\n  return (\n    <span className=\"relative z-0 flex h-full w-full items-center justify-center overflow-hidden rounded-md\">\n      {/* Background image with a blur effect to give a depth illusion */}\n      <motion.img\n        className=\"absolute z-10 translate-y-2.5 scale-125 transform opacity-40 blur-md filter\"\n        src={src}\n        alt=\"\"\n      />\n\n      {/* AnimatePresence component to handle the entrance and exit animations of children - in our case, the \"openIcon\" */}\n      <AnimatePresence>\n        {animatingIndexes.includes(parseInt(id)) && children ? (\n          <motion.div\n            className=\"relative z-0 h-full w-full rounded-full\"\n            initial={{ scale: 0, opacity: 0, filter: \"blur(4px)\" }}\n            animate={{\n              scale: 1,\n              opacity: 1,\n              filter: \"blur(0px)\",\n              transition: { type: \"spring\", delay: 0.2 }, // Animation to spring into place with a delay so our layoutId animations can be smooth\n            }}\n            exit={{\n              scale: 0,\n              opacity: 0,\n              filter: \"blur(4px)\",\n              transition: { duration: 0 }, // Exit animation with no delay\n            }}\n          >\n            <div className=\"flex h-full w-full flex-col items-center justify-center\">\n              {/* Render the openIcon */}\n              {children}\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n\n      {/* Another AnimatePresence to handle layout animations */}\n      <AnimatePresence mode=\"popLayout\">\n        {!animatingIndexes.includes(parseInt(id)) ? (\n          <motion.img\n            layoutId={id} // Unique identifier for layout animations\n            className=\"relative z-0 h-1/2 w-1/2 rounded-full border border-black/30 dark:border-white/10\"\n            src={src}\n            alt=\"\"\n          />\n        ) : null}\n      </AnimatePresence>\n    </span>\n  )\n}\n\n// Props for the DockCard component\ninterface DockCardProps {\n  children: ReactNode // React children to be rendered within the dock card\n  id: string // Unique identifier for the dock card\n}\n\n// DockCard component: manages individual card behavior within the dock\nfunction DockCard({ children, id }: DockCardProps) {\n  const cardRef = useRef<HTMLButtonElement>(null) // Reference to the card button element for direct DOM manipulation and measurement\n  const [elCenterX, setElCenterX] = useState(0) // State to store the center X position of the card for accurate mouse interaction calculations\n  const dock = useDock() // Access the Dock context to get shared state and control functions\n\n  // Spring animation for the size of the card, providing a smooth and responsive scaling effect\n  const size = useSpring(INITIAL_WIDTH, {\n    stiffness: 320,\n    damping: 20,\n    mass: 0.1,\n  })\n\n  // Spring animation for the opacity of the card, enabling smooth fade-in and fade-out effects\n  const opacity = useSpring(0, {\n    stiffness: 300,\n    damping: 20,\n  })\n\n  // Custom hook to track mouse position and update the card size dynamically based on proximity to the mouse\n  useMousePosition(\n    {\n      onChange: ({ value }) => {\n        const mouseX = value.x\n        if (dock.width > 0) {\n          // Calculate transformed value based on mouse position and card center, using a cosine function for a smooth curve\n          const transformedValue =\n            INITIAL_WIDTH +\n            36 *\n              Math.cos((((mouseX - elCenterX) / dock.width) * Math.PI) / 2) **\n                12\n\n          // Only animate size if the dock is hovered\n          if (dock.hovered) {\n            animate(size, transformedValue)\n          }\n        }\n      },\n    },\n    [elCenterX, dock]\n  )\n\n  // Hook to update the center X position of the card on window resize for accurate mouse interaction\n  useWindowResize(() => {\n    const { x } = cardRef.current?.getBoundingClientRect() || { x: 0 }\n    setElCenterX(x + 24) // 24 is the half of INITIAL_WIDTH (48 / 2), centering the element\n  })\n\n  const isAnimating = useRef(false) // Reference to track if the card is currently animating to avoid conflicting animations\n  const controls = useAnimation() // Animation controls for managing card's Y position during the animation loop\n  const timeoutRef = useRef<number | null>(null) // Reference to manage timeout cleanup on component unmount\n\n  // Handle click event to start or stop the card's animation\n  const handleClick = () => {\n    if (!isAnimating.current) {\n      isAnimating.current = true\n      // Add the card's id to the animatingIndexes array in the Dock context\n      dock.setAnimatingIndexes([...dock.animatingIndexes, parseInt(id)])\n      opacity.set(0.5) // Set opacity for the animation\n      controls.start({\n        y: -24, // Move the card up by 24 pixels\n        transition: {\n          repeat: Infinity, // Repeat the animation indefinitely\n          repeatType: \"reverse\", // Reverse the direction of the animation each cycle\n          duration: 0.5, // Duration of each cycle\n        },\n      })\n    } else {\n      isAnimating.current = false\n      // Remove the card's id from the animatingIndexes array in the Dock context\n      dock.setAnimatingIndexes(\n        dock.animatingIndexes.filter((index) => index !== parseInt(id))\n      )\n      opacity.set(0) // Reset opacity\n      controls.start({\n        y: 0, // Reset Y position to the original state\n        transition: { duration: 0.5 }, // Smooth transition back to original state\n      })\n    }\n  }\n\n  // Cleanup timeout on component unmount to prevent memory leaks\n  useEffect(() => {\n    return () => clearTimeout(timeoutRef.current!)\n  }, [])\n\n  // Calculate the distance from the mouse position to the center of the card\n  const distance = useTransform(dock.mouseX, (val) => {\n    const bounds = cardRef.current?.getBoundingClientRect() ?? {\n      x: 0,\n      width: 0,\n    }\n    return val - bounds.x - bounds.width / 2 // Calculate distance to the center of the card\n  })\n\n  // Transform the calculated distance into a responsive width for the card\n  let widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40])\n  let width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 })\n\n  return (\n    <div className=\"flex flex-col items-center gap-1\" key={id}>\n      {/* Motion button for the card, handling click events and animations */}\n      <motion.button\n        ref={cardRef} // Reference to the button element\n        className=\"border-opacity-10 transition-filter aspect-square rounded-lg border border-black/5 bg-neutral-100 brightness-90 saturate-90 duration-200 hover:brightness-112 hover:saturate-100 dark:border-white/5 dark:bg-neutral-800\"\n        onClick={handleClick} // Click handler to start/stop animation\n        style={{\n          width: width, // Responsive width based on mouse distance\n        }}\n        animate={controls} // Animation controls for Y position\n        whileTap={{ scale: 0.95 }} // Scale down slightly on tap for a tactile feel\n      >\n        {children}{\" \"}\n        {/* Render the children of the DockCard inside the motion button */}\n      </motion.button>\n\n      {/* AnimatePresence to manage the presence and layout animations of the card's indicator */}\n      <AnimatePresence mode=\"popLayout\">\n        {dock.animatingIndexes.includes(parseInt(id)) ? (\n          <motion.div\n            key={id} // Unique key for the motion div\n            layoutId={id} // Layout identifier for smooth layout animations\n            className=\"rounded-full\"\n            style={{ opacity }} // Bind opacity to the animated opacity spring\n          >\n            <motion.div\n              exit={{ transition: { duration: 0 } }} // Exit transition with no duration for immediate removal\n              className=\"h-1.5 w-1.5 rounded-full bg-black dark:bg-white\"\n              style={{ opacity }} // Bind opacity to the animated opacity spring\n            />\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n// Divider component for the dock\nfunction DockDivider() {\n  return (\n    <motion.div\n      className=\"flex h-full cursor-ns-resize items-center p-1.5\"\n      drag=\"y\"\n      dragConstraints={{ top: -100, bottom: 50 }}\n    >\n      <span className=\"h-full w-0.5 rounded bg-neutral-800/10 dark:bg-neutral-100/10\"></span>\n    </motion.div>\n  )\n}\n\ntype UseWindowResizeCallback = (width: number, height: number) => void\n\n// Custom hook to handle window resize events and invoke a callback with the new dimensions\nfunction useWindowResize(callback: UseWindowResizeCallback) {\n  // Create a stable callback reference to ensure the latest callback is always used\n  const callbackRef = useCallbackRef(callback)\n\n  useEffect(() => {\n    // Function to handle window resize and call the provided callback with updated dimensions\n    const handleResize = () => {\n      callbackRef(window.innerWidth, window.innerHeight)\n    }\n\n    // Initial call to handleResize to capture the current window size\n    handleResize()\n    // Adding event listener for window resize events\n    window.addEventListener(\"resize\", handleResize)\n\n    // Cleanup function to remove the event listener when the component unmounts or dependencies change\n    return () => {\n      window.removeEventListener(\"resize\", handleResize)\n    }\n  }, [callbackRef]) // Dependency array includes the stable callback reference\n}\n\n// Custom hook to create a stable callback reference\nfunction useCallbackRef<T extends (...args: any[]) => any>(callback: T): T {\n  // Use a ref to store the callback\n  const callbackRef = useRef(callback)\n\n  // Update the ref with the latest callback whenever it changes\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  // Return a memoized version of the callback that always uses the latest callback stored in the ref\n  return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, [])\n}\n\n// Interface for mouse position options\ninterface MousePositionOptions {\n  onChange?: (position: { value: { x: number; y: number } }) => void\n}\n\n// Custom hook to track mouse position and animate values accordingly\nexport function useMousePosition(\n  options: MousePositionOptions = {}, // Options to customize behavior, including an onChange callback\n  deps: readonly any[] = [] // Dependencies array to determine when the effect should re-run\n) {\n  const { onChange } = options // Destructure onChange from options for use in the effect\n\n  // Create motion values for x and y coordinates, initialized to 0\n  const x = useMotionValue(0)\n  const y = useMotionValue(0)\n\n  useEffect(() => {\n    // Function to handle mouse move events, animating the x and y motion values to the current mouse coordinates\n    const handleMouseMove = (event: MouseEvent) => {\n      animate(x, event.clientX)\n      animate(y, event.clientY)\n    }\n\n    // Function to handle changes in the motion values, calling the onChange callback if it exists\n    const handleChange = () => {\n      if (onChange) {\n        onChange({ value: { x: x.get(), y: y.get() } })\n      }\n    }\n\n    // Subscribe to changes in the x and y motion values\n    const unsubscribeX = x.on(\"change\", handleChange)\n    const unsubscribeY = y.on(\"change\", handleChange)\n\n    // Add event listener for mouse move events\n    window.addEventListener(\"mousemove\", handleMouseMove)\n\n    // Cleanup function to remove event listener and unsubscribe from motion value changes\n    return () => {\n      window.removeEventListener(\"mousemove\", handleMouseMove)\n      unsubscribeX()\n      unsubscribeY()\n    }\n  }, [x, y, onChange, ...deps]) // Dependency array includes x, y, onChange, and any additional dependencies\n\n  // Memoize and return the motion values for x and y coordinates\n  return useMemo(\n    () => ({\n      x, // Motion value for x coordinate\n      y, // Motion value for y coordinate\n    }),\n    [x, y] // Dependencies for the memoized return value\n  )\n}\n\nexport { Dock, DockCard, DockCardInner, DockDivider, useDock }\nexport default Dock\n",
      "type": "registry:ui"
    }
  ]
}