Files
lotteryFront/src/components/layout/player-sidebar.tsx
kang 7d03ffa30b
Some checks failed
lotteryfront CI / build (push) Has been cancelled
feat(player): PC 侧栏布局、顶栏与页内间距优化
- 桌面端侧栏 + 全局顶栏(Logo 在侧栏顶、激活态对齐后台红底白字)
- 移除各页重复页内顶栏,统一主区与卡片顶部留白
- 视口遮罩/弹层约束到主内容区,移动端保留底栏与顶栏 Logo
2026-06-26 18:02:19 +08:00

118 lines
3.7 KiB
TypeScript

"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
BarChart3,
BookOpen,
ClipboardList,
Home,
Wallet,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { isCreditFundingPlayer } from "@/lib/player-funding-mode";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
const navItems = [
{
href: "/hall",
labelKey: "nav.home",
labelDefault: "首页",
icon: Home,
match: (p: string) => p === "/hall",
},
{
href: "/results",
labelKey: "nav.results",
labelDefault: "开奖结果",
icon: BarChart3,
match: (p: string) => p === "/results" || p.startsWith("/results/"),
},
{
href: "/orders",
labelKey: "nav.orders",
labelDefault: "我的注单",
icon: ClipboardList,
match: (p: string) => p === "/orders" || p.startsWith("/orders/"),
},
{
href: "/wallet",
labelKey: "nav.wallet",
creditLabelKey: "nav.credit",
labelDefault: "钱包",
creditLabelDefault: "信用",
icon: Wallet,
match: (p: string) => p === "/wallet" || p.startsWith("/wallet/"),
},
{
href: "/rules",
labelKey: "nav.rules",
labelDefault: "规则",
icon: BookOpen,
match: (p: string) => p === "/rules",
},
] as const;
/** 与 lotteryadmin `NAV_BTN` / `NAV_ACTIVE` 一致 */
const playerNavLinkClass =
"flex h-8 w-full items-center gap-2 rounded-md px-2.5 text-[13px] leading-snug font-normal text-sidebar-foreground/90 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0";
const playerNavLinkActiveClass =
"bg-sidebar-primary/90 font-medium text-sidebar-primary-foreground shadow-sm hover:bg-sidebar-primary/90 hover:text-sidebar-primary-foreground";
/** 桌面端左侧导航(激活态对齐后台侧栏) */
export function PlayerSidebar() {
const pathname = usePathname() ?? "";
const { t } = useTranslation("player");
const creditMode = isCreditFundingPlayer(usePlayerSessionStore((state) => state.profile));
return (
<aside
className="player-sidebar hidden h-full w-56 shrink-0 flex-col border-r border-[#e4ebf5] bg-white lg:flex"
aria-label={t("nav.aria")}
>
<div className="shrink-0 border-b border-[#e4ebf5] px-2 py-2">
<Link href="/hall" className="flex h-10 items-center px-1" aria-label="N lotto">
<Image
src="/logo.png"
alt="N lotto"
width={243}
height={84}
className="h-auto max-h-10 w-full object-contain object-left"
priority
/>
</Link>
</div>
<nav className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-1.5 py-1.5">
{navItems.map(({ href, labelKey, labelDefault, icon: Icon, match, ...item }) => {
const active = match(pathname);
const creditLabelKey = "creditLabelKey" in item ? item.creditLabelKey : undefined;
const creditLabelDefault =
"creditLabelDefault" in item ? item.creditLabelDefault : undefined;
const label =
creditMode && creditLabelKey
? t(creditLabelKey, { defaultValue: creditLabelDefault ?? labelDefault })
: t(labelKey, { defaultValue: labelDefault });
return (
<Link
key={href}
href={href}
prefetch
aria-current={active ? "page" : undefined}
className={cn(playerNavLinkClass, active && playerNavLinkActiveClass)}
>
<Icon aria-hidden />
<span className="truncate">{label}</span>
</Link>
);
})}
</nav>
</aside>
);
}