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:
parent
3dd0caf621
commit
0730ae6f42
2 changed files with 271 additions and 133 deletions
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue