2025-09-16 16:15:57 +08:00

298 lines
12 KiB
TypeScript

'use client';
import { useState, useEffect, useRef } from 'react';
import { ChevronDown, Menu, X, Globe } from 'lucide-react';
import Link from 'next/link';
import { NavigationItem } from './NavigationItem';
import { MobileMenu } from './MobileMenu';
import { DropdownMenu } from './DropdownMenu';
type Lang = 'zh' | 'zh-tw' | 'en';
interface NavigationProps {
currentLang: Lang;
onLanguageChange: React.Dispatch<React.SetStateAction<Lang>> | ((lang: Lang) => void);
translations: any;
}
export function Navigation({ currentLang, onLanguageChange, translations }: NavigationProps) {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
const [currentPath, setCurrentPath] = useState('/');
const dropdownTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const t = translations[currentLang];
// Navigation structure with dropdown menus
const navigationItems = [
{
key: 'home',
label: t.nav.home,
href: '/',
current: currentPath === '/',
},
{
key: 'products',
label: t.nav.products,
href: '/products',
current: currentPath.startsWith('/products'),
dropdown: [
{
label:
currentLang === 'zh'
? '云服务器 ECS'
: currentLang === 'zh-tw'
? '雲伺服器 ECS'
: 'Cloud Server ECS',
href: '/products/ecs',
},
{
label:
currentLang === 'zh'
? '云数据库 RDS'
: currentLang === 'zh-tw'
? '雲資料庫 RDS'
: 'Cloud Database RDS',
href: '/products/rds',
},
{
label:
currentLang === 'zh'
? '负载均衡 SLB'
: currentLang === 'zh-tw'
? '負載平衡 SLB'
: 'Load Balancer SLB',
href: '/products/slb',
},
{
label:
currentLang === 'zh'
? '对象存储 OSS'
: currentLang === 'zh-tw'
? '物件儲存 OSS'
: 'Object Storage OSS',
href: '/products/oss',
},
],
},
{
key: 'solutions',
label: t.nav.solutions,
href: '/solutions',
current: currentPath.startsWith('/solutions'),
dropdown: [
{
label:
currentLang === 'zh'
? '企业上云'
: currentLang === 'zh-tw'
? '企業上雲'
: 'Enterprise Cloud',
href: '/solutions/enterprise',
},
{
label:
currentLang === 'zh'
? '数据备份'
: currentLang === 'zh-tw'
? '資料備份'
: 'Data Backup',
href: '/solutions/backup',
},
{
label:
currentLang === 'zh'
? '灾难恢复'
: currentLang === 'zh-tw'
? '災難恢復'
: 'Disaster Recovery',
href: '/solutions/recovery',
},
],
},
{
key: 'support',
label: t.nav.support,
href: '/support',
current: currentPath.startsWith('/support'),
},
{
key: 'news',
label: t.nav.news,
href: '/news',
current: currentPath.startsWith('/news'),
},
{
key: 'contact',
label: t.nav.contact,
href: '/contact',
current: currentPath === '/contact',
},
];
// Handle dropdown interactions
const handleDropdownEnter = (key: string) => {
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
}
setActiveDropdown(key);
};
const handleDropdownLeave = () => {
dropdownTimeoutRef.current = setTimeout(() => {
setActiveDropdown(null);
}, 150);
};
// Handle keyboard navigation
const handleKeyDown = (event: React.KeyboardEvent, key: string, hasDropdown: boolean) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
if (hasDropdown) {
setActiveDropdown(activeDropdown === key ? null : key);
}
} else if (event.key === 'Escape') {
setActiveDropdown(null);
}
};
// Close mobile menu when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (isMobileMenuOpen && !(event.target as Element).closest('[data-mobile-menu]')) {
setIsMobileMenuOpen(false);
}
};
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, [isMobileMenuOpen]);
// Prevent body scroll when mobile menu is open
useEffect(() => {
if (isMobileMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isMobileMenuOpen]);
return (
<nav
className="bg-gradient-to-r from-amber-900 to-yellow-800 shadow-lg sticky top-0 z-50"
role="navigation"
aria-label="Main navigation"
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div className="flex items-center">
<Link
href="/"
className="text-2xl font-bold text-yellow-300 hover:text-yellow-200 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-yellow-400 focus:ring-offset-2 focus:ring-offset-amber-900 rounded-md px-2 py-1"
aria-label="CloudPro - Home"
>
CloudPro
</Link>
</div>
{/* Desktop Navigation */}
<div className="hidden lg:block">
<div className="ml-10 flex items-baseline space-x-2">
{navigationItems.map((item) => (
<div
key={item.key}
className="relative"
onMouseEnter={() =>
item.dropdown && handleDropdownEnter(item.key)
}
onMouseLeave={() => item.dropdown && handleDropdownLeave()}
>
<NavigationItem
label={item.label}
href={item.href}
current={item.current}
hasDropdown={!!item.dropdown}
isDropdownOpen={activeDropdown === item.key}
onKeyDown={(e) =>
handleKeyDown(e, item.key, !!item.dropdown)
}
/>
{item.dropdown && activeDropdown === item.key && (
<DropdownMenu
items={item.dropdown}
onClose={() => setActiveDropdown(null)}
/>
)}
</div>
))}
</div>
</div>
{/* Language Selector & Mobile Menu Button */}
<div className="flex items-center space-x-4">
{/* Language Selector */}
<div className="relative">
<div className="relative group">
<select
value={currentLang}
onChange={(e) =>
(onLanguageChange as (lang: Lang) => void)(
e.target.value as Lang,
)
}
className="appearance-none bg-gradient-to-r from-amber-800 to-amber-700 text-yellow-100 border border-yellow-600/50 rounded-lg px-4 py-2.5 pr-10 text-sm font-medium hover:from-amber-700 hover:to-amber-600 focus:outline-none focus:ring-2 focus:ring-yellow-400 focus:ring-offset-2 focus:ring-offset-amber-900 transition-all duration-200 shadow-md hover:shadow-lg cursor-pointer min-w-[100px]"
aria-label="Select language"
>
<option value="zh" className="bg-amber-800 text-yellow-100">
</option>
<option value="zh-tw" className="bg-amber-800 text-yellow-100">
</option>
<option value="en" className="bg-amber-800 text-yellow-100">
English
</option>
</select>
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none">
<Globe className="w-4 h-4 text-yellow-300 mr-1" />
<ChevronDown className="w-3 h-3 text-yellow-300 group-hover:text-yellow-200 transition-colors duration-200" />
</div>
</div>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="lg:hidden inline-flex items-center justify-center p-2 rounded-md text-yellow-100 hover:text-yellow-300 hover:bg-amber-800 focus:outline-none focus:ring-2 focus:ring-yellow-400 focus:ring-offset-2 focus:ring-offset-amber-900 transition-colors duration-200"
aria-expanded={isMobileMenuOpen}
aria-label="Toggle mobile menu"
data-mobile-menu
>
{isMobileMenuOpen ? (
<X className="block h-6 w-6" aria-hidden="true" />
) : (
<Menu className="block h-6 w-6" aria-hidden="true" />
)}
</button>
</div>
</div>
</div>
{/* Mobile Menu */}
<MobileMenu
isOpen={isMobileMenuOpen}
items={navigationItems}
onClose={() => setIsMobileMenuOpen(false)}
currentLang={currentLang}
/>
</nav>
);
}