UI-UX/src/components/NavLinks.tsx
iye d5ed43acbd feat(ui): design overhaul, global login modal, design spec
- nav: center links (首页/排行榜/我的), right-side AuthMenu + RemainingVotesBadge; image logo with responsive sizing
- auth: replace /login route with global LoginModal triggered anywhere; "我的" intercepts unauth users with post-login redirect
- home: full-screen Hero, redesigned Top12 (12 pill cards, top-3 glow), scroll-snap mandatory between Hero/Top12/candidates
- home: candidates section with sticky filter that gains frosted-glass bg when stuck (matches nav)
- filter: simplified tags (全部/舞蹈/声乐/rap/全能型); ArtistCard uniform purple vote button
- ranking/me: remove Top12Bar; me header stacks 编辑资料/退出登录 vertically
- typography: font-logo set to Orbitron; ✦ glyph in CYBER ✦ STAR preserved
- layout: max-w-[1500px] unified across pages
- docs: add design-spec.md + design-spec.html with full visual spec (lucide SVG, zero emoji policy)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 18:59:30 +08:00

110 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useSession } from "next-auth/react";
import { cn } from "@/lib/cn";
import { useLoginModalStore } from "@/lib/login-modal-store";
const NAV_ITEMS: Array<{
label: string;
href: string;
/** 若 true未登录时点击会拦截并弹出登录弹窗 */
requireAuth?: boolean;
}> = [
{ label: "首页", href: "/" },
{ label: "排行榜", href: "/ranking" },
{ label: "我的", href: "/me", requireAuth: true },
];
interface NavLinksProps {
className?: string;
mobile?: boolean;
}
export default function NavLinks({ className, mobile = false }: NavLinksProps) {
const pathname = usePathname();
const { status } = useSession();
const openLogin = useLoginModalStore((s) => s.show);
const isActive = (href: string) => {
if (href === "/") return pathname === "/";
return pathname.startsWith(href);
};
const handleClick = (
e: React.MouseEvent<HTMLAnchorElement>,
item: (typeof NAV_ITEMS)[number],
) => {
if (item.requireAuth && status !== "authenticated") {
e.preventDefault();
// 登录成功后直接跳目标页面(如「我的」→ /me不回首页
if (status === "unauthenticated") openLogin(item.href);
}
};
if (mobile) {
return (
<ul
className={cn(
"flex items-center gap-6 px-6 py-2.5 text-[13px] tracking-[0.1em] whitespace-nowrap",
className,
)}
>
{NAV_ITEMS.map((item) => {
const active = isActive(item.href);
return (
<li key={item.href}>
<Link
href={item.href}
onClick={(e) => handleClick(e, item)}
className={cn(
"transition-colors",
active ? "text-purple-300" : "text-white/55",
)}
>
{item.label}
</Link>
</li>
);
})}
</ul>
);
}
return (
<ul
className={cn(
"items-center gap-8 text-sm tracking-[0.1em]",
className,
)}
>
{NAV_ITEMS.map((item) => {
const active = isActive(item.href);
return (
<li key={item.href}>
<Link
href={item.href}
onClick={(e) => handleClick(e, item)}
className={cn(
"relative py-1 transition-colors",
active
? "text-purple-300 glow-text-purple"
: "text-white/65 hover:text-white",
)}
>
{item.label}
{active && (
<span
className="absolute -bottom-1 left-0 right-0 h-px bg-purple-400"
style={{ boxShadow: "0 0 8px #a78bfa" }}
/>
)}
</Link>
</li>
);
})}
</ul>
);
}