Files
tgex-frontend/components/nav-main.tsx
2026-01-21 10:36:08 +03:00

271 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 shouldHighlightButton = item.title === "Аналитика"
? false
: isActive;
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={shouldHighlightButton}
className={cn(highlightedButtonClass)}
>
{item.icon && (
<item.icon
className={cn(
"h-4 w-4 transition-colors duration-200",
shouldHighlightButton
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
: "text-muted-foreground/90"
)}
/>
)}
<span
className={cn(
"transition-colors duration-200",
shouldHighlightButton
? "text-foreground font-medium"
: "text-muted-foreground/90"
)}
>{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 transition-colors duration-200",
isSubActive
? "bg-accent text-accent-foreground"
: "text-muted-foreground hover:text-foreground"
)}
>
{subItem.icon && (
<subItem.icon
className={cn(
"h-4 w-4 transition-colors duration-200",
isSubActive
? "!text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
: "!text-muted-foreground/90"
)}
/>
)}
<span className={cn(
isSubActive
? "text-foreground font-medium"
: "text-muted-foreground/90"
)}>{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={shouldHighlightButton}
className={cn(highlightedButtonClass)}
>
{item.icon && (
<item.icon
className={cn(
"h-4 w-4 transition-colors duration-200",
shouldHighlightButton
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
: "text-muted-foreground/90"
)}
/>
)}
<span
className={cn(
"transition-colors duration-200",
shouldHighlightButton
? "text-foreground font-medium"
: "text-muted-foreground/90"
)}
>{item.title}</span>
<ChevronRight
className={cn(
"ml-auto transition-transform duration-200",
shouldHighlightButton
? "text-[hsl(262_83%_65%)]"
: "text-muted-foreground/60",
"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 transition-colors duration-200",
isSubActive
? "!text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
: "!text-muted-foreground/90"
)}
/>
)}
<span
className={cn(
"transition-colors duration-200",
isSubActive
? "text-foreground font-medium"
: "text-muted-foreground/90"
)}
>{subItem.title}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</>
) : (
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={shouldHighlightButton}
className={cn(highlightedButtonClass)}
>
<Link href={item.url} className="flex items-center gap-2">
{item.icon && (
<item.icon
className={cn(
"h-4 w-4 transition-colors duration-200",
shouldHighlightButton
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
: "text-muted-foreground/90"
)}
/>
)}
<span
className={cn(
"transition-colors duration-200",
shouldHighlightButton
? "text-foreground font-medium"
: "text-muted-foreground/90"
)}
>{item.title}</span>
</Link>
</SidebarMenuButton>
)}
</SidebarMenuItem>
</Collapsible>
);
})}
</SidebarMenu>
</SidebarGroup>
);
}