125 lines
4.9 KiB
TypeScript
125 lines
4.9 KiB
TypeScript
|
|
import { useState, useRef, useEffect } from 'react';
|
||
|
|
import { Link, useLocation } from 'react-router-dom';
|
||
|
|
import { Languages, ChevronDown, Check, Menu, X } from 'lucide-react';
|
||
|
|
import { useLanguage } from '../../contexts/LanguageContext';
|
||
|
|
|
||
|
|
export default function MarketingNavbar() {
|
||
|
|
const { language, setLanguage, t } = useLanguage();
|
||
|
|
const location = useLocation();
|
||
|
|
const [showLangMenu, setShowLangMenu] = useState(false);
|
||
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||
|
|
const langMenuRef = useRef<HTMLDivElement>(null);
|
||
|
|
const isHome = location.pathname === '/';
|
||
|
|
|
||
|
|
const navLinks = [
|
||
|
|
{ to: '/', label: t.marketing.nav.home },
|
||
|
|
{ to: '/docs', label: t.marketing.nav.docs },
|
||
|
|
{ to: '/blog', label: t.marketing.nav.blog },
|
||
|
|
];
|
||
|
|
|
||
|
|
const anchorLinks = isHome
|
||
|
|
? [
|
||
|
|
{ href: '#pricing', label: t.marketing.nav.pricing },
|
||
|
|
{ href: '#contact', label: t.marketing.nav.contact },
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handleClickOutside = (event: MouseEvent) => {
|
||
|
|
if (langMenuRef.current && !langMenuRef.current.contains(event.target as Node)) {
|
||
|
|
setShowLangMenu(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.addEventListener('mousedown', handleClickOutside);
|
||
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<nav className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 flex-shrink-0 z-[60] relative">
|
||
|
|
<Link to="/" className="flex items-center gap-2">
|
||
|
|
<img src="/texpixel-app-icon.svg" alt="TexPixel" className="w-8 h-8" />
|
||
|
|
<span className="text-xl font-bold text-gray-900 tracking-tight">TexPixel</span>
|
||
|
|
</Link>
|
||
|
|
|
||
|
|
<div className="hidden md:flex items-center gap-6">
|
||
|
|
{navLinks.map((link) => (
|
||
|
|
<Link
|
||
|
|
key={link.to}
|
||
|
|
to={link.to}
|
||
|
|
className={`text-sm font-medium transition-colors ${
|
||
|
|
location.pathname === link.to ? 'text-blue-600' : 'text-gray-700 hover:text-gray-900'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{link.label}
|
||
|
|
</Link>
|
||
|
|
))}
|
||
|
|
{anchorLinks.map((link) => (
|
||
|
|
<a
|
||
|
|
key={link.href}
|
||
|
|
href={link.href}
|
||
|
|
className="text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors"
|
||
|
|
>
|
||
|
|
{link.label}
|
||
|
|
</a>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-3">
|
||
|
|
<div className="relative" ref={langMenuRef}>
|
||
|
|
<button
|
||
|
|
onClick={() => setShowLangMenu(!showLangMenu)}
|
||
|
|
className="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 rounded-lg text-gray-700 text-sm font-medium transition-colors"
|
||
|
|
>
|
||
|
|
<Languages size={18} />
|
||
|
|
<span className="hidden sm:inline">{language === 'en' ? 'EN' : '中文'}</span>
|
||
|
|
<ChevronDown size={14} className={`transition-transform duration-200 ${showLangMenu ? 'rotate-180' : ''}`} />
|
||
|
|
</button>
|
||
|
|
{showLangMenu && (
|
||
|
|
<div className="absolute right-0 top-full mt-2 w-32 bg-white rounded-xl shadow-lg border border-gray-200 py-1 z-50">
|
||
|
|
{(['en', 'zh'] as const).map((lang) => (
|
||
|
|
<button
|
||
|
|
key={lang}
|
||
|
|
onClick={() => { setLanguage(lang); setShowLangMenu(false); }}
|
||
|
|
className={`w-full flex items-center justify-between px-4 py-2 text-sm transition-colors hover:bg-gray-50 ${language === lang ? 'text-blue-600 font-medium' : 'text-gray-700'}`}
|
||
|
|
>
|
||
|
|
{lang === 'en' ? 'English' : '简体中文'}
|
||
|
|
{language === lang && <Check size={14} />}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<Link
|
||
|
|
to="/app"
|
||
|
|
className="hidden sm:inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors"
|
||
|
|
>
|
||
|
|
{t.marketing.nav.launchApp}
|
||
|
|
</Link>
|
||
|
|
|
||
|
|
<button className="md:hidden p-2" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>
|
||
|
|
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{mobileMenuOpen && (
|
||
|
|
<div className="absolute top-16 left-0 right-0 bg-white border-b border-gray-200 shadow-lg md:hidden z-50 py-4 px-6 flex flex-col gap-3">
|
||
|
|
{navLinks.map((link) => (
|
||
|
|
<Link key={link.to} to={link.to} className="text-sm font-medium text-gray-700 py-2" onClick={() => setMobileMenuOpen(false)}>
|
||
|
|
{link.label}
|
||
|
|
</Link>
|
||
|
|
))}
|
||
|
|
{anchorLinks.map((link) => (
|
||
|
|
<a key={link.href} href={link.href} className="text-sm font-medium text-gray-700 py-2" onClick={() => setMobileMenuOpen(false)}>
|
||
|
|
{link.label}
|
||
|
|
</a>
|
||
|
|
))}
|
||
|
|
<Link to="/app" className="text-sm font-medium text-blue-600 py-2" onClick={() => setMobileMenuOpen(false)}>
|
||
|
|
{t.marketing.nav.launchApp}
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</nav>
|
||
|
|
);
|
||
|
|
}
|