Navbar
Animated expanding navbar that reveals links on scroll or hover with a smooth width and opacity transition inspired by Kumail Nanji.
Scroll down or hover to reveal the navbar
"use client";
import { useState, useEffect, useRef } from "react";
import {
useMotionValue,
useTransform,
useMotionValueEvent,
useScroll,
} from "motion/react";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion } from "motion/react";
import { HugeiconsIcon } from "@hugeicons/react";
import { CloudIcon } from "@hugeicons/core-free-icons";
const ROUTES = [
{ title: "Home", href: "/" },
{ title: "About", href: "/about" },
{ title: "Benefits", href: "/contact" },
{ title: "Labs", href: "/contact" },
{ title: "contact", href: "/contact" },
];
const Nav = () => {
const [isHidden, setIsHidden] = useState(false);
const [height, setHeight] = useState(0);
const [mounted, setMounted] = useState(false);
const { scrollY } = useScroll();
const lastYRef = useRef(0);
const navbarWidth = useMotionValue(65);
const routesOpacity = useTransform(navbarWidth, [65, 500], [0, 1]);
useEffect(() => {
setMounted(true);
}, []);
useMotionValueEvent(scrollY, "change", (y) => {
const difference = y - lastYRef.current;
if (difference > 50) {
setIsHidden(false);
} else {
setIsHidden(true);
}
setHeight(difference);
});
const firstNavVariants = {
hidden: {
width: 65,
background: "black",
},
vissible: {
width: "auto",
background: "black",
},
};
if (!mounted) return null;
return (
<motion.header
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
className="h-full w-full center md:block hidden"
transition={{
default: {
ease: "easeInOut",
},
delay: 0.2,
duration: 0.4,
}}
>
<motion.nav
animate={height > 50 && !isHidden ? "vissible" : "hidden"}
whileHover="vissible"
initial="hidden"
exit="hidden"
onFocusCapture={() => setIsHidden(false)}
variants={firstNavVariants}
transition={{
default: {
ease: "easeInOut",
},
}}
className={cn(
"fixed text-white z-500 h-16.25 min-w-16.25 backdrop-blur top-10 left-0 right-0 mx-auto overflow-hidden rounded-lg flex items-center max-w-fit ",
)}
style={{
width: navbarWidth,
}}
>
<div
className={cn(
"size-16.25 min-w-16.25 flex items-center justify-center",
)}
>
<HugeiconsIcon icon={CloudIcon} className="size-8" />
</div>
<AnimatePresence>
<div className="mr-10" key={"09q9q0"} />
{(height >= 0 || !isHidden) && (
<motion.ul className="flex items-center gap-10 list-none! mt-0!">
{ROUTES.map((route, index) => (
<motion.li
key={route.title}
className="text-white text-lg font-medium relative"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{
opacity: routesOpacity,
marginRight: index === ROUTES.length - 1 ? "20px" : "0px",
}}
>
{route.title}
</motion.li>
))}
</motion.ul>
)}
</AnimatePresence>
</motion.nav>
</motion.header>
);
};
const SkalewayNav = () => {
return (
<div className="p-10 ">
<Nav />
<div className="flex items-center justify-center">
<h1 className="text-muted-foreground text-xl text-center font-semibold">
Scroll down or hover to reveal the navbar
</h1>
</div>
</div>
);
};
export default SkalewayNav;