163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect, useTransition } from 'react';
|
|
import { Menu, X, Globe } from 'lucide-react';
|
|
import { useTranslations, useLocale } from 'next-intl';
|
|
import { usePathname, useRouter } from '@/i18n/routing';
|
|
|
|
const Navbar: React.FC = () => {
|
|
const t = useTranslations('Navbar');
|
|
const locale = useLocale();
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
const [isLangMenuOpen, setIsLangMenuOpen] = useState(false);
|
|
|
|
const navItems = [
|
|
{ label: t('home'), href: '#hero' },
|
|
{ label: t('about'), href: '#about' },
|
|
{ label: t('services'), href: '#services' },
|
|
{ label: t('contact'), href: '#contact' },
|
|
];
|
|
|
|
const onSelectChange = (nextLocale: string) => {
|
|
startTransition(() => {
|
|
router.replace(pathname, {locale: nextLocale});
|
|
});
|
|
setIsLangMenuOpen(false);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 50);
|
|
};
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const handleResize = () => {
|
|
if (window.innerWidth >= 768) {
|
|
setIsMobileMenuOpen(false);
|
|
}
|
|
};
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, []);
|
|
|
|
return (
|
|
<nav
|
|
className={`fixed top-0 left-0 w-full z-50 transition-all duration-300 ${
|
|
isScrolled || isMobileMenuOpen
|
|
? 'bg-feie-cream/95 backdrop-blur-md shadow-sm py-4'
|
|
: 'bg-transparent py-6'
|
|
}`}
|
|
>
|
|
<div className="container mx-auto px-6 flex justify-between items-center">
|
|
<a href="#" className="flex items-center gap-2 group">
|
|
<div className={`w-8 h-8 rounded-sm border-2 flex items-center justify-center transition-colors ${isScrolled || isMobileMenuOpen ? 'border-feie-dark text-feie-dark' : 'border-feie-white text-feie-white'}`}>
|
|
<span className="font-serif font-bold text-lg">F</span>
|
|
</div>
|
|
<span className={`font-serif text-xl font-bold tracking-wide transition-colors ${isScrolled || isMobileMenuOpen ? 'text-feie-dark' : 'text-feie-white'}`}>
|
|
FEIE TECH
|
|
</span>
|
|
</a>
|
|
|
|
{/* Desktop Menu */}
|
|
<div className="hidden md:flex items-center space-x-8">
|
|
{navItems.map((item) => (
|
|
<a
|
|
key={item.label}
|
|
href={item.href}
|
|
className={`font-serif text-sm tracking-widest uppercase hover:text-feie-gold transition-colors duration-300 ${
|
|
isScrolled ? 'text-feie-dark' : 'text-feie-white/90'
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
|
|
{/* Language Switcher */}
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setIsLangMenuOpen(!isLangMenuOpen)}
|
|
className={`flex items-center gap-1 font-serif text-sm tracking-widest uppercase hover:text-feie-gold transition-colors duration-300 ${
|
|
isScrolled ? 'text-feie-dark' : 'text-feie-white/90'
|
|
}`}
|
|
>
|
|
<Globe size={16} />
|
|
<span>{locale.toUpperCase()}</span>
|
|
</button>
|
|
|
|
{isLangMenuOpen && (
|
|
<div className="absolute top-full right-0 mt-2 w-24 bg-white rounded-md shadow-lg py-1 z-50">
|
|
{['zh', 'en', 'ja'].map((l) => (
|
|
<button
|
|
key={l}
|
|
onClick={() => onSelectChange(l)}
|
|
className={`block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 ${locale === l ? 'text-feie-gold font-bold' : 'text-gray-700'}`}
|
|
>
|
|
{l === 'zh' ? '中文' : l === 'en' ? 'English' : '日本語'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Toggle */}
|
|
<div className="md:hidden flex items-center gap-4">
|
|
{/* Mobile Language Switcher */}
|
|
<button
|
|
onClick={() => {
|
|
const nextLocale = locale === 'zh' ? 'en' : locale === 'en' ? 'ja' : 'zh';
|
|
onSelectChange(nextLocale);
|
|
}}
|
|
className={`flex items-center gap-1 font-serif text-sm tracking-widest uppercase ${
|
|
isScrolled || isMobileMenuOpen ? 'text-feie-dark' : 'text-feie-white'
|
|
}`}
|
|
>
|
|
{locale.toUpperCase()}
|
|
</button>
|
|
|
|
<button
|
|
className="focus:outline-none"
|
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
>
|
|
{isMobileMenuOpen ? (
|
|
<X className={isScrolled || isMobileMenuOpen ? 'text-feie-dark' : 'text-feie-white'} />
|
|
) : (
|
|
<Menu className={isScrolled || isMobileMenuOpen ? 'text-feie-dark' : 'text-feie-white'} />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Menu */}
|
|
<div
|
|
className={`md:hidden absolute top-full left-0 w-full bg-feie-cream/95 backdrop-blur-md border-t border-gray-200 shadow-lg flex flex-col items-center transition-all duration-300 ease-in-out overflow-hidden ${
|
|
isMobileMenuOpen ? 'max-h-96 opacity-100 py-6' : 'max-h-0 opacity-0 py-0'
|
|
}`}
|
|
>
|
|
<div className="flex flex-col items-center space-y-6 w-full">
|
|
{navItems.map((item) => (
|
|
<a
|
|
key={item.label}
|
|
href={item.href}
|
|
className="font-serif text-lg text-feie-dark hover:text-feie-gold transition-colors"
|
|
onClick={() => setIsMobileMenuOpen(false)}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
export default Navbar;
|