From a18114da5df7fca88b06221e8dbdd0ad78ea1ee6 Mon Sep 17 00:00:00 2001 From: condyl Date: Tue, 20 Jan 2026 21:02:24 -0500 Subject: [PATCH] feat: animate active navbar pill --- src/components/sitewide/NavbarComponent.jsx | 157 ++++++++++++++++---- 1 file changed, 132 insertions(+), 25 deletions(-) diff --git a/src/components/sitewide/NavbarComponent.jsx b/src/components/sitewide/NavbarComponent.jsx index f51703f..0f5e924 100644 --- a/src/components/sitewide/NavbarComponent.jsx +++ b/src/components/sitewide/NavbarComponent.jsx @@ -1,6 +1,12 @@ -import { useContext, useState } from "react"; +import { + useContext, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; import ColorModeContext from "@/lib/contexts/sitewide/ColorModeContext"; -import { Link } from "react-router-dom"; +import { Link, useLocation } from "react-router-dom"; import { useIsMobile } from "@/lib/utils/screenSizeUtils"; import { BookOpen, @@ -20,26 +26,82 @@ import { SheetTrigger, } from "@/components/ui/sheet"; import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; const NavbarComponent = () => { const colorMode = useContext(ColorModeContext); const isMobile = useIsMobile(); const [drawerOpen, setDrawerOpen] = useState(false); + const location = useLocation(); + const desktopNavRef = useRef(null); + const desktopItemRefs = useRef([]); + const [indicatorStyle, setIndicatorStyle] = useState(null); const navItems = [ { label: "Generator", to: "/", icon: Home, + end: true, }, { label: "Guide", to: "/guide", icon: BookOpen, newTab: true, + end: true, }, ]; + const isActiveRoute = (item) => { + if (item.end) { + return location.pathname === item.to; + } + return location.pathname.startsWith(item.to); + }; + + const activeIndex = navItems.findIndex((item) => isActiveRoute(item)); + + const updateIndicatorFromIndex = (index) => { + if (index < 0) { + setIndicatorStyle(null); + return; + } + const navNode = desktopNavRef.current; + const itemNode = desktopItemRefs.current[index]; + if (!navNode || !itemNode) { + return; + } + const navRect = navNode.getBoundingClientRect(); + const itemRect = itemNode.getBoundingClientRect(); + setIndicatorStyle({ + left: itemRect.left - navRect.left, + top: itemRect.top - navRect.top, + width: itemRect.width, + height: itemRect.height, + }); + }; + + const resetIndicator = () => { + updateIndicatorFromIndex(activeIndex); + }; + + useLayoutEffect(() => { + if (isMobile) { + return; + } + resetIndicator(); + }, [isMobile, location.pathname]); + + useEffect(() => { + if (isMobile) { + return undefined; + } + const handleResize = () => resetIndicator(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, [isMobile, location.pathname]); + const drawerContent = (
@@ -51,25 +113,27 @@ const NavbarComponent = () => {
) : ( -
- {navItems.map((item) => ( - - ))} + + + ); + })}