site-header.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. "use client";
  2. import Link from "next/link";
  3. import { usePathname } from "next/navigation";
  4. import { useEffect, useState } from "react";
  5. const navItems = [
  6. { href: "/", label: "首页" },
  7. { href: "/chat", label: "聊天室" },
  8. { href: "/library", label: "书架" },
  9. { href: "/agents", label: "Agent 观察" }
  10. ];
  11. export function SiteHeader() {
  12. const pathname = usePathname();
  13. const [menuOpen, setMenuOpen] = useState(false);
  14. useEffect(() => {
  15. setMenuOpen(false);
  16. }, [pathname]);
  17. useEffect(() => {
  18. const onKeyDown = (event: KeyboardEvent) => {
  19. if (event.key === "Escape") {
  20. setMenuOpen(false);
  21. }
  22. };
  23. window.addEventListener("keydown", onKeyDown);
  24. return () => window.removeEventListener("keydown", onKeyDown);
  25. }, []);
  26. if (pathname?.startsWith("/reader/")) {
  27. return null;
  28. }
  29. return (
  30. <header className={`site-header${menuOpen ? " site-header--menu-open" : ""}`}>
  31. <div className="page-shell site-header__inner">
  32. <Link href="/" className="site-brand">
  33. <div className="site-brand__mark">LAN</div>
  34. <div>
  35. <div className="site-brand__name">局域网书房</div>
  36. <div className="site-brand__tag">聊天、传文件、看小说,放在一个入口里</div>
  37. </div>
  38. </Link>
  39. <nav className="site-nav" aria-label="主导航">
  40. {navItems.map((item) => (
  41. <Link key={item.href} href={item.href} className={pathname === item.href ? "is-active" : undefined}>
  42. {item.label}
  43. </Link>
  44. ))}
  45. </nav>
  46. <button
  47. type="button"
  48. className={`site-menu-button${menuOpen ? " is-open" : ""}`}
  49. aria-expanded={menuOpen}
  50. aria-controls="mobile-site-nav"
  51. aria-label={menuOpen ? "关闭导航" : "打开导航"}
  52. onClick={() => setMenuOpen((value) => !value)}
  53. >
  54. <span />
  55. <span />
  56. <span />
  57. </button>
  58. </div>
  59. <div
  60. className={`site-drawer-backdrop${menuOpen ? " is-open" : ""}`}
  61. onClick={() => setMenuOpen(false)}
  62. aria-hidden={!menuOpen}
  63. />
  64. <div className={`site-drawer${menuOpen ? " is-open" : ""}`} id="mobile-site-nav" aria-hidden={!menuOpen}>
  65. <div className="site-drawer__panel">
  66. <div className="site-drawer__top">
  67. <div>
  68. <div className="site-drawer__title">导航</div>
  69. <div className="site-drawer__hint">手机端单独唤出,和页面正文彻底分层</div>
  70. </div>
  71. <button
  72. type="button"
  73. className="site-drawer__close"
  74. aria-label="关闭导航"
  75. onClick={() => setMenuOpen(false)}
  76. >
  77. 关闭
  78. </button>
  79. </div>
  80. <nav className="site-drawer__nav" aria-label="移动端主导航">
  81. {navItems.map((item) => (
  82. <Link
  83. key={item.href}
  84. href={item.href}
  85. className={pathname === item.href ? "is-active" : undefined}
  86. >
  87. {item.label}
  88. </Link>
  89. ))}
  90. </nav>
  91. </div>
  92. </div>
  93. </header>
  94. );
  95. }