157 lines
6.5 KiB
TypeScript
157 lines
6.5 KiB
TypeScript
|
|
'use client';
|
|||
|
|
import Link from 'next/link';
|
|||
|
|
import { usePathname } from 'next/navigation';
|
|||
|
|
import { useState } from 'react';
|
|||
|
|
import { LanguageType } from '../lib/content';
|
|||
|
|
|
|||
|
|
interface NavigationProps {
|
|||
|
|
/** 🔧 修改 #1:currentLang 从 string 改为 LanguageType */
|
|||
|
|
currentLang: LanguageType;
|
|||
|
|
|
|||
|
|
/** 🔧 修改 #2:setCurrentLang 返回值改为 void(原来写成了 string),签名也保持只接受 LanguageType */
|
|||
|
|
setCurrentLang: (lang: LanguageType) => void;
|
|||
|
|
|
|||
|
|
content: {
|
|||
|
|
nav: {
|
|||
|
|
home: string;
|
|||
|
|
services: string;
|
|||
|
|
solutions: string;
|
|||
|
|
pricing: string;
|
|||
|
|
news: string;
|
|||
|
|
contact: string;
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 🔧 修改 #3:createLocalizedPath 的签名改为 (path: string) => string */
|
|||
|
|
createLocalizedPath?: (path: string) => string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function Navigation({
|
|||
|
|
currentLang,
|
|||
|
|
setCurrentLang,
|
|||
|
|
content,
|
|||
|
|
createLocalizedPath,
|
|||
|
|
}: NavigationProps) {
|
|||
|
|
const pathname = usePathname();
|
|||
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|||
|
|
|
|||
|
|
// Helper to build localized URLs
|
|||
|
|
const getLocalizedPath = (path: string) => {
|
|||
|
|
return createLocalizedPath ? createLocalizedPath(path) : path;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const navItems = [
|
|||
|
|
{ label: content.nav.home, href: getLocalizedPath('/') },
|
|||
|
|
{ label: content.nav.services, href: getLocalizedPath('/services') },
|
|||
|
|
{ label: content.nav.solutions, href: getLocalizedPath('/solutions') },
|
|||
|
|
{ label: content.nav.pricing, href: getLocalizedPath('/pricing') },
|
|||
|
|
{ label: content.nav.news, href: getLocalizedPath('/news') },
|
|||
|
|
{ label: content.nav.contact, href: getLocalizedPath('/contact') },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<nav className="relative z-10 px-6 py-4 border-b border-cyan-500/20 backdrop-blur-sm">
|
|||
|
|
<div className="max-w-7xl mx-auto flex justify-between items-center">
|
|||
|
|
{/* Logo */}
|
|||
|
|
<Link
|
|||
|
|
href="/"
|
|||
|
|
className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent hover:scale-105 transition-transform duration-300"
|
|||
|
|
>
|
|||
|
|
CloudAgency
|
|||
|
|
</Link>
|
|||
|
|
|
|||
|
|
{/* Desktop Nav */}
|
|||
|
|
<div className="hidden md:flex space-x-8">
|
|||
|
|
{navItems.map((item, idx) => {
|
|||
|
|
const isActive = pathname === item.href;
|
|||
|
|
return (
|
|||
|
|
<Link
|
|||
|
|
key={idx}
|
|||
|
|
href={item.href}
|
|||
|
|
className={`relative group transition-colors duration-300 ${
|
|||
|
|
isActive ? 'text-cyan-400' : 'text-gray-300 hover:text-cyan-400'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{item.label}
|
|||
|
|
<span
|
|||
|
|
className={`absolute -bottom-1 left-0 h-0.5 bg-cyan-400 transition-all duration-300 ${
|
|||
|
|
isActive ? 'w-full' : 'w-0 group-hover:w-full'
|
|||
|
|
}`}
|
|||
|
|
/>
|
|||
|
|
</Link>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Right side */}
|
|||
|
|
<div className="flex items-center space-x-4">
|
|||
|
|
{/* 🔧 修改 #4:onChange 时将 value 强制断言为 LanguageType */}
|
|||
|
|
<select
|
|||
|
|
value={currentLang}
|
|||
|
|
onChange={(e) => setCurrentLang(e.target.value as LanguageType)}
|
|||
|
|
className="bg-gray-800 border border-cyan-500/30 rounded-lg px-3 py-1 text-sm focus:outline-none focus:border-cyan-400 transition-colors"
|
|||
|
|
>
|
|||
|
|
<option value="zh-CN">简体中文</option>
|
|||
|
|
<option value="zh-TW">繁體中文</option>
|
|||
|
|
<option value="en">English</option>
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
{/* Mobile menu button */}
|
|||
|
|
<button
|
|||
|
|
className="md:hidden text-gray-300 hover:text-cyan-400 transition-colors"
|
|||
|
|
onClick={() => setIsMobileMenuOpen((o) => !o)}
|
|||
|
|
>
|
|||
|
|
<svg
|
|||
|
|
className="w-6 h-6"
|
|||
|
|
fill="none"
|
|||
|
|
stroke="currentColor"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
>
|
|||
|
|
{isMobileMenuOpen ? (
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round"
|
|||
|
|
strokeLinejoin="round"
|
|||
|
|
strokeWidth={2}
|
|||
|
|
d="M6 18L18 6M6 6l12 12"
|
|||
|
|
/>
|
|||
|
|
) : (
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round"
|
|||
|
|
strokeLinejoin="round"
|
|||
|
|
strokeWidth={2}
|
|||
|
|
d="M4 6h16M4 12h16M4 18h16"
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Mobile Menu */}
|
|||
|
|
{isMobileMenuOpen && (
|
|||
|
|
<div className="md:hidden absolute top-full left-0 right-0 bg-gray-900/95 backdrop-blur-sm border-b border-cyan-500/20">
|
|||
|
|
<div className="px-6 py-4 space-y-4">
|
|||
|
|
{navItems.map((item, idx) => {
|
|||
|
|
const isActive = pathname === item.href;
|
|||
|
|
return (
|
|||
|
|
<Link
|
|||
|
|
key={idx}
|
|||
|
|
href={item.href}
|
|||
|
|
className={`block py-2 transition-colors duration-300 ${
|
|||
|
|
isActive
|
|||
|
|
? 'text-cyan-400'
|
|||
|
|
: 'text-gray-300 hover:text-cyan-400'
|
|||
|
|
}`}
|
|||
|
|
onClick={() => setIsMobileMenuOpen(false)}
|
|||
|
|
>
|
|||
|
|
{item.label}
|
|||
|
|
</Link>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</nav>
|
|||
|
|
);
|
|||
|
|
}
|