122 lines
4.4 KiB
TypeScript
Executable File
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>
|
|
);
|
|
}
|