"use client"; import { AlignJustify, BadgeDollarSign, BitcoinIcon, ChevronRight, Columns3Cog, Compass, ExternalLink, Form, Grid3x3, Home, IdCard, Layout, List, Package, Plus, ScrollText, Server, Settings, Table, UserSquare2, X, } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import type { ComponentType } from "react"; import { useEffect, useRef, useState } from "react"; import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; type NavbarEntry = { name: string; icon: ComponentType<{ size?: number; name?: string; className?: string }>; divide?: boolean; last?: boolean; } & ({ href: string; sub?: never } | { sub: NavbarEntry[]; href?: never }); function VersionBadge({ name, className, }: { name?: string; className?: string; }) { return ( {name?.match(/[0-9.]+$/)?.[1]} ); } function isActive(entry: NavbarEntry, pathname: string): boolean { if ("href" in entry && entry.href) return pathname === entry.href || pathname.startsWith(`${entry.href}/`); if ("sub" in entry && entry.sub) return entry.sub.some((s) => isActive(s, pathname)); return false; } function findActiveParent( items: NavbarEntry[], pathname: string, ): NavbarEntry | null { for (const item of items) { if ("sub" in item && item.sub) { if (item.sub.some((s) => isActive(s, pathname))) return item; } } return null; } function DropdownMenu({ items, showLabels, onClose, depth = 0, }: { items: NavbarEntry[]; showLabels: boolean; onClose: () => void; depth?: number; }) { const pathname = usePathname(); const [openSub, setOpenSub] = useState(null); return (
0 && "ml-2 border-l border-white/10 pl-2", )} > {items.map((item) => { const Icon = item.icon; const active = isActive(item, pathname); const hasSub = "sub" in item && item.sub; const isOpen = openSub === item.name; return (
{hasSub ? ( <> {isOpen && ( )} ) : ( {item.name} )}
); })}
); } export function AdminNavbar({ adminShortcuts, tierlistTypes, tierlistVersions, }: { adminShortcuts: { name: string; url: string; }[]; tierlistTypes: { id: string; name: string; count: number; }[]; tierlistVersions: { id: string; name: string; }[]; }) { const pathname = usePathname(); const isMobile = useIsMobile(); const [showLabels, setShowLabels] = useState(false); const [openDropdown, setOpenDropdown] = useState(null); const navRef = useRef(null); const items: NavbarEntry[] = [ { name: "หน้าหลัก", href: "/admin", icon: Home, divide: true }, { name: "Tierlist", icon: Table, sub: [ { name: "Layout", icon: Columns3Cog, href: "/admin/tl/layout" }, ...tierlistTypes.map((type) => ({ name: type.name, icon: Layout, href: `/admin/tl/${type.id}`, })), ], }, { name: "Guide", icon: Compass, href: "/admin/guide" }, { name: "Versions", icon: List, sub: [ { name: "Types", icon: Form, href: "/admin/ver/types" }, { name: "Create", icon: Plus, href: "/admin/ver/create" }, ...tierlistVersions.map((v) => ({ name: v.name, icon: VersionBadge, href: `/admin/ver/${v.id}`, })), ], }, { name: "Global", icon: Server, sub: [ { name: "Admin Pages", icon: ExternalLink, sub: [ { name: "Artifact", icon: IdCard, href: "/artifact/admin" }, { name: "Rubgram", icon: BadgeDollarSign, href: "/rubgram/admin" }, { name: "Donate", icon: BitcoinIcon, href: "/donate/admin" }, { name: "Tierlist", icon: Grid3x3, sub: adminShortcuts.map((s) => ({ name: s.name, icon: VersionBadge, href: `/tl/${s.url}/admin`, })), }, ], }, { name: "Characters", icon: UserSquare2, href: "/admin/char" }, { name: "CDN", icon: Package, href: "/admin/cdn" }, { name: "Audit Log", icon: ScrollText, href: "/admin/log" }, { name: "Settings", icon: Settings, href: "/admin/settings" }, ], }, ]; const [scopeOverride, setScopeOverride] = useState<"root" | null>(null); // Reset override whenever pathname changes (navigating resets scope) // biome-ignore lint/correctness/useExhaustiveDependencies: used for other purepose useEffect(() => { setScopeOverride(null); }, [pathname]); const activeParent = scopeOverride === "root" ? null : findActiveParent(items, pathname); const prefixParent = scopeOverride === "root" ? findActiveParent(items, pathname) : activeParent; useEffect(() => { function handleClick(e: MouseEvent) { if (navRef.current && !navRef.current.contains(e.target as Node)) { setOpenDropdown(null); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, []); if (!isMobile) return ""; return (
{prefixParent && ( )}
{(activeParent ? activeParent.sub! : items).map((item) => { const Icon = item.icon; const active = isActive(item, pathname); const hasSub = "sub" in item && item.sub; const isOpen = openDropdown === item.name; const buttonContent = ( <> {showLabels && ( {item.name} )} ); const buttonClass = cn( "flex shrink-0 items-center gap-1.5 rounded px-2.5 py-1.5 transition-colors", active || isOpen ? "bg-white/15 text-white" : "text-white/50 hover:bg-white/10 hover:text-white", ); if (hasSub) { return ( ); } return ( {buttonContent} ); })}
{openDropdown && (() => { const entry = (activeParent ? activeParent.sub! : items).find( (i) => i.name === openDropdown, ); if (!entry || !("sub" in entry) || !entry.sub) return null; return (
setOpenDropdown(null)} />
); })()}
); }