forked from codechefPesuecc/CodeChef-PESUECC-Chapter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNavbar.tsx
More file actions
223 lines (203 loc) · 7.66 KB
/
Copy pathNavbar.tsx
File metadata and controls
223 lines (203 loc) · 7.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import { useUser } from "@/components/auth/useUser";
import { ThemeToggle } from "@/components/ui/theme-toggle";
const links = [
{ href: "/", label: "Home" },
{ href: "/cp-arena", label: "Arena" },
{ href: "/leaderboard", label: "Leaderboard" },
{ href: "/initiatives", label: "Initiatives" },
{ href: "/team", label: "Team" },
{ href: "/newsroom", label: "Newsroom" },
];
export default function Navbar() {
const pathname = usePathname();
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const user = useUser();
const logout = async () => {
try {
await fetch("/api/auth/logout", { method: "POST" });
} catch {}
window.location.href = "/";
};
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
// The active theme lives entirely in the `.dark` class on <html>.
// We use the View Transitions API to create a seamless pan effect across the screen.
const toggleTheme = (event: React.MouseEvent) => {
const isDark = document.documentElement.classList.contains("dark");
// Fallback if browser doesn't support startViewTransition
if (!document.startViewTransition) {
document.documentElement.classList.toggle("dark");
try { localStorage.setItem("theme", !isDark ? "dark" : "light"); } catch {}
return;
}
// Calculate the center of the toggle button to use as the animation origin.
// This is much more robust than event.clientX/Y which can break on external
// monitors with different scale factors, or when the button is triggered via keyboard.
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const endRadius = Math.hypot(
Math.max(x, window.innerWidth - x),
Math.max(y, window.innerHeight - y)
);
const transition = document.startViewTransition(() => {
document.documentElement.classList.toggle("dark");
try { localStorage.setItem("theme", !isDark ? "dark" : "light"); } catch {}
});
transition.ready.then(() => {
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`
];
document.documentElement.animate(
{
clipPath: clipPath,
},
{
duration: 500,
easing: "ease-in-out",
pseudoElement: "::view-transition-new(root)",
}
);
});
};
const isActive = (href: string) =>
href === "/" ? pathname === "/" : pathname.startsWith(href);
return (
<header
className={`fixed inset-x-0 top-0 z-50 transition-colors duration-300 ${
scrolled
? "border-b border-hairline bg-cream/80 backdrop-blur-md dark:bg-[#140f0a]/85"
: "border-b border-transparent"
}`}
>
<div className="relative mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
{/* Brand */}
<Link href="/" className="flex items-center gap-2.5">
<span className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg border border-hairline bg-white shadow-sm">
<Image
src="/logo.svg"
alt="CodeChef PESU ECC Chapter"
width={36}
height={36}
priority
unoptimized
className="h-9 w-9 object-contain"
/>
</span>
<span className="hidden font-display leading-none sm:flex sm:flex-col">
<span className="text-lg font-bold tracking-tight text-chocolate">
CodeChef
</span>
<span className="mt-0.5 text-xs font-semibold tracking-[0.2em] text-bronze">
PESUECC Chapter
</span>
</span>
</Link>
{/* Centered floating pill (desktop) */}
<nav className="absolute left-1/2 hidden -translate-x-1/2 items-center gap-1 rounded-full border border-hairline bg-white/70 p-1.5 shadow-sm backdrop-blur md:flex dark:bg-panel/70">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
aria-current={isActive(link.href) ? "page" : undefined}
className={`rounded-full px-4 py-2 text-sm transition-colors ${
isActive(link.href)
? "bg-bronze/15 font-semibold text-chocolate"
: "font-medium text-charcoal/70 hover:text-chocolate"
}`}
>
{link.label}
</Link>
))}
</nav>
{/* Actions */}
<div className="flex items-center gap-2">
<ThemeToggle onToggle={toggleTheme} />
{user ? (
<div className="flex items-center gap-2">
<Link
href="/profile"
title={`@${user.username} · your profile`}
aria-label="Your profile"
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-hairline bg-bronze/10 text-sm font-bold uppercase text-bronze shadow-sm transition-shadow hover:shadow-md"
>
{user.username.charAt(0).toUpperCase()}
</Link>
<button
type="button"
onClick={logout}
className="mecha-btn mecha-btn--ghost"
>
Log out
</button>
</div>
) : (
<Link
href="/login"
className="mecha-btn mecha-btn--solid"
>
Log in
</Link>
)}
<button
type="button"
onClick={() => setOpen((v) => !v)}
aria-label="Toggle navigation menu"
aria-expanded={open}
className="flex h-10 w-10 items-center justify-center rounded-full border border-hairline bg-white/70 text-chocolate shadow-sm backdrop-blur md:hidden dark:bg-panel/70"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
{open ? (
<path d="M18 6 6 18M6 6l12 12" />
) : (
<path d="M4 7h16M4 12h16M4 17h16" />
)}
</svg>
</button>
</div>
</div>
{/* Mobile dropdown */}
{open && (
<div className="mx-auto max-w-6xl px-6 md:hidden">
<nav className="flex flex-col gap-1 rounded-2xl border border-hairline bg-white/90 p-2 shadow-lg backdrop-blur dark:bg-panel/95">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={() => setOpen(false)}
aria-current={isActive(link.href) ? "page" : undefined}
className={`rounded-xl px-4 py-2.5 text-sm transition-colors ${
isActive(link.href)
? "bg-bronze/15 font-semibold text-chocolate"
: "font-medium text-charcoal/80 hover:bg-black/5 dark:hover:bg-cream/5"
}`}
>
{link.label}
</Link>
))}
</nav>
</div>
)}
</header>
);
}