feat: reorganize navigation menu for better UX

- Group links into Developers and Resources dropdowns
- Primary nav shows only Tools and Playground
- Segment auth section (Dashboard/Agents when logged in, Sign In when not)
- Mobile menu uses same structure with section headers
- Add descriptions to help users find what they need

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2026-01-07 17:58:01 +10:00
parent 3dd0caf621
commit 0730ae6f42
2 changed files with 271 additions and 133 deletions

View file

@ -5,10 +5,107 @@ import { Button } from '@tpmjs/ui/Button/Button';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import Link from 'next/link';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { MobileMenu } from './MobileMenu';
import { ThemeToggle } from './ThemeToggle';
interface DropdownItem {
href: string;
label: string;
description?: string;
external?: boolean;
}
interface NavDropdownProps {
label: string;
items: DropdownItem[];
}
function NavDropdown({ label, items }: NavDropdownProps): React.ReactElement {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className="relative" ref={dropdownRef}>
<Button
variant="ghost"
size="sm"
className="text-foreground hover:text-foreground flex items-center gap-1"
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-haspopup="true"
>
{label}
<Icon
icon="chevronDown"
size="sm"
className={`transition-transform ${isOpen ? 'rotate-180' : ''}`}
/>
</Button>
{isOpen && (
<div className="absolute top-full left-0 mt-1 w-56 bg-background border border-border rounded-lg shadow-lg py-1 z-50">
{items.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between px-4 py-2 text-sm text-foreground hover:bg-surface transition-colors"
onClick={() => setIsOpen(false)}
>
<div>
<div className="font-medium">{item.label}</div>
{item.description && (
<div className="text-xs text-foreground-tertiary">{item.description}</div>
)}
</div>
<Icon icon="externalLink" size="sm" className="text-foreground-tertiary" />
</a>
) : (
<Link
key={item.href}
href={item.href}
className="block px-4 py-2 text-sm text-foreground hover:bg-surface transition-colors"
onClick={() => setIsOpen(false)}
>
<div className="font-medium">{item.label}</div>
{item.description && (
<div className="text-xs text-foreground-tertiary">{item.description}</div>
)}
</Link>
)
)}
</div>
)}
</div>
);
}
const developerItems: DropdownItem[] = [
{ href: '/docs', label: 'Documentation', description: 'Guides and tutorials' },
{ href: '/sdk', label: 'SDK', description: 'Build with our SDK' },
{ href: '/spec', label: 'Specification', description: 'TPMJS tool format' },
{ href: '/integrations', label: 'Integrations', description: 'Connect your tools' },
];
const resourceItems: DropdownItem[] = [
{ href: '/how-it-works', label: 'How It Works', description: 'Learn the basics' },
{ href: '/faq', label: 'FAQ', description: 'Common questions' },
{ href: '/changelog', label: 'Changelog', description: 'Latest updates' },
{ href: '/stats', label: 'Stats', description: 'Platform metrics' },
];
/**
* Shared application header used across all pages
*/
@ -32,8 +129,8 @@ export function AppHeader(): React.ReactElement {
actions={
<>
{/* Desktop Navigation - hidden on mobile */}
<div className="hidden md:flex items-center gap-4">
{/* Core Product Links */}
<div className="hidden lg:flex items-center gap-1">
{/* Primary Links */}
<Link href="/tool/tool-search">
<Button
variant="ghost"
@ -43,70 +140,26 @@ export function AppHeader(): React.ReactElement {
Tools
</Button>
</Link>
<Link href="/dashboard/agents">
<Button
variant="ghost"
size="sm"
className="text-foreground hover:text-foreground font-semibold"
>
Agents
</Button>
</Link>
<Link href="/how-it-works">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
How It Works
</Button>
</Link>
<a href="https://playground.tpmjs.com" target="_blank" rel="noopener noreferrer">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Playground
</Button>
</a>
<Link href="/integrations">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Integrations
</Button>
</Link>
{/* Dropdown Menus */}
<NavDropdown label="Developers" items={developerItems} />
<NavDropdown label="Resources" items={resourceItems} />
{/* Separator */}
<span className="text-foreground-tertiary">|</span>
<span className="text-foreground-tertiary mx-2">|</span>
{/* Developer Section */}
<Link href="/docs">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Docs
</Button>
</Link>
<Link href="/sdk">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
SDK
</Button>
</Link>
<Link href="/spec">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Spec
</Button>
</Link>
<Link href="/changelog">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Changelog
</Button>
</Link>
<Link href="/faq">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
FAQ
</Button>
</Link>
<Link href="/stats">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Stats
</Button>
</Link>
{/* Social & Utilities */}
<a
href="https://discord.gg/KuJRBCn89c"
target="_blank"
rel="noopener noreferrer"
className="text-foreground hover:text-foreground transition-colors"
className="p-2 text-foreground hover:text-foreground-secondary transition-colors"
aria-label="Join our Discord"
>
<Icon icon="discord" size="md" />
</a>
@ -114,21 +167,36 @@ export function AppHeader(): React.ReactElement {
href="https://github.com/tpmjs/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground hover:text-foreground transition-colors"
className="p-2 text-foreground hover:text-foreground-secondary transition-colors"
aria-label="View on GitHub"
>
<Icon icon="github" size="md" />
</a>
<ThemeToggle />
{/* Auth Section - Segmented */}
<span className="text-foreground-tertiary mx-2">|</span>
{session ? (
<Link href="/dashboard">
<Button
variant="ghost"
size="sm"
className="text-foreground hover:text-foreground"
>
Dashboard
</Button>
</Link>
<>
<Link href="/dashboard">
<Button
variant="ghost"
size="sm"
className="text-foreground hover:text-foreground"
>
Dashboard
</Button>
</Link>
<Link href="/dashboard/agents">
<Button
variant="ghost"
size="sm"
className="text-foreground hover:text-foreground"
>
Agents
</Button>
</Link>
</>
) : (
<Link href="/sign-in">
<Button
@ -140,18 +208,20 @@ export function AppHeader(): React.ReactElement {
</Button>
</Link>
)}
{/* Primary CTA */}
<Link href="/publish">
<Button variant="default" size="sm">
Publish Tool
Publish
</Button>
</Link>
</div>
{/* Mobile Hamburger Button - shown on mobile only */}
{/* Mobile Hamburger Button - shown on mobile/tablet */}
<Button
variant="ghost"
size="sm"
className="flex md:hidden text-foreground"
className="flex lg:hidden text-foreground"
onClick={() => setMobileMenuOpen(true)}
aria-label="Open menu"
>

View file

@ -12,20 +12,42 @@ interface MobileMenuProps {
session: { user: { name: string; email: string } } | null;
}
const navLinks = [
// Core Product Links
{ href: '/tool/tool-search', label: 'Tools' },
{ href: '/dashboard/agents', label: 'Agents' },
{ href: '/how-it-works', label: 'How It Works' },
{ href: 'https://playground.tpmjs.com', label: 'Playground', external: true },
{ href: '/integrations', label: 'Integrations' },
// Developer Section
{ href: '/docs', label: 'Docs' },
{ href: '/sdk', label: 'SDK' },
{ href: '/spec', label: 'Spec' },
{ href: '/changelog', label: 'Changelog' },
{ href: '/faq', label: 'FAQ' },
{ href: '/stats', label: 'Stats' },
interface NavSection {
title: string;
links: { href: string; label: string; description?: string; external?: boolean }[];
}
const navSections: NavSection[] = [
{
title: 'Explore',
links: [
{ href: '/tool/tool-search', label: 'Tools', description: 'Browse all tools' },
{
href: 'https://playground.tpmjs.com',
label: 'Playground',
description: 'Try tools live',
external: true,
},
],
},
{
title: 'Developers',
links: [
{ href: '/docs', label: 'Documentation', description: 'Guides and tutorials' },
{ href: '/sdk', label: 'SDK', description: 'Build with our SDK' },
{ href: '/spec', label: 'Specification', description: 'TPMJS tool format' },
{ href: '/integrations', label: 'Integrations', description: 'Connect your tools' },
],
},
{
title: 'Resources',
links: [
{ href: '/how-it-works', label: 'How It Works', description: 'Learn the basics' },
{ href: '/faq', label: 'FAQ', description: 'Common questions' },
{ href: '/changelog', label: 'Changelog', description: 'Latest updates' },
{ href: '/stats', label: 'Stats', description: 'Platform metrics' },
],
},
];
const socialLinks = [
@ -94,36 +116,54 @@ export function MobileMenu({
</Button>
</div>
{/* Navigation links */}
<nav className="p-4">
<ul className="space-y-1">
{navLinks.map((link) => (
<li key={link.href}>
{link.external ? (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-3 py-3 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
{link.label}
<Icon icon="externalLink" size="sm" className="text-foreground-tertiary" />
</a>
) : (
<Link
href={link.href}
className="block px-3 py-3 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
{link.label}
</Link>
)}
</li>
))}
</ul>
{/* Navigation sections */}
<nav className="p-4 overflow-y-auto max-h-[calc(100vh-80px)]">
{navSections.map((section, index) => (
<div key={section.title}>
<h3 className="px-3 py-2 text-xs font-semibold text-foreground-tertiary uppercase tracking-wider">
{section.title}
</h3>
<ul className="space-y-1 mb-4">
{section.links.map((link) => (
<li key={link.href}>
{link.external ? (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
<div>
<div className="font-medium">{link.label}</div>
{link.description && (
<div className="text-xs text-foreground-tertiary">
{link.description}
</div>
)}
</div>
<Icon icon="externalLink" size="sm" className="text-foreground-tertiary" />
</a>
) : (
<Link
href={link.href}
className="block px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
<div className="font-medium">{link.label}</div>
{link.description && (
<div className="text-xs text-foreground-tertiary">{link.description}</div>
)}
</Link>
)}
</li>
))}
</ul>
{index < navSections.length - 1 && <div className="my-3 border-t border-border" />}
</div>
))}
{/* Divider */}
{/* Divider before utilities */}
<div className="my-4 border-t border-border" />
{/* Social links and theme toggle */}
@ -149,43 +189,71 @@ export function MobileMenu({
</div>
</div>
{/* Divider */}
{/* Divider before auth */}
<div className="my-4 border-t border-border" />
{/* Auth links */}
{/* Auth Section - Segmented based on login status */}
<h3 className="px-3 py-2 text-xs font-semibold text-foreground-tertiary uppercase tracking-wider">
Account
</h3>
{session ? (
<Link
href="/dashboard"
className="block px-3 py-3 text-foreground hover:bg-surface rounded-md transition-colors mb-4"
onClick={onClose}
>
Dashboard
</Link>
) : (
<div className="space-y-1 mb-4">
<Link
href="/sign-in"
className="block px-3 py-3 text-foreground hover:bg-surface rounded-md transition-colors"
href="/dashboard"
className="block px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
Sign In
<div className="font-medium">Dashboard</div>
<div className="text-xs text-foreground-tertiary">Your overview</div>
</Link>
<Link
href="/sign-up"
className="block px-3 py-3 text-foreground hover:bg-surface rounded-md transition-colors"
href="/dashboard/agents"
className="block px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
Sign Up
<div className="font-medium">My Agents</div>
<div className="text-xs text-foreground-tertiary">Manage your agents</div>
</Link>
<Link
href="/dashboard/collections"
className="block px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
<div className="font-medium">Collections</div>
<div className="text-xs text-foreground-tertiary">Organize your tools</div>
</Link>
<Link
href="/dashboard/settings/api-keys"
className="block px-3 py-2.5 text-foreground hover:bg-surface rounded-md transition-colors"
onClick={onClose}
>
<div className="font-medium">API Keys</div>
<div className="text-xs text-foreground-tertiary">Manage credentials</div>
</Link>
</div>
) : (
<div className="space-y-2 mb-4 px-3">
<Link href="/sign-in" onClick={onClose} className="block">
<Button variant="outline" size="md" className="w-full">
Sign In
</Button>
</Link>
<Link href="/sign-up" onClick={onClose} className="block">
<Button variant="ghost" size="md" className="w-full">
Create Account
</Button>
</Link>
</div>
)}
{/* Publish button */}
<Link href="/publish" onClick={onClose}>
<Button variant="default" size="lg" className="w-full">
Publish Tool
</Button>
</Link>
{/* Primary CTA */}
<div className="px-3 mt-4">
<Link href="/publish" onClick={onClose} className="block">
<Button variant="default" size="lg" className="w-full">
Publish Tool
</Button>
</Link>
</div>
</nav>
</div>
</>