fix(nav): replace NavigationMenu submenus with DropdownMenu

The Radix NavigationMenu uses a single shared viewport centered under
the full nav list. Opening a right-side submenu (Analyzer, Admin)
positioned the dropdown panel far to the left, so moving the mouse to
reach it crossed other triggers and closed it mid-way.

Switch all items-with-children to DropdownMenu, which positions each
panel directly under its own trigger. Keeps the same visual style
(navigationMenuTriggerStyle, ChevronDown, item layout with icon +
description) and active-state highlighting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lorentz 2026-05-03 11:18:52 -04:00
parent 370125d249
commit 2d4a546a8f

View file

@ -17,16 +17,21 @@ import {
Brain,
Search,
AlertTriangle,
ChevronDown,
} from 'lucide-react';
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { ThemeToggle } from '@/components/theme-toggle';
import { StatusIndicator } from '@/components/navigation/status-indicator';
@ -242,57 +247,57 @@ export function AppNavigation() {
return (
<NavigationMenuItem key={item.title}>
{item.children ? (
<>
<NavigationMenuTrigger
className={cn(
'h-9 px-4 py-2',
childActive && cn(activeRule, 'text-foreground'),
)}
>
{item.icon && <item.icon className="w-4 h-4 mr-2" />}
{item.title}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid gap-1 p-2 min-w-[320px] max-w-[440px]">
{item.children.map((child) => {
const active = isActive(child.href);
return (
<li key={child.title}>
<NavigationMenuLink asChild>
<Link
href={child.href || '#'}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
navigationMenuTriggerStyle(),
'h-9 px-4 py-2 group',
childActive && cn(activeRule, 'text-foreground'),
)}
>
{item.icon && <item.icon className="w-4 h-4 mr-2" />}
{item.title}
<ChevronDown className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" aria-hidden="true" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-2 min-w-[320px] max-w-[440px]">
{item.children.map((child) => {
const active = isActive(child.href);
return (
<DropdownMenuItem key={child.title} asChild className="p-0 focus:bg-transparent">
<Link
href={child.href || '#'}
className={cn(
'flex w-full items-start gap-3 rounded-sm px-3 py-2 text-sm leading-none no-underline outline-none transition-colors cursor-pointer',
'hover:bg-accent/40 focus:bg-accent/40',
active && 'bg-primary/10 text-primary',
)}
>
{child.icon && (
<child.icon
className={cn(
'flex items-start gap-3 rounded-sm px-3 py-2 leading-none no-underline outline-none transition-colors',
'hover:bg-accent/40 focus:bg-accent/40',
active && 'bg-primary/10 text-primary',
'w-4 h-4 mt-0.5 shrink-0',
active ? 'text-primary' : 'text-muted-foreground',
)}
>
{child.icon && (
<child.icon
className={cn(
'w-4 h-4 mt-0.5 shrink-0',
active ? 'text-primary' : 'text-muted-foreground',
)}
/>
)}
<div className="min-w-0 flex-1">
<div className={cn('text-sm font-medium leading-none', active && 'text-primary')}>
{child.title}
</div>
{child.description && (
<p className="mt-1 line-clamp-2 text-xs leading-snug text-muted-foreground">
{child.description}
</p>
)}
</div>
</Link>
</NavigationMenuLink>
</li>
);
})}
</ul>
</NavigationMenuContent>
</>
/>
)}
<div className="min-w-0 flex-1">
<div className={cn('font-medium leading-none', active && 'text-primary')}>
{child.title}
</div>
{child.description && (
<p className="mt-1 line-clamp-2 text-xs leading-snug text-muted-foreground">
{child.description}
</p>
)}
</div>
</Link>
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
) : (
<Link href={item.href || '#'} legacyBehavior passHref>
<NavigationMenuLink