site-header.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use client";
  2. import Link from "next/link";
  3. import { usePathname } from "next/navigation";
  4. import { useState } from "react";
  5. import { BrandLogo } from "@/components/brand/brand-logo";
  6. import { MenuIcon } from "@/components/icons/menu";
  7. import { buttonVariants } from "@/components/ui/button";
  8. import { MARKETING_PRIMARY_NAV } from "@/lib/marketing-nav";
  9. import { cn } from "@/lib/utils";
  10. export function SiteHeader() {
  11. const pathname = usePathname();
  12. const [open, setOpen] = useState(false);
  13. return (
  14. <header className="sticky top-0 z-40 bg-background/80 backdrop-blur-md">
  15. <div className="mx-auto flex h-16 max-w-6xl items-center justify-between gap-4 px-5 lg:px-8">
  16. <Link
  17. href="/"
  18. className="flex items-center gap-2.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
  19. onClick={() => setOpen(false)}
  20. >
  21. <BrandLogo height={32} priority />
  22. <span className="font-display text-lg font-semibold tracking-tight">
  23. krypco
  24. </span>
  25. </Link>
  26. <nav
  27. className="hidden items-center gap-1 lg:flex"
  28. aria-label="Primary"
  29. >
  30. {MARKETING_PRIMARY_NAV.map((item) => {
  31. const active =
  32. pathname === item.href || pathname.startsWith(`${item.href}/`);
  33. return (
  34. <Link
  35. key={item.href}
  36. href={item.href}
  37. aria-current={active ? "page" : undefined}
  38. className={cn(
  39. "rounded-md px-3 py-2 text-[13px] transition-colors",
  40. active
  41. ? "text-foreground"
  42. : "text-muted-foreground hover:text-foreground",
  43. )}
  44. >
  45. {item.label}
  46. </Link>
  47. );
  48. })}
  49. </nav>
  50. <div className="flex items-center gap-2">
  51. <Link
  52. href="/portal"
  53. className={cn(
  54. buttonVariants({ variant: "outline", size: "sm" }),
  55. "hidden sm:inline-flex",
  56. )}
  57. >
  58. Open portal
  59. </Link>
  60. <button
  61. type="button"
  62. className="inline-flex h-10 w-10 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-muted hover:text-foreground lg:hidden"
  63. aria-label={open ? "Close menu" : "Open menu"}
  64. aria-expanded={open}
  65. onClick={() => setOpen((v) => !v)}
  66. >
  67. <MenuIcon size={18} />
  68. </button>
  69. </div>
  70. </div>
  71. {open ? (
  72. <div className="bg-card lg:hidden">
  73. <nav className="mx-auto flex max-w-6xl flex-col gap-1 px-5 py-4" aria-label="Mobile">
  74. {MARKETING_PRIMARY_NAV.map((item) => (
  75. <Link
  76. key={item.href}
  77. href={item.href}
  78. onClick={() => setOpen(false)}
  79. className="rounded-md px-3 py-3 text-[13px] text-muted-foreground hover:bg-muted hover:text-foreground"
  80. >
  81. {item.label}
  82. </Link>
  83. ))}
  84. <Link
  85. href="/about"
  86. onClick={() => setOpen(false)}
  87. className="rounded-md px-3 py-3 text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
  88. >
  89. About
  90. </Link>
  91. <Link
  92. href="/contact"
  93. onClick={() => setOpen(false)}
  94. className="rounded-md px-3 py-3 text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
  95. >
  96. Contact
  97. </Link>
  98. <Link
  99. href="/portal"
  100. onClick={() => setOpen(false)}
  101. className={cn(buttonVariants({ variant: "primary" }), "mt-2")}
  102. >
  103. Open portal
  104. </Link>
  105. </nav>
  106. </div>
  107. ) : null}
  108. </header>
  109. );
  110. }