Components
This React component renders a customizable dock with icons that dynamically adjust their size and position based on mouse hover interactions. It uses CSS variables and utility functions for efficient styling and scaling. The dock provides a visually appealing and interactive user experience.
Loading preview...
import React, { useRef } from "react";
import {
Chrome,
Code,
Music,
MessageCircle,
Mail,
Calendar,
Camera,
Settings,
Folder,
Terminal,
Globe,
FileText,
} from "lucide-react";
// Utility functions
type ScaleValueParams = [number, number];
const cn = (...classes: (string | undefined)[]) =>
classes.filter(Boolean).join(' ');
const scaleValue = function (
value: number,
from: ScaleValueParams,
to: ScaleValueParams
): number {
const scale = (to[1] - to[0]) / (from[1] - from[0]);
const capped = Math.min(from[1], Math.max(from[0], value)) - from[0];
return Math.floor(capped * scale + to[0]);
};
// DockIcon Component
interface DockIconProps {
className?: string;
icon: React.ReactNode;
href: string;
name: string;
handleIconHover?: (e: React.MouseEvent<HTMLLIElement>) => void;
iconSize?: number;
color?: string;
}
function DockIcon({
className,
icon,
href,
name,
handleIconHover,
iconSize = 60,
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}: DockIconProps) {
const ref = useRef<HTMLLIElement | null>(null);
return (
<>
<style jsx>{`
.dock-icon:hover + .dock-icon {
width: calc(var(--icon-size) * 1.4 + var(--dock-offset-right, 0px));
height: calc(var(--icon-size) * 1.4 + var(--dock-offset-right, 0px));
transform: translateY(calc(var(--icon-size) * -0.2));
}
.dock-icon:hover + .dock-icon + .dock-icon {
width: calc(var(--icon-size) * 1.2 + var(--dock-offset-right, 0px));
height: calc(var(--icon-size) * 1.2 + var(--dock-offset-right, 0px));
transform: translateY(calc(var(--icon-size) * -0.1));
}
.dock-icon:has(+ .dock-icon:hover) {
width: calc(var(--icon-size) * 1.4 + var(--dock-offset-left, 0px));
height: calc(var(--icon-size) * 1.4 + var(--dock-offset-left, 0px));
transform: translateY(calc(var(--icon-size) * -0.2));
}
.dock-icon:has(+ .dock-icon + .dock-icon:hover) {
width: calc(var(--icon-size) * 1.2 + var(--dock-offset-left, 0px));
height: calc(var(--icon-size) * 1.2 + var(--dock-offset-left, 0px));
transform: translateY(calc(var(--icon-size) * -0.1));
}
.dock-icon:hover {
width: calc(var(--icon-size) * 1.6);
height: calc(var(--icon-size) * 1.6);
transform: translateY(calc(var(--icon-size) * -0.3));
}
`}</style>
<li
ref={ref}
style={{
transition: "all cubic-bezier(0.23, 1, 0.32, 1) 200ms",
"--icon-size": `${iconSize}px`,
} as React.CSSProperties}
onMouseMove={handleIconHover}
className={cn(
"dock-icon group/li flex cursor-pointer items-end justify-center transition-all duration-200 ease-out",
"h-[var(--icon-size)] w-[var(--icon-size)]",
className
)}
>
<a href={href} className="relative group/icon block w-full h-full">
<div
className="relative aspect-square w-full h-full rounded-[22%] overflow-hidden shadow-lg hover:shadow-2xl transition-shadow duration-200 flex items-center justify-center"
style={{
background: color,
}}
>
<div className="text-white" style={{ fontSize: iconSize * 0.4 }}>
{icon}
</div>
</div>
{/* Tooltip */}
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 opacity-0 group-hover/li:opacity-100 transition-opacity duration-200 pointer-events-none z-50">
<div className="bg-black/80 backdrop-blur-sm text-white text-sm px-2 py-1 rounded whitespace-nowrap">
{name}
</div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-black/80"></div>
</div>
{/* Active indicator dot */}
<div className="absolute -bottom-1 left-1/2 transform -translate-x-1/2 w-1 h-1 bg-white rounded-full opacity-70 shadow-sm"></div>
</a>
</li>
</>
);
}
// Dock Component
interface DockProps {
className?: string;
children: React.ReactNode;
maxAdditionalSize?: number;
iconSize?: number;
}
function Dock({
className,
children,
maxAdditionalSize = 8,
iconSize = 60,
}: DockProps) {
const dockRef = useRef<HTMLDivElement | null>(null);
const handleIconHover = (e: React.MouseEvent<HTMLLIElement>) => {
if (!dockRef.current) return;
const mousePos = e.clientX;
const iconPosLeft = e.currentTarget.getBoundingClientRect().left;
const iconWidth = e.currentTarget.getBoundingClientRect().width;
const cursorDistance = (mousePos - iconPosLeft) / iconWidth;
const offsetPixels = scaleValue(
cursorDistance,
[0, 1],
[maxAdditionalSize * -1, maxAdditionalSize]
);
dockRef.current.style.setProperty(
"--dock-offset-left",
`${offsetPixels * -1}px`
);
dockRef.current.style.setProperty(
"--dock-offset-right",
`${offsetPixels}px`
);
};
return (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50">
<nav
ref={dockRef}
role="navigation"
aria-label="Dock"
className={cn(
"backdrop-blur-lg bg-white/10 border border-white/20 rounded-2xl p-2 shadow-2xl",
className
)}
style={{
background:
"linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)",
backdropFilter: "blur(20px)",
boxShadow:
"0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.2)",
}}
>
<ul className="flex items-end gap-1">
{React.Children.map(children, (child) =>
React.isValidElement<DockIconProps>(child)
? React.cloneElement(child as React.ReactElement<DockIconProps>, {
handleIconHover,
iconSize,
})
: child
)}
</ul>
</nav>
</div>
);
}
// Main Demo Component
export default function MacOSDockDemo() {
const apps = [
{
name: "Chrome",
icon: <Chrome />,
href: "#",
color: "linear-gradient(135deg, #4285f4 0%, #34a853 100%)",
},
{
name: "VS Code",
icon: <Code />,
href: "#",
color: "linear-gradient(135deg, #007acc 0%, #005a9e 100%)",
},
{
name: "Spotify",
icon: <Music />,
href: "#",
color: "linear-gradient(135deg, #1db954 0%, #1ed760 100%)",
},
{
name: "Messages",
icon: <MessageCircle />,
href: "#",
color: "linear-gradient(135deg, #007aff 0%, #5856d6 100%)",
},
{
name: "Mail",
icon: <Mail />,
href: "#",
color: "linear-gradient(135deg, #ff3b30 0%, #ff9500 100%)",
},
{
name: "Calendar",
icon: <Calendar />,
href: "#",
color: "linear-gradient(135deg, #ff3b30 0%, #ff2d92 100%)",
},
{
name: "Photos",
icon: <Camera />,
href: "#",
color: "linear-gradient(135deg, #ffcc02 0%, #ff9500 100%)",
},
{
name: "Finder",
icon: <Folder />,
href: "#",
color: "linear-gradient(135deg, #007aff 0%, #00d4ff 100%)",
},
{
name: "Terminal",
icon: <Terminal />,
href: "#",
color: "linear-gradient(135deg, #2d3748 0%, #4a5568 100%)",
},
{
name: "Safari",
icon: <Globe />,
href: "#",
color: "linear-gradient(135deg, #007aff 0%, #00c7be 100%)",
},
{
name: "Notes",
icon: <FileText />,
href: "#",
color: "linear-gradient(135deg, #ffcc02 0%, #ff9500 100%)",
},
{
name: "Settings",
icon: <Settings />,
href: "#",
color: "linear-gradient(135deg, #8e8e93 0%, #636366 100%)",
},
];
return (
<div className="min-h-screen bg-gradient-to-br from-blue-400 via-purple-500 to-pink-500 relative overflow-hidden w-full">
{/* Animated background elements */}
<div className="absolute inset-0">
<div className="absolute top-10 left-10 w-32 h-32 bg-white/10 rounded-full blur-xl animate-pulse"></div>
<div className="absolute top-1/2 right-20 w-24 h-24 bg-yellow-300/20 rounded-full blur-lg animate-bounce"></div>
<div className="absolute bottom-20 left-1/4 w-40 h-40 bg-pink-300/10 rounded-full blur-2xl animate-pulse"></div>
<div className="absolute top-1/4 left-1/2 w-20 h-20 bg-green-300/15 rounded-full blur-lg animate-pulse delay-1000"></div>
<div className="absolute bottom-1/3 right-1/3 w-28 h-28 bg-blue-300/10 rounded-full blur-xl animate-bounce delay-500"></div>
</div>
{/* Desktop content */}
<div className="relative z-10 p-8">
<h1 className="text-white text-6xl font-thin mb-4 drop-shadow-lg">
macOS Dock
</h1>
<p className="text-white/80 text-lg font-light max-w-md drop-shadow mb-8">
Hover over the dock icons to see the beautiful macOS-style
magnification effect in action. Built with Lucide React icons.
</p>
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-4 max-w-lg">
<h3 className="text-white font-medium mb-2">Features:</h3>
<ul className="text-white/70 text-sm space-y-1">
<li>• Smooth magnification animations</li>
<li>• Neighbor icon scaling effects</li>
<li>• Glassmorphism design</li>
<li>• Hover tooltips</li>
<li>• Active indicators</li>
<li>• Responsive and accessible</li>
</ul>
</div>
</div>
{/* Dock */}
<Dock>
{apps.map((app, index) => (
<DockIcon
key={index}
name={app.name}
icon={app.icon}
href={app.href}
color={app.color}
/>
))}
</Dock>
</div>
);
}