181 lines
5.9 KiB
TypeScript
181 lines
5.9 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* Subsection Navigation Component
|
|
*
|
|
* Reusable navigation bar for documentation and other subsections
|
|
* Features:
|
|
* - Dark nav bar with decorative wave line
|
|
* - Active state indicator (purple color)
|
|
* - Responsive (hamburger menu on mobile)
|
|
* - Three-column layout matching docs structure
|
|
* - Customizable left/right slots
|
|
*
|
|
* Uses ThreeColumnLayout for consistent alignment with docs pages.
|
|
* Columns align with docs layout at all screen widths.
|
|
*
|
|
* Usage:
|
|
* <SubsectionNav
|
|
* items={[...]}
|
|
* currentPath="/docs/final"
|
|
* leftSlot={<Logo />}
|
|
* rightSlot={<UserMenu />}
|
|
* />
|
|
*/
|
|
|
|
import { useState, ReactNode } from 'react';
|
|
import { ThreeColumnLayout } from '@/components/layout/ThreeColumnLayout';
|
|
|
|
interface NavItem {
|
|
label: string;
|
|
href: string;
|
|
}
|
|
|
|
interface SubsectionNavProps {
|
|
items: NavItem[];
|
|
currentPath: string;
|
|
ctaText?: string;
|
|
ctaHref?: string;
|
|
onCtaClick?: () => void;
|
|
/** Optional content for left column (w-64, hidden until lg) */
|
|
leftSlot?: ReactNode;
|
|
/** Optional content for right column (w-56, hidden until xl) */
|
|
rightSlot?: ReactNode;
|
|
}
|
|
|
|
export const SubsectionNav = ({ items, currentPath, leftSlot, rightSlot }: SubsectionNavProps) => {
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
|
|
const isActive = (href: string) => currentPath.startsWith(href);
|
|
|
|
return (
|
|
<nav className="sticky top-0 z-40 bg-slate-950/80 backdrop-blur-sm border-b border-white/10 relative">
|
|
{/* Three-Column Layout */}
|
|
<ThreeColumnLayout
|
|
left={leftSlot}
|
|
center={
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="flex items-center justify-between h-12">
|
|
{/* Desktop Navigation Items */}
|
|
<div className="hidden md:flex items-center gap-8">
|
|
{items.map((item) => {
|
|
const active = isActive(item.href);
|
|
return (
|
|
<a
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`
|
|
py-3 text-sm font-medium transition-colors
|
|
${active ? 'text-purple-400' : 'text-gray-400 hover:text-white'}
|
|
`}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Mobile Menu Button */}
|
|
<div className="md:hidden flex items-center ml-auto">
|
|
<button
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
className="p-2 text-gray-400 hover:text-white transition-colors"
|
|
aria-label="Toggle menu"
|
|
>
|
|
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
{mobileMenuOpen ? (
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
) : (
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M4 6h16M4 12h16M4 18h16"
|
|
/>
|
|
)}
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* Right Slot - Absolutely Positioned */}
|
|
{rightSlot && (
|
|
<div className="absolute top-0 right-0 h-12 flex items-center pr-0 hidden xl:flex overflow-visible">
|
|
{rightSlot}
|
|
</div>
|
|
)}
|
|
|
|
{/* Decorative Wave Line */}
|
|
<div className="absolute bottom-0 left-0 right-0 h-px overflow-hidden">
|
|
<svg
|
|
className="absolute inset-0 w-full h-full"
|
|
viewBox="0 0 1200 2"
|
|
preserveAspectRatio="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<defs>
|
|
<linearGradient id="wave-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stopColor="rgb(147, 51, 234)" stopOpacity="0.3" />
|
|
<stop offset="50%" stopColor="rgb(8, 145, 178)" stopOpacity="0.5" />
|
|
<stop offset="100%" stopColor="rgb(147, 51, 234)" stopOpacity="0.3" />
|
|
</linearGradient>
|
|
</defs>
|
|
<path
|
|
d="M0,1 Q300,0 600,1 T1200,1"
|
|
stroke="url(#wave-gradient)"
|
|
strokeWidth="1"
|
|
fill="none"
|
|
className="animate-pulse"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Mobile Menu Overlay */}
|
|
{mobileMenuOpen && (
|
|
<div className="md:hidden border-t border-white/10 bg-slate-900/95 backdrop-blur-sm">
|
|
<div className="px-6 py-4">
|
|
{/* ApiKeyWidget - Mobile */}
|
|
{rightSlot && (
|
|
<>
|
|
<div className="flex justify-end mb-3">
|
|
{rightSlot}
|
|
</div>
|
|
{/* Visual separator */}
|
|
<div className="border-t border-white/10 mb-3" />
|
|
</>
|
|
)}
|
|
|
|
{/* Navigation items */}
|
|
<div className="space-y-2">
|
|
{items.map((item) => {
|
|
const active = isActive(item.href);
|
|
return (
|
|
<a
|
|
key={item.href}
|
|
href={item.href}
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
className={`
|
|
block px-4 py-3 rounded-lg text-sm font-medium transition-colors
|
|
${active ? 'bg-purple-500/10 text-purple-400' : 'text-gray-400 hover:text-white hover:bg-white/5'}
|
|
`}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</nav>
|
|
);
|
|
};
|