223 lines
7.9 KiB
TypeScript
223 lines
7.9 KiB
TypeScript
"use client";
|
||
|
||
import { ChevronRight, type LucideIcon } from "lucide-react";
|
||
|
||
import {
|
||
Collapsible,
|
||
CollapsibleContent,
|
||
CollapsibleTrigger,
|
||
} from "@/components/ui/collapsible";
|
||
import {
|
||
SidebarGroup,
|
||
SidebarMenu,
|
||
SidebarMenuButton,
|
||
SidebarMenuItem,
|
||
SidebarMenuSub,
|
||
SidebarMenuSubButton,
|
||
SidebarMenuSubItem,
|
||
useSidebar,
|
||
} from "@/components/ui/sidebar";
|
||
import {
|
||
DropdownMenu,
|
||
DropdownMenuContent,
|
||
DropdownMenuItem,
|
||
DropdownMenuTrigger,
|
||
} from "@/components/ui/dropdown-menu";
|
||
import { usePathname } from "next/navigation";
|
||
import Link from "next/link";
|
||
import * as React from "react";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
export type NavGuideState = {
|
||
highlightedItem?: string;
|
||
expandedItems?: string[];
|
||
};
|
||
|
||
export function NavMain({
|
||
items,
|
||
guideState,
|
||
}: {
|
||
items: {
|
||
title: string;
|
||
url: string;
|
||
icon?: LucideIcon;
|
||
isActive?: boolean;
|
||
items?: {
|
||
title: string;
|
||
url: string;
|
||
icon?: LucideIcon;
|
||
}[];
|
||
}[];
|
||
guideState?: NavGuideState;
|
||
}) {
|
||
const pathname = usePathname();
|
||
const { state } = useSidebar();
|
||
const isCollapsed = state === "collapsed";
|
||
const highlightedItem = guideState?.highlightedItem;
|
||
|
||
return (
|
||
<SidebarGroup>
|
||
<SidebarMenu>
|
||
{items.map((item) => {
|
||
const hasSubmenu = item.items && item.items.length > 0;
|
||
const isActive = pathname === item.url;
|
||
const hasActiveSubmenu =
|
||
hasSubmenu && item.items!.some((sub) => pathname === sub.url);
|
||
const shouldForceOpen = guideState?.expandedItems?.includes(item.title);
|
||
const isGuideHighlight = highlightedItem === item.title;
|
||
const highlightedButtonClass = isGuideHighlight
|
||
? "ring-2 ring-primary/70"
|
||
: undefined;
|
||
|
||
// В свернутом состоянии показываем DropdownMenu для элементов с подменю
|
||
if (isCollapsed && hasSubmenu) {
|
||
return (
|
||
<SidebarMenuItem key={item.title}>
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<SidebarMenuButton
|
||
tooltip={item.title}
|
||
isActive={isActive || hasActiveSubmenu}
|
||
className={cn(highlightedButtonClass)}
|
||
>
|
||
{item.icon && (
|
||
<item.icon
|
||
className={cn(
|
||
"h-4 w-4",
|
||
(isActive || hasActiveSubmenu) &&
|
||
"text-[#2B1D49] dark:text-[#5F31F4]"
|
||
)}
|
||
/>
|
||
)}
|
||
<span>{item.title}</span>
|
||
</SidebarMenuButton>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent
|
||
side="right"
|
||
align="start"
|
||
className="min-w-[200px]"
|
||
>
|
||
{item.items!.map((subItem) => {
|
||
const isSubActive = pathname === subItem.url;
|
||
return (
|
||
<DropdownMenuItem key={subItem.title} asChild>
|
||
<Link
|
||
href={subItem.url}
|
||
className={cn(
|
||
"flex items-center gap-2",
|
||
isSubActive
|
||
? "bg-accent text-accent-foreground"
|
||
: ""
|
||
)}
|
||
>
|
||
{subItem.icon && (
|
||
<subItem.icon
|
||
className={cn(
|
||
"h-4 w-4",
|
||
isSubActive &&
|
||
"text-[#2B1D49] dark:text-[#5F31F4]"
|
||
)}
|
||
/>
|
||
)}
|
||
<span>{subItem.title}</span>
|
||
</Link>
|
||
</DropdownMenuItem>
|
||
);
|
||
})}
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</SidebarMenuItem>
|
||
);
|
||
}
|
||
|
||
// Обычное отображение для развернутого состояния
|
||
return (
|
||
<Collapsible
|
||
key={`${item.title}-${shouldForceOpen ? "guide" : "default"}`}
|
||
asChild
|
||
defaultOpen={
|
||
shouldForceOpen || isActive || hasActiveSubmenu
|
||
}
|
||
className="group/collapsible"
|
||
>
|
||
<SidebarMenuItem>
|
||
{hasSubmenu ? (
|
||
<>
|
||
<CollapsibleTrigger asChild>
|
||
<SidebarMenuButton
|
||
tooltip={item.title}
|
||
isActive={isActive || hasActiveSubmenu}
|
||
className={cn(highlightedButtonClass)}
|
||
>
|
||
{item.icon && (
|
||
<item.icon
|
||
className={cn(
|
||
"h-4 w-4",
|
||
(isActive || hasActiveSubmenu) &&
|
||
"text-[#2B1D49] dark:text-[#5F31F4]"
|
||
)}
|
||
/>
|
||
)}
|
||
<span>{item.title}</span>
|
||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||
</SidebarMenuButton>
|
||
</CollapsibleTrigger>
|
||
<CollapsibleContent>
|
||
<SidebarMenuSub>
|
||
{item.items!.map((subItem) => {
|
||
const isSubActive = pathname === subItem.url;
|
||
return (
|
||
<SidebarMenuSubItem key={subItem.title}>
|
||
<SidebarMenuSubButton
|
||
asChild
|
||
isActive={isSubActive}
|
||
>
|
||
<Link href={subItem.url} className="flex items-center gap-2">
|
||
{subItem.icon && (
|
||
<subItem.icon
|
||
className={cn(
|
||
"h-4 w-4",
|
||
isSubActive &&
|
||
"text-[#2B1D49] dark:text-[#5F31F4]"
|
||
)}
|
||
/>
|
||
)}
|
||
<span>{subItem.title}</span>
|
||
</Link>
|
||
</SidebarMenuSubButton>
|
||
</SidebarMenuSubItem>
|
||
);
|
||
})}
|
||
</SidebarMenuSub>
|
||
</CollapsibleContent>
|
||
</>
|
||
) : (
|
||
<SidebarMenuButton
|
||
asChild
|
||
tooltip={item.title}
|
||
isActive={isActive}
|
||
className={cn(highlightedButtonClass)}
|
||
>
|
||
<Link href={item.url} className="flex items-center gap-2">
|
||
{item.icon && (
|
||
<item.icon
|
||
className={cn(
|
||
"h-4 w-4",
|
||
isActive &&
|
||
"text-[#2B1D49] dark:text-[#5F31F4]"
|
||
)}
|
||
/>
|
||
)}
|
||
<span>{item.title}</span>
|
||
</Link>
|
||
</SidebarMenuButton>
|
||
)}
|
||
</SidebarMenuItem>
|
||
</Collapsible>
|
||
);
|
||
})}
|
||
</SidebarMenu>
|
||
</SidebarGroup>
|
||
);
|
||
}
|