Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/layout/mobile-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function MobileMenu({ navItems, isOpen, onClose }: MobileMenuProps) {
>
<Link
href={item.href}
onClick={() => window.scrollTo(0, 0)}
className={cn(
"block py-4 text-2xl font-medium transition-colors",
isActive(item.href)
Expand Down
45 changes: 28 additions & 17 deletions components/layout/top-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ export function TopNav() {
const pathname = usePathname()
const router = useRouter()
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false)
const navContainerRef = React.useRef<HTMLDivElement>(null)
const [indicator, setIndicator] = React.useState({ left: 0, width: 0, opacity: 0 })

const isActive = (href: string) => {
if (href === "/") return pathname === "/"
return pathname.startsWith(href)
}

const activeIndex = navItems.findIndex(item => isActive(item.href))

// Measure active tab position (scroll-independent via offsetLeft/offsetWidth)
React.useLayoutEffect(() => {
const container = navContainerRef.current
if (!container || activeIndex === -1) return
const links = container.querySelectorAll<HTMLAnchorElement>("a")
const activeLink = links[activeIndex]
if (activeLink) {
setIndicator({ left: activeLink.offsetLeft, width: activeLink.offsetWidth, opacity: 1 })
}
}, [activeIndex])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indicator persists when no nav item is active

Low Severity

When activeIndex is -1 (e.g., on a 404 page or a route not in navItems), the useLayoutEffect returns early without updating the indicator state. If the indicator was previously visible (opacity: 1), it remains highlighted on the last active tab. The old layoutId approach correctly removed the indicator by not rendering the motion.span at all. The early return needs to also set opacity: 0 to hide the indicator.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indicator position stale after font swap or resize

Low Severity

The useLayoutEffect only re-measures when activeIndex changes. Since the site uses Google Fonts with display: "swap", the initial measurement may use fallback font metrics. When the custom font (Montserrat) loads and text reflows, offsetLeft/offsetWidth values change but the effect doesn't re-run, leaving the indicator misaligned. The previous layoutId approach handled layout changes automatically.

Fix in Cursor Fix in Web


React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand All @@ -31,6 +51,7 @@ export function TopNav() {

const num = parseInt(e.key)
if (num >= 1 && num <= navItems.length) {
window.scrollTo(0, 0)
router.push(navItems[num - 1].href)
}
}
Expand All @@ -39,11 +60,6 @@ export function TopNav() {
return () => window.removeEventListener("keydown", handleKeyDown)
}, [router])

const isActive = (href: string) => {
if (href === "/") return pathname === "/"
return pathname.startsWith(href)
}

const closeMobileMenu = React.useCallback(() => {
setMobileMenuOpen(false)
}, [])
Expand All @@ -55,29 +71,24 @@ export function TopNav() {
<div className="flex h-14 items-center justify-center">
{/* Desktop Navigation */}
<TooltipProvider delayDuration={300}>
<div className="hidden md:flex items-center gap-1">
<div ref={navContainerRef} className="relative hidden md:flex items-center gap-1">
<motion.span
className="absolute top-0 bottom-0 bg-accent/10 rounded-md"
animate={indicator}
transition={{ type: "spring", stiffness: 350, damping: 30 }}
/>
{navItems.map((item, index) => (
<ShortcutTooltip key={item.href} shortcut={String(index + 1)}>
<Link
href={item.href}
onClick={() => window.scrollTo(0, 0)}
className={cn(
"relative px-3 py-1.5 text-sm whitespace-nowrap transition-colors rounded-md",
isActive(item.href)
? "text-accent"
: "text-muted-foreground hover:text-foreground"
)}
>
{isActive(item.href) && (
<motion.span
layoutId="nav-indicator"
className="absolute inset-0 bg-accent/10 rounded-md"
transition={{
type: "spring",
stiffness: 350,
damping: 30,
}}
/>
)}
<span className="relative z-10">{item.title}</span>
</Link>
</ShortcutTooltip>
Expand Down