Files
aragrp/components/header.tsx
T
alirez.brandCursor a47ec493c5 feat: add version history drawer with release notes
Introduce a footer-triggered Version History drawer with categorized release notes, hardcoded release data, and semver tracking at v0.3.0. Also ships blog blockquote/table styling, site-wide typography, home page image updates, header refinements, and related bug fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 02:46:20 +03:30

146 lines
7.1 KiB
TypeScript

'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Logo } from '@/components/logo'
import { Menu, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import React from 'react'
import { cn } from '@/lib/utils'
import { type } from '@/lib/typography'
import { ThemeToggle } from '@/components/theme-toggle'
const menuItems = [
{ name: 'صفحه اصلی', href: '/' },
{ name: 'دانشنامه', href: '/blog' },
{ name: 'راه حل‌ها', href: '/solutions' },
{ name: 'مشاوره سازمانی', href: '/consulting' },
// { name: 'قیمت‌گذاری', href: '/pricing' },
// { name: 'آزمایشگاه فیپ', href: '/playground' },
{ name: 'درباره ما', href: '/about' },
]
/** Outer gutter — matches hero image wrapper (`px-6 md:px-2`). */
const siteGutterClass = 'px-6 md:px-2 lg:px-6'
/** Bar width — matches hero image inner container (`max-w-5xl md:max-w-4xl`). */
const headerBarClass = 'mx-auto w-full max-w-5xl md:max-w-4xl lg:max-w-6xl'
function isLinkActive(pathname: string, href: string): boolean {
if (href === '/') return pathname === '/'
return pathname === href || pathname.startsWith(href + '/')
}
export const HeroHeader = () => {
const pathname = usePathname()
const [menuState, setMenuState] = React.useState(false)
const [isScrolled, setIsScrolled] = React.useState(false)
React.useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
React.useEffect(() => {
document.body.style.overflow = menuState ? 'hidden' : ''
return () => {
document.body.style.overflow = ''
}
}, [menuState])
return (
<header className="contents">
<nav
data-state={menuState ? 'active' : undefined}
aria-label="ناوبری اصلی"
className={cn(
'fixed inset-x-0 top-0 z-20 w-full pt-[max(0.5rem,env(safe-area-inset-top,0px))]',
siteGutterClass
)}>
<div
className={cn(
headerBarClass,
'mt-2 rounded-md border border-border/10 bg-background/30 backdrop-blur-md transition-[max-width,box-shadow,background-color] duration-300',
isScrolled && 'bg-background/20 shadow-lg lg:max-w-4xl'
)}>
<div className="relative flex h-14 items-center justify-between gap-3 px-3 sm:gap-4 sm:px-4 lg:h-16">
<div className="flex min-w-0 shrink items-center">
<Logo />
</div>
<div className="pointer-events-none absolute inset-x-0 hidden lg:flex lg:items-center lg:justify-center">
<ul className={cn("pointer-events-auto flex gap-1", type.nav)}>
{menuItems.map((item, index) => {
const active = isLinkActive(pathname ?? '', item.href)
return (
<li key={index}>
<Link
href={item.href}
className={cn(
'relative rounded-md p-2 px-4 text-muted-foreground transition-all duration-300 hover:bg-accent/20 hover:text-foreground',
active && 'bg-accent/40 text-foreground'
)}>
<span className="relative z-10">{item.name}</span>
</Link>
</li>
)
})}
</ul>
</div>
<div className="flex shrink-0 items-center gap-1 sm:gap-1.5">
<ThemeToggle />
<Button
type="button"
variant="ghost"
size="icon-lg"
onClick={() => setMenuState((open) => !open)}
aria-expanded={menuState}
aria-controls="mobile-nav"
aria-label={menuState ? 'بستن منو' : 'باز کردن منو'}
className="relative rounded-lg lg:hidden">
<Menu
aria-hidden
className="size-5 transition-all duration-200 in-data-[state=active]:rotate-180 in-data-[state=active]:scale-0 in-data-[state=active]:opacity-0"
/>
<X
aria-hidden
className="absolute inset-0 m-auto size-5 -rotate-180 scale-0 opacity-0 transition-all duration-200 in-data-[state=active]:rotate-0 in-data-[state=active]:scale-100 in-data-[state=active]:opacity-100"
/>
</Button>
</div>
</div>
<div
id="mobile-nav"
className={cn(
'overflow-hidden border-t border-border/10 transition-[max-height,opacity] duration-300 lg:hidden',
menuState ? 'max-h-[min(70vh,28rem)] opacity-100' : 'max-h-0 border-transparent opacity-0'
)}>
<ul className={cn("flex flex-col px-3 py-2 sm:px-4", type["nav-mobile"])}>
{menuItems.map((item, index) => {
const active = isLinkActive(pathname ?? '', item.href)
return (
<li key={index}>
<Link
href={item.href}
onClick={() => setMenuState(false)}
className={cn(
'block w-full rounded-md p-2 px-4 text-muted-foreground transition-all duration-300 hover:bg-accent/20 hover:text-foreground',
active && 'bg-accent/40 text-foreground'
)}>
<span className="relative z-10">{item.name}</span>
</Link>
</li>
)
})}
</ul>
</div>
</div>
</nav>
</header>
)
}