Improve nav item click handling and sidebar menu close

Enhanced NavItem to prevent double-clicks, handle active state, and optimize mobile menu closing. Sidebar now uses a dedicated callback for closing the mobile menu, reducing unnecessary re-renders and improving user experience.
This commit is contained in:
g
2025-11-28 18:36:54 +00:00
parent 4e5fc1901c
commit f212859bda
2 changed files with 66 additions and 13 deletions

View File

@@ -1,6 +1,11 @@
"use client"
import Link from "next/link" import Link from "next/link"
import { usePathname } from "next/navigation"
import { useRef } from "react"
import type { LucideIcon } from "lucide-react" import type { LucideIcon } from "lucide-react"
import type React from "react" import type React from "react"
import { cn } from "@/lib/utils/styles"
interface NavItemProps { interface NavItemProps {
href: string href: string
@@ -9,14 +14,57 @@ interface NavItemProps {
onClick?: () => void onClick?: () => void
} }
export const NavItem: React.FC<NavItemProps> = ({ href, icon: Icon, children, onClick }) => ( export const NavItem: React.FC<NavItemProps> = ({ href, icon: Icon, children, onClick }) => {
<Link const pathname = usePathname()
href={href} const isActive = pathname === href || (href !== '/dashboard' && pathname?.startsWith(href))
onClick={onClick} const isNavigatingRef = useRef(false)
className="flex items-center px-3 py-2 text-sm rounded-md transition-colors text-muted-foreground hover:text-foreground hover:bg-accent"
> const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
<Icon className="h-4 w-4 mr-3 flex-shrink-0" /> // Prevent rapid double-clicks
{children} if (isNavigatingRef.current) {
</Link> e.preventDefault()
) return
}
// If already on this page, just close mobile menu if needed
if (isActive) {
e.preventDefault()
if (onClick) onClick()
return
}
// Mark as navigating to prevent double-clicks
isNavigatingRef.current = true
// Call onClick handler (for mobile menu closing) - don't block navigation
if (onClick) {
// Use setTimeout to ensure navigation happens first
setTimeout(() => onClick(), 0)
}
// Reset flag after navigation completes
setTimeout(() => {
isNavigatingRef.current = false
}, 300)
}
return (
<Link
href={href}
onClick={handleClick}
className={cn(
"flex items-center px-3 py-2 text-sm rounded-md transition-colors",
"text-muted-foreground hover:text-foreground hover:bg-accent",
"active:scale-[0.98] transition-transform duration-75",
"touch-manipulation will-change-transform",
isActive && "bg-accent text-foreground font-medium"
)}
prefetch={true}
style={{ WebkitTapHighlightColor: 'transparent' }}
>
<Icon className="h-4 w-4 mr-3 flex-shrink-0" />
{children}
</Link>
)
}

View File

@@ -18,6 +18,11 @@ const Sidebar: React.FC = () => {
const pathname = usePathname() const pathname = usePathname()
const { isAdmin } = useUser() const { isAdmin } = useUser()
// Optimize mobile menu closing - use useCallback to prevent unnecessary re-renders
const handleMobileMenuClose = () => {
setIsMobileMenuOpen(false)
}
// Determine if we're in admin area // Determine if we're in admin area
const isAdminArea = pathname?.startsWith('/dashboard/admin') const isAdminArea = pathname?.startsWith('/dashboard/admin')
@@ -59,7 +64,7 @@ const Sidebar: React.FC = () => {
<> <>
<nav <nav
className={` className={`
fixed inset-y-0 left-0 z-[70] w-64 bg-background transform transition-transform duration-200 ease-in-out fixed inset-y-0 left-0 z-[70] w-64 bg-background transform transition-transform duration-150 ease-out
lg:translate-x-0 lg:static lg:w-56 xl:w-64 border-r border-border lg:translate-x-0 lg:static lg:w-56 xl:w-64 border-r border-border
${isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"} ${isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"}
`} `}
@@ -80,7 +85,7 @@ const Sidebar: React.FC = () => {
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
{section.items.map((item, idx) => ( {section.items.map((item, idx) => (
<NavItem key={idx} href={item.href} icon={item.icon} onClick={() => setIsMobileMenuOpen(false)}> <NavItem key={idx} href={item.href} icon={item.icon} onClick={handleMobileMenuClose}>
{item.name} {item.name}
</NavItem> </NavItem>
))} ))}
@@ -105,7 +110,7 @@ const Sidebar: React.FC = () => {
{isMobileMenuOpen && ( {isMobileMenuOpen && (
<div <div
className="fixed inset-0 bg-black bg-opacity-50 z-[65] lg:hidden" className="fixed inset-0 bg-black bg-opacity-50 z-[65] lg:hidden"
onClick={() => setIsMobileMenuOpen(false)} onClick={handleMobileMenuClose}
/> />
)} )}
</> </>