Files
zero-fucks-given/apps/admin/src/app/layouts/08__DropdownMenu/components/SidebarMenu.tsx
T

122 lines
4.4 KiB
TypeScript
Executable File

import { BarChart2, Settings, ShoppingCart, User, Users } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom';
import { cn } from '@crux-kit/utils';
import { MenuConfig } from '#app/config/types';
import { useMenuConfig } from '#app/menu';
import { useMenu } from '#shared/hooks/useMenu';
import { createMenuPathMatcher } from '#shared/lib/menuPath';
import { Button } from '#shared/ui/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '#shared/ui/DropdownMenu';
export function SidebarMenu() {
const { pathname } = useLocation();
const { hasActiveChild } = useMenu(pathname);
const config = useMenuConfig();
const menuConfig: MenuConfig = [
{
title: 'Boards',
icon: BarChart2,
path: '/'
},
{
title: 'Profiles',
icon: User,
children: config?.[2].children
},
{
title: 'Account',
icon: Settings,
children: config?.[3].children
},
{
title: 'Store',
icon: ShoppingCart,
children: config?.[7].children
},
{
title: 'Network',
icon: Users,
children: config?.[4].children
}
];
// A leaf highlights only when nothing deeper in the menu matches the route more closely.
const isActive = createMenuPathMatcher(menuConfig, pathname);
const buildMenu = (items: MenuConfig) => {
return items.map((item, index) => (
<div key={index} className="flex flex-col items-center">
{item.children ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
data-here={hasActiveChild(item.children) || undefined}
className={cn(
'flex h-15 w-15 flex-col items-center justify-center gap-1 rounded-lg shadow-none',
'text-secondary-foreground bg-transparent text-xs font-medium',
'hover:text-primary hover:bg-background hover:border-border',
'data-[state=open]:text-primary data-[state=open]:bg-background data-[state=open]:border-border',
'data-[here=true]:text-primary data-[here=true]:bg-background data-[here=true]:border-border'
)}
>
{item.icon && <item.icon className="size-5!" />}
{item.title}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" side="right" className="w-50">
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
{buildMenuChildren(item.children)}
</DropdownMenuContent>
</DropdownMenu>
) : (
<Link
data-active={isActive(item.path) || undefined}
to={item.path || '#'}
className={cn(
'flex h-15 w-15.5 flex-col items-center justify-center gap-1 rounded-lg p-2',
'text-muted-foreground bg-transparent text-xs font-medium',
'hover:text-primary hover:bg-background hover:border-border',
'data-[active=true]:text-primary data-[active=true]:bg-background data-[active=true]:border-border'
)}
>
{item.icon && <item.icon className="size-5!" />}
{item.title}
</Link>
)}
</div>
));
};
const buildMenuChildren = (items: MenuConfig) => {
return items.map((item, index) => {
if (item.disabled) return null;
if (item.separator) return <DropdownMenuSeparator key={index} />;
if (item.children) {
return (
<DropdownMenuSub key={index}>
<DropdownMenuSubTrigger data-here={hasActiveChild(item.children) || undefined}>
{item.title}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-50">
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
{buildMenuChildren(item.children)}
</DropdownMenuSubContent>
</DropdownMenuSub>
);
}
return (
<DropdownMenuItem key={index} asChild>
<Link to={item.path || '#'}>{item.title}</Link>
</DropdownMenuItem>
);
});
};
return (
<div className="kt-scrollable-y-auto flex max-h-[calc(100vh-5rem)] grow flex-col gap-2.5 lg:max-h-[calc(100vh-6rem)]">
{buildMenu(menuConfig)}
</div>
);
}