sidebar.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. "use client";
  2. import Link from "next/link";
  3. import { usePathname } from "next/navigation";
  4. import { BrandIcon } from "@/components/brand/brand-icon";
  5. import { NAV_GROUPS } from "@/lib/nav";
  6. import { cn } from "@/lib/utils";
  7. interface SidebarProps {
  8. open: boolean;
  9. onClose: () => void;
  10. }
  11. export function Sidebar({ open, onClose }: SidebarProps) {
  12. const pathname = usePathname();
  13. return (
  14. <>
  15. <div
  16. aria-hidden
  17. onClick={onClose}
  18. className={cn(
  19. "fixed inset-0 z-30 bg-background/70 backdrop-blur-sm transition-opacity lg:hidden",
  20. open ? "opacity-100" : "pointer-events-none opacity-0",
  21. )}
  22. />
  23. <aside
  24. className={cn(
  25. "fixed inset-y-0 left-0 z-40 flex w-64 flex-col border-r border-border bg-card transition-transform lg:translate-x-0",
  26. open ? "translate-x-0" : "-translate-x-full",
  27. )}
  28. >
  29. <div className="flex h-14 items-center justify-between gap-2 border-b border-border px-4">
  30. <Link
  31. href="/portal"
  32. onClick={onClose}
  33. className="flex items-center gap-2.5"
  34. >
  35. <BrandIcon size={26} />
  36. <span className="font-display text-lg font-semibold tracking-tight">
  37. krypco
  38. </span>
  39. </Link>
  40. <Link
  41. href="/"
  42. onClick={onClose}
  43. className="text-[11px] text-muted-foreground hover:text-foreground"
  44. >
  45. Site
  46. </Link>
  47. </div>
  48. <nav className="flex-1 overflow-y-auto px-3 py-4">
  49. {NAV_GROUPS.map((group) => (
  50. <div key={group.label} className="mb-5">
  51. <p className="mb-1.5 px-3 text-xs font-medium uppercase tracking-wider text-muted-foreground">
  52. {group.label}
  53. </p>
  54. <ul className="flex flex-col gap-0.5">
  55. {group.items.map((item) => {
  56. const active =
  57. item.href === "/portal"
  58. ? pathname === "/portal"
  59. : pathname === item.href ||
  60. pathname.startsWith(`${item.href}/`);
  61. const Icon = item.icon;
  62. return (
  63. <li key={item.href}>
  64. <Link
  65. href={item.href}
  66. onClick={onClose}
  67. aria-current={active ? "page" : undefined}
  68. className={cn(
  69. "flex min-h-11 items-center gap-3 rounded-md px-3 py-2.5 text-sm transition-colors sm:min-h-0 sm:py-2",
  70. active
  71. ? "bg-accent-muted font-medium text-accent-hover"
  72. : "text-muted-foreground hover:bg-muted hover:text-foreground",
  73. )}
  74. >
  75. <Icon size={17} />
  76. {item.label}
  77. </Link>
  78. </li>
  79. );
  80. })}
  81. </ul>
  82. </div>
  83. ))}
  84. </nav>
  85. <div className="border-t border-border px-5 py-4">
  86. <p className="text-xs text-muted-foreground">
  87. portal · follow the hybrid path
  88. </p>
  89. </div>
  90. </aside>
  91. </>
  92. );
  93. }