Dock

PreviousNext

An interactive dock component inspired by macOS dock with animation capabilities.

References

Installation

pnpm dlx shadcn@latest add https://cult-ui.com/r/dock.json

Usage

import { Card, Dock, DockCard, DockDivider, useDock, } from "@/components/Dock/Dock"
// Example usage of the Dock component with animated cards and dividers const DockDemo = () => { const gradients = [ "https://products.ls.graphics/mesh-gradients/images/03.-Snowy-Mint_1-p-130x130q80.jpeg", "https://products.ls.graphics/mesh-gradients/images/04.-Hopbush_1-p-130x130q80.jpeg", "https://products.ls.graphics/mesh-gradients/images/06.-Wisteria-p-130x130q80.jpeg", "https://products.ls.graphics/mesh-gradients/images/09.-Light-Sky-Blue-p-130x130q80.jpeg", "https://products.ls.graphics/mesh-gradients/images/12.-Tumbleweed-p-130x130q80.jpeg", "https://products.ls.graphics/mesh-gradients/images/15.-Perfume_1-p-130x130q80.jpeg", null, "https://products.ls.graphics/mesh-gradients/images/36.-Pale-Chestnut-p-130x130q80.jpeg", ] const openIcons = [ <CircleIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <TriangleIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <SquareIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <PentagonIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <HexagonIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <OctagonIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, <OctagonIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, // skip <BlocksIcon className="h-8 w-8 fill-black stroke-black rounded-full" />, ] return ( <div className="min-h-screen flex items-center justify-center"> <Dock> {gradients.map((src, index) => src ? ( <DockCard key={src} id={`${index}`}> <Card src={src} id={`${index}`}> {openIcons[index]} </Card> </DockCard> ) : ( <DockDivider key={index} /> ) )} </Dock> </div> ) } export default DockDemo

Dock Component

// Main Dock component: orchestrating the dock's animation behavior const Dock = ({ children }: DockProps) => { // State to track if the dock is hovered. When the mouse hovers over the dock, this state changes to true. const [hovered, setHovered] = useState(false) // State to track the width of the dock. This dynamically updates based on the dock's current width. const [width, setWidth] = useState(0) // Reference to the dock element in the DOM. This allows direct manipulation and measurement of the dock. const dockRef = useRef<HTMLDivElement>(null) // Reference to track if the zooming animation is active. This prevents conflicting animations. const isZooming = useRef(false) // State to track which dock items are currently animating. This array holds the indices of animating items. const [animatingIndexes, setAnimatingIndexes] = useState<number[]>([]) // Callback to toggle the zooming state. This ensures that we don't trigger hover animations while zooming. const setIsZooming = useCallback((value: boolean) => { isZooming.current = value // Update the zooming reference setHovered(!value) // Update the hover state based on zooming }, []) // Motion value for the zoom level of the dock. This provides a smooth zooming animation. const zoomLevel = useMotionValue(1) // Hook to handle window resize events and update the dock's width accordingly. useWindowResize(() => { setWidth(dockRef.current?.clientWidth || 0) // Set width to the dock's current width or 0 if undefined }) // Motion value to track the mouse's X position relative to the viewport. Initialized to Infinity to denote no tracking initially. const mouseX = useMotionValue(Infinity) return ( // Provide the dock's state and control methods to the rest of the application through context. <DockContext.Provider value={{ hovered, // Current hover state of the dock setIsZooming, // Method to set the zooming state width, // Current width of the dock zoomLevel, // Current zoom level motion value mouseX, // Current mouse X position motion value animatingIndexes, // Current animating indexes setAnimatingIndexes, // Method to set animating indexes }} > <motion.div ref={dockRef} // Reference to the dock element className="fixed bottom-3 left-1/2 transform -translate-x-1/2 flex items-end h-14 p-2 gap-3 bg-black bg-opacity-90 rounded-xl" // Event handler for mouse movement within the dock onMouseMove={(e) => { mouseX.set(e.pageX) // Update the mouseX motion value to the current mouse position if (!isZooming.current) { // Only set hovered if not zooming setHovered(true) // Set hovered state to true } }} // Event handler for when the mouse leaves the dock onMouseLeave={() => { mouseX.set(Infinity) // Reset mouseX motion value setHovered(false) // Set hovered state to false }} style={{ x: "-50%", // Center the dock horizontally scale: zoomLevel, // Bind the zoom level to the scale style property }} > {children} {/* Render the dock's children within the motion div */} </motion.div> </DockContext.Provider> ) }