Files
Nexumi/apps/webui/src/components/layout/sidebar.tsx
TheOnlyMace 75ac91eecc Add new features and enhancements to the WebUI and dashboard components
- Updated package.json to include new dependencies: `@radix-ui/react-dialog` and `cmdk`.
- Enhanced global styles in globals.css for improved visual depth in dark mode.
- Refactored dashboard page components to improve layout and accessibility, including new icons and responsive design adjustments.
- Implemented suspense handling in commands page for better loading states.
- Improved sidebar navigation with new icons and mobile responsiveness.
- Added FieldAnchor components across various forms for better accessibility and structure.
- Enhanced commands manager with search parameter handling for improved user experience.
- Updated multiple module forms to include FieldAnchor for consistent layout and accessibility.
2026-07-22 19:44:55 +02:00

175 lines
4.8 KiB
TypeScript

'use client';
import { DASHBOARD_MODULES, type DashboardModuleGroup, type DashboardModuleId } from '@nexumi/shared';
import {
Archive,
BarChart3,
Cake,
CalendarClock,
Coins,
Gamepad2,
Gift,
Hash,
LayoutGrid,
MessageSquareQuote,
MessagesSquare,
PartyPopper,
Radio,
Rss,
Settings,
Shield,
ShieldAlert,
ShieldCheck,
SlidersHorizontal,
Sparkles,
Star,
Terminal,
Ticket,
UserCheck,
Volume2,
type LucideIcon
} from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useTranslations } from '@/components/locale-provider';
import { cn } from '@/lib/utils';
const MODULE_GROUP_ORDER: DashboardModuleGroup[] = [
'moderation',
'engagement',
'community',
'utility',
'integrations'
];
const MODULE_ICONS: Record<DashboardModuleId, LucideIcon> = {
moderation: Shield,
automod: ShieldAlert,
logging: Hash,
welcome: PartyPopper,
verification: UserCheck,
leveling: Sparkles,
economy: Coins,
fun: Gamepad2,
giveaways: Gift,
tickets: Ticket,
selfroles: UserCheck,
tags: MessageSquareQuote,
starboard: Star,
suggestions: MessagesSquare,
birthdays: Cake,
tempvoice: Volume2,
stats: BarChart3,
feeds: Rss,
scheduler: CalendarClock,
guildbackup: Archive
};
interface SidebarNavProps {
guildId: string;
onNavigate?: () => void;
className?: string;
}
function NavLink({
href,
active,
onNavigate,
children
}: {
href: string;
active: boolean;
onNavigate?: () => void;
children: React.ReactNode;
}) {
return (
<Link
href={href}
onClick={onNavigate}
className={cn(
'flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors',
active
? 'border-l-2 border-primary bg-primary/10 pl-2 text-foreground'
: 'border-l-2 border-transparent text-muted-foreground hover:bg-accent/60 hover:text-foreground'
)}
>
{children}
</Link>
);
}
export function SidebarNav({ guildId, onNavigate, className }: SidebarNavProps) {
const pathname = usePathname();
const t = useTranslations();
const base = `/dashboard/${guildId}`;
const groups = MODULE_GROUP_ORDER.map((group) => ({
group,
modules: DASHBOARD_MODULES.filter((module) => module.group === group)
}));
return (
<nav className={cn('flex h-full flex-col gap-5 overflow-y-auto px-3 py-4', className)}>
<Link
href={base}
onClick={onNavigate}
className="mb-1 flex items-center gap-2.5 rounded-md px-2.5 py-1.5 transition-opacity hover:opacity-90"
>
<Image src="/nexumi-logo.svg" alt="" width={28} height={28} className="size-7" />
<span className="text-base font-semibold tracking-tight">Nexumi</span>
</Link>
<div className="flex flex-col gap-0.5">
<NavLink href={base} active={pathname === base} onNavigate={onNavigate}>
<LayoutGrid className="size-4 shrink-0" />
{t('nav.overview')}
</NavLink>
<NavLink href={`${base}/settings`} active={pathname === `${base}/settings`} onNavigate={onNavigate}>
<Settings className="size-4 shrink-0" />
{t('nav.settings')}
</NavLink>
<NavLink href={`${base}/modules`} active={pathname === `${base}/modules`} onNavigate={onNavigate}>
<SlidersHorizontal className="size-4 shrink-0" />
{t('nav.modules')}
</NavLink>
<NavLink href={`${base}/access`} active={pathname === `${base}/access`} onNavigate={onNavigate}>
<ShieldCheck className="size-4 shrink-0" />
{t('nav.access')}
</NavLink>
<NavLink href={`${base}/commands`} active={pathname === `${base}/commands`} onNavigate={onNavigate}>
<Terminal className="size-4 shrink-0" />
{t('nav.commands')}
</NavLink>
</div>
{groups.map(({ group, modules }) => (
<div key={group} className="flex flex-col gap-0.5">
<p className="mb-1 px-2.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/80">
{t(`moduleGroups.${group}`)}
</p>
{modules.map((module) => {
const href = `${base}/${module.href}`;
const Icon = MODULE_ICONS[module.id] ?? Radio;
return (
<NavLink key={module.id} href={href} active={pathname === href} onNavigate={onNavigate}>
<Icon className="size-4 shrink-0" />
{t(`modules.${module.id}.label`)}
</NavLink>
);
})}
</div>
))}
</nav>
);
}
/** Desktop sidebar chrome. */
export function Sidebar({ guildId }: { guildId: string }) {
return (
<aside className="hidden h-screen w-60 shrink-0 border-r border-border bg-card/40 lg:sticky lg:top-0 lg:block">
<SidebarNav guildId={guildId} />
</aside>
);
}