- Энциклопедия 20 видов грибов Крыма с детальными описаниями - Интерактивный календарь грибника по месяцам - Справочник: правила сбора, первая помощь, кулинария - Поиск и фильтрация по съедобности и сезону - Адаптивный дизайн, природная цветовая палитра - Docker-конфигурация для деплоя Tech: Next.js 15, TypeScript, Tailwind CSS 4, React 19 Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import Link from 'next/link';
|
||
import { usePathname } from 'next/navigation';
|
||
import { Menu, X, TreePine } from 'lucide-react';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
const navigation = [
|
||
{ name: 'Главная', href: '/' },
|
||
{ name: 'Энциклопедия', href: '/encyclopedia' },
|
||
{ name: 'Календарь', href: '/calendar' },
|
||
{ name: 'Справочник', href: '/guide' },
|
||
];
|
||
|
||
export function Header() {
|
||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||
const pathname = usePathname();
|
||
|
||
return (
|
||
<header className="sticky top-0 z-50 border-b border-border bg-white/80 backdrop-blur-md">
|
||
<nav className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
|
||
<Link href="/" className="flex items-center gap-2.5 group">
|
||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-forest-600 text-white shadow-sm transition-transform group-hover:scale-105">
|
||
<TreePine className="h-5 w-5" />
|
||
</div>
|
||
<div>
|
||
<span className="block text-lg font-bold leading-tight text-forest-900">
|
||
Грибы Крыма
|
||
</span>
|
||
<span className="block text-[11px] font-medium leading-tight text-muted-foreground tracking-wide">
|
||
Энциклопедия грибника
|
||
</span>
|
||
</div>
|
||
</Link>
|
||
|
||
{/* Desktop nav */}
|
||
<div className="hidden md:flex md:items-center md:gap-1">
|
||
{navigation.map((item) => {
|
||
const isActive = pathname === item.href ||
|
||
(item.href !== '/' && pathname.startsWith(item.href));
|
||
return (
|
||
<Link
|
||
key={item.name}
|
||
href={item.href}
|
||
className={cn(
|
||
'rounded-lg px-4 py-2 text-sm font-medium transition-colors',
|
||
isActive
|
||
? 'bg-forest-50 text-forest-700'
|
||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||
)}
|
||
>
|
||
{item.name}
|
||
</Link>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Mobile menu button */}
|
||
<button
|
||
type="button"
|
||
className="md:hidden rounded-lg p-2 text-muted-foreground hover:bg-muted"
|
||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||
aria-label="Toggle menu"
|
||
>
|
||
{mobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||
</button>
|
||
</nav>
|
||
|
||
{/* Mobile menu */}
|
||
{mobileMenuOpen && (
|
||
<div className="md:hidden border-t border-border bg-white animate-fade-in">
|
||
<div className="space-y-1 px-4 py-3">
|
||
{navigation.map((item) => {
|
||
const isActive = pathname === item.href ||
|
||
(item.href !== '/' && pathname.startsWith(item.href));
|
||
return (
|
||
<Link
|
||
key={item.name}
|
||
href={item.href}
|
||
onClick={() => setMobileMenuOpen(false)}
|
||
className={cn(
|
||
'block rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||
isActive
|
||
? 'bg-forest-50 text-forest-700'
|
||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||
)}
|
||
>
|
||
{item.name}
|
||
</Link>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</header>
|
||
);
|
||
}
|