154 lines
5.3 KiB
TypeScript
154 lines
5.3 KiB
TypeScript
"use client";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useState, useCallback } from "react";
|
|
import { motion } from "framer-motion";
|
|
|
|
// 导航链接配置
|
|
const NAV_LINKS = [{ path: "/", label: "首页" }];
|
|
|
|
export default function Header() {
|
|
const pathname = usePathname();
|
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
|
|
const toggleMenu = useCallback(() => {
|
|
setIsMenuOpen((prev) => !prev);
|
|
}, []);
|
|
|
|
const closeMenu = useCallback(() => {
|
|
setIsMenuOpen(false);
|
|
}, []);
|
|
|
|
const sidebarVariants = {
|
|
open: {
|
|
x: 0,
|
|
transition: {
|
|
type: "spring" as const,
|
|
stiffness: 300,
|
|
damping: 30,
|
|
staggerChildren: 0.1,
|
|
delayChildren: 0.1,
|
|
},
|
|
},
|
|
closed: {
|
|
x: "100%",
|
|
transition: {
|
|
type: "spring" as const,
|
|
stiffness: 300,
|
|
damping: 30,
|
|
staggerChildren: 0.05,
|
|
staggerDirection: -1,
|
|
},
|
|
},
|
|
};
|
|
|
|
const menuItemVariants = {
|
|
open: {
|
|
y: 0,
|
|
opacity: 1,
|
|
transition: {
|
|
y: { stiffness: 1000, velocity: -100 },
|
|
},
|
|
},
|
|
closed: {
|
|
y: 20,
|
|
opacity: 0,
|
|
transition: {
|
|
y: { stiffness: 1000 },
|
|
},
|
|
},
|
|
};
|
|
|
|
// 导航链接组件
|
|
const NavLink = ({ href, children, className, onClick }: { href: string; children: React.ReactNode; className?: string; onClick?: () => void }) => (
|
|
<Link href={href} className={pathname === href ? "no-underline" : ""} onClick={onClick}>
|
|
<motion.div className={className} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} initial={false}>
|
|
{children}
|
|
</motion.div>
|
|
</Link>
|
|
);
|
|
|
|
// 按钮组件
|
|
const NavButton = ({ onClick, children, className }: { onClick: () => void; children: React.ReactNode; className?: string }) => (
|
|
<button onClick={onClick} className={className}>
|
|
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} initial={false}>
|
|
{children}
|
|
</motion.div>
|
|
</button>
|
|
);
|
|
|
|
return (
|
|
<div className="pb-24">
|
|
<header className="w-full fixed top-0 left-0 right-0 bg-white/80 dark:bg-black/80 py-3 z-50 backdrop-blur-md border-b border-transparent dark:border-gray-800 transition-colors duration-300">
|
|
<nav className="mx-auto max-w-7xl flex justify-between items-center px-4 md:px-6">
|
|
<div className="flex items-center min-w-0">
|
|
<Link href="/" className="truncate">
|
|
<motion.h1 className="text-xl font-bold text-gray-900 dark:text-white truncate" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
智能计算实验室
|
|
</motion.h1>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* 桌面端导航 */}
|
|
<div className="hidden md:flex space-x-6 shrink-0">
|
|
{NAV_LINKS.map((link) => (
|
|
<NavLink
|
|
key={link.path}
|
|
href={link.path}
|
|
className={`text-sm font-medium text-gray-600 dark:text-gray-400 hover:text-black dark:hover:text-white transition-colors duration-200 ${
|
|
pathname === link.path ? "text-black dark:text-white font-bold" : ""
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
|
|
{/* 移动端菜单按钮 */}
|
|
<NavButton className="flex items-center md:hidden focus:outline-none shrink-0 ml-4" onClick={toggleMenu}>
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="打开导航菜单" className="text-gray-900 dark:text-white">
|
|
<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</NavButton>
|
|
</nav>
|
|
</header>
|
|
|
|
{/* 遮罩层 */}
|
|
<div
|
|
className={`md:hidden fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ${isMenuOpen ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
|
onClick={closeMenu}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* 移动端侧边菜单 */}
|
|
<motion.div
|
|
className="md:hidden fixed top-[57px] right-0 bottom-0 w-full max-w-xs bg-white/80 dark:bg-black/80 backdrop-blur-md border-l border-gray-200 dark:border-gray-800 shadow-2xl z-40"
|
|
initial="closed"
|
|
animate={isMenuOpen ? "open" : "closed"}
|
|
variants={sidebarVariants}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label="导航菜单"
|
|
>
|
|
<div className="px-6 py-8 flex flex-col gap-2 h-full overflow-y-auto">
|
|
{NAV_LINKS.map((link) => (
|
|
<motion.div key={link.path} variants={menuItemVariants}>
|
|
<NavLink
|
|
href={link.path}
|
|
className={`block py-4 px-4 rounded-0 text-lg transition-all duration-200 ${
|
|
pathname === link.path
|
|
? "bg-gray-100 dark:bg-gray-800 text-black dark:text-white font-bold"
|
|
: "text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900 hover:text-black dark:hover:text-white"
|
|
}`}
|
|
onClick={closeMenu}
|
|
>
|
|
{link.label}
|
|
</NavLink>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
}
|