site-header.tsx 3.1 KB

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