|
@@ -18,75 +18,35 @@ import {
|
|
|
useSidebar,
|
|
useSidebar,
|
|
|
} from '@/components/ui/sidebar'
|
|
} from '@/components/ui/sidebar'
|
|
|
import { Badge } from '../ui/badge'
|
|
import { Badge } from '../ui/badge'
|
|
|
-import { NavItem, type NavGroup } from './types'
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ DropdownMenu,
|
|
|
|
|
+ DropdownMenuContent,
|
|
|
|
|
+ DropdownMenuItem,
|
|
|
|
|
+ DropdownMenuLabel,
|
|
|
|
|
+ DropdownMenuSeparator,
|
|
|
|
|
+ DropdownMenuTrigger,
|
|
|
|
|
+} from '../ui/dropdown-menu'
|
|
|
|
|
+import { NavCollapsible, NavItem, NavLink, type NavGroup } from './types'
|
|
|
|
|
|
|
|
export function NavGroup({ title, items }: NavGroup) {
|
|
export function NavGroup({ title, items }: NavGroup) {
|
|
|
- const { setOpenMobile } = useSidebar()
|
|
|
|
|
|
|
+ const { state } = useSidebar()
|
|
|
const href = useLocation({ select: (location) => location.href })
|
|
const href = useLocation({ select: (location) => location.href })
|
|
|
return (
|
|
return (
|
|
|
<SidebarGroup>
|
|
<SidebarGroup>
|
|
|
<SidebarGroupLabel>{title}</SidebarGroupLabel>
|
|
<SidebarGroupLabel>{title}</SidebarGroupLabel>
|
|
|
<SidebarMenu>
|
|
<SidebarMenu>
|
|
|
{items.map((item) => {
|
|
{items.map((item) => {
|
|
|
- if (!item.items) {
|
|
|
|
|
|
|
+ const key = `${item.title}-${item.url}`
|
|
|
|
|
+
|
|
|
|
|
+ if (!item.items)
|
|
|
|
|
+ return <SidebarMenuLink key={key} item={item} href={href} />
|
|
|
|
|
+
|
|
|
|
|
+ if (state === 'collapsed')
|
|
|
return (
|
|
return (
|
|
|
- <SidebarMenuItem key={item.title}>
|
|
|
|
|
- <SidebarMenuButton
|
|
|
|
|
- asChild
|
|
|
|
|
- isActive={checkIsActive(href, item)}
|
|
|
|
|
- tooltip={item.title}
|
|
|
|
|
- >
|
|
|
|
|
- <Link to={item.url} onClick={() => setOpenMobile(false)}>
|
|
|
|
|
- {item.icon && <item.icon />}
|
|
|
|
|
- <span>{item.title}</span>
|
|
|
|
|
- {item.badge && <NavBadge>{item.badge}</NavBadge>}
|
|
|
|
|
- </Link>
|
|
|
|
|
- </SidebarMenuButton>
|
|
|
|
|
- </SidebarMenuItem>
|
|
|
|
|
|
|
+ <SidebarMenuCollapsedDropdown key={key} item={item} href={href} />
|
|
|
)
|
|
)
|
|
|
- }
|
|
|
|
|
- return (
|
|
|
|
|
- <Collapsible
|
|
|
|
|
- key={item.title}
|
|
|
|
|
- asChild
|
|
|
|
|
- defaultOpen={checkIsActive(href, item, true)}
|
|
|
|
|
- className='group/collapsible'
|
|
|
|
|
- >
|
|
|
|
|
- <SidebarMenuItem>
|
|
|
|
|
- <CollapsibleTrigger asChild>
|
|
|
|
|
- <SidebarMenuButton tooltip={item.title}>
|
|
|
|
|
- {item.icon && <item.icon />}
|
|
|
|
|
- <span>{item.title}</span>
|
|
|
|
|
- {item.badge && <NavBadge>{item.badge}</NavBadge>}
|
|
|
|
|
- <ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
|
|
|
|
|
- </SidebarMenuButton>
|
|
|
|
|
- </CollapsibleTrigger>
|
|
|
|
|
- <CollapsibleContent className='CollapsibleContent'>
|
|
|
|
|
- <SidebarMenuSub>
|
|
|
|
|
- {item.items.map((subItem) => (
|
|
|
|
|
- <SidebarMenuSubItem key={subItem.title}>
|
|
|
|
|
- <SidebarMenuSubButton
|
|
|
|
|
- asChild
|
|
|
|
|
- isActive={checkIsActive(href, subItem)}
|
|
|
|
|
- >
|
|
|
|
|
- <Link
|
|
|
|
|
- to={subItem.url}
|
|
|
|
|
- onClick={() => setOpenMobile(false)}
|
|
|
|
|
- >
|
|
|
|
|
- {subItem.icon && <subItem.icon />}
|
|
|
|
|
- <span>{subItem.title}</span>
|
|
|
|
|
- {subItem.badge && (
|
|
|
|
|
- <NavBadge>{subItem.badge}</NavBadge>
|
|
|
|
|
- )}
|
|
|
|
|
- </Link>
|
|
|
|
|
- </SidebarMenuSubButton>
|
|
|
|
|
- </SidebarMenuSubItem>
|
|
|
|
|
- ))}
|
|
|
|
|
- </SidebarMenuSub>
|
|
|
|
|
- </CollapsibleContent>
|
|
|
|
|
- </SidebarMenuItem>
|
|
|
|
|
- </Collapsible>
|
|
|
|
|
- )
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return <SidebarMenuCollapsible key={key} item={item} href={href} />
|
|
|
})}
|
|
})}
|
|
|
</SidebarMenu>
|
|
</SidebarMenu>
|
|
|
</SidebarGroup>
|
|
</SidebarGroup>
|
|
@@ -97,6 +57,117 @@ const NavBadge = ({ children }: { children: ReactNode }) => (
|
|
|
<Badge className='text-xs rounded-full px-1 py-0'>{children}</Badge>
|
|
<Badge className='text-xs rounded-full px-1 py-0'>{children}</Badge>
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
|
|
|
|
|
+ const { setOpenMobile } = useSidebar()
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton
|
|
|
|
|
+ asChild
|
|
|
|
|
+ isActive={checkIsActive(href, item)}
|
|
|
|
|
+ tooltip={item.title}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Link to={item.url} onClick={() => setOpenMobile(false)}>
|
|
|
|
|
+ {item.icon && <item.icon />}
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ {item.badge && <NavBadge>{item.badge}</NavBadge>}
|
|
|
|
|
+ </Link>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const SidebarMenuCollapsible = ({
|
|
|
|
|
+ item,
|
|
|
|
|
+ href,
|
|
|
|
|
+}: {
|
|
|
|
|
+ item: NavCollapsible
|
|
|
|
|
+ href: string
|
|
|
|
|
+}) => {
|
|
|
|
|
+ const { setOpenMobile } = useSidebar()
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Collapsible
|
|
|
|
|
+ asChild
|
|
|
|
|
+ defaultOpen={checkIsActive(href, item, true)}
|
|
|
|
|
+ className='group/collapsible'
|
|
|
|
|
+ >
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <CollapsibleTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton tooltip={item.title}>
|
|
|
|
|
+ {item.icon && <item.icon />}
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ {item.badge && <NavBadge>{item.badge}</NavBadge>}
|
|
|
|
|
+ <ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </CollapsibleTrigger>
|
|
|
|
|
+ <CollapsibleContent className='CollapsibleContent'>
|
|
|
|
|
+ <SidebarMenuSub>
|
|
|
|
|
+ {item.items.map((subItem) => (
|
|
|
|
|
+ <SidebarMenuSubItem key={subItem.title}>
|
|
|
|
|
+ <SidebarMenuSubButton
|
|
|
|
|
+ asChild
|
|
|
|
|
+ isActive={checkIsActive(href, subItem)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Link to={subItem.url} onClick={() => setOpenMobile(false)}>
|
|
|
|
|
+ {subItem.icon && <subItem.icon />}
|
|
|
|
|
+ <span>{subItem.title}</span>
|
|
|
|
|
+ {subItem.badge && <NavBadge>{subItem.badge}</NavBadge>}
|
|
|
|
|
+ </Link>
|
|
|
|
|
+ </SidebarMenuSubButton>
|
|
|
|
|
+ </SidebarMenuSubItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenuSub>
|
|
|
|
|
+ </CollapsibleContent>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </Collapsible>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const SidebarMenuCollapsedDropdown = ({
|
|
|
|
|
+ item,
|
|
|
|
|
+ href,
|
|
|
|
|
+}: {
|
|
|
|
|
+ item: NavCollapsible
|
|
|
|
|
+ href: string
|
|
|
|
|
+}) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton
|
|
|
|
|
+ tooltip={item.title}
|
|
|
|
|
+ isActive={checkIsActive(href, item)}
|
|
|
|
|
+ >
|
|
|
|
|
+ {item.icon && <item.icon />}
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ {item.badge && <NavBadge>{item.badge}</NavBadge>}
|
|
|
|
|
+ <ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent side='right' align='start' sideOffset={4}>
|
|
|
|
|
+ <DropdownMenuLabel>
|
|
|
|
|
+ {item.title} {item.badge ? `(${item.badge})` : ''}
|
|
|
|
|
+ </DropdownMenuLabel>
|
|
|
|
|
+ <DropdownMenuSeparator />
|
|
|
|
|
+ {item.items.map((sub) => (
|
|
|
|
|
+ <DropdownMenuItem key={`${sub.title}-${sub.url}`} asChild>
|
|
|
|
|
+ <Link
|
|
|
|
|
+ to={sub.url}
|
|
|
|
|
+ className={`${checkIsActive(href, sub) ? 'bg-secondary' : ''}`}
|
|
|
|
|
+ >
|
|
|
|
|
+ {sub.icon && <sub.icon />}
|
|
|
|
|
+ <span className='max-w-52 text-wrap'>{sub.title}</span>
|
|
|
|
|
+ {sub.badge && (
|
|
|
|
|
+ <span className='ml-auto text-xs'>{sub.badge}</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Link>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function checkIsActive(href: string, item: NavItem, mainNav = false) {
|
|
function checkIsActive(href: string, item: NavItem, mainNav = false) {
|
|
|
return (
|
|
return (
|
|
|
href === item.url || // /endpint?search=param
|
|
href === item.url || // /endpint?search=param
|