| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import { type ReactNode } from 'react'
- import { Link, useLocation } from '@tanstack/react-router'
- import { ChevronRight } from 'lucide-react'
- import {
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
- } from '@/components/ui/collapsible'
- import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- useSidebar,
- } from '@/components/ui/sidebar'
- import { Badge } from '../ui/badge'
- import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
- } from '../ui/dropdown-menu'
- import { type NavCollapsible, type NavItem, type NavLink, type NavGroup } from './types'
- export function NavGroup({ title, items }: NavGroup) {
- const { state, isMobile } = useSidebar()
- const href = useLocation({ select: (location) => location.href })
- return (
- <SidebarGroup>
- <SidebarGroupLabel>{title}</SidebarGroupLabel>
- <SidebarMenu>
- {items.map((item) => {
- const key = `${item.title}-${item.url}`
- if (!item.items)
- return <SidebarMenuLink key={key} item={item} href={href} />
- if (state === 'collapsed' && !isMobile)
- return (
- <SidebarMenuCollapsedDropdown key={key} item={item} href={href} />
- )
- return <SidebarMenuCollapsible key={key} item={item} href={href} />
- })}
- </SidebarMenu>
- </SidebarGroup>
- )
- }
- function NavBadge({ children }: { children: ReactNode }) {
- return <Badge className='rounded-full px-1 py-0 text-xs'>{children}</Badge>
- }
- function 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>
- )
- }
- function 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='ms-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>
- )
- }
- function 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='ms-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='ms-auto text-xs'>{sub.badge}</span>
- )}
- </Link>
- </DropdownMenuItem>
- ))}
- </DropdownMenuContent>
- </DropdownMenu>
- </SidebarMenuItem>
- )
- }
- function checkIsActive(href: string, item: NavItem, mainNav = false) {
- return (
- href === item.url || // /endpint?search=param
- href.split('?')[0] === item.url || // endpoint
- !!item?.items?.filter((i) => i.url === href).length || // if child nav is active
- (mainNav &&
- href.split('/')[1] !== '' &&
- href.split('/')[1] === item?.url?.split('/')[1])
- )
- }
|