nav-group.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { ReactNode } from 'react'
  2. import { Link, useLocation } from '@tanstack/react-router'
  3. import { ChevronRight } from 'lucide-react'
  4. import {
  5. Collapsible,
  6. CollapsibleContent,
  7. CollapsibleTrigger,
  8. } from '@/components/ui/collapsible'
  9. import {
  10. SidebarGroup,
  11. SidebarGroupLabel,
  12. SidebarMenu,
  13. SidebarMenuButton,
  14. SidebarMenuItem,
  15. SidebarMenuSub,
  16. SidebarMenuSubButton,
  17. SidebarMenuSubItem,
  18. useSidebar,
  19. } from '@/components/ui/sidebar'
  20. import { Badge } from '../ui/badge'
  21. import { NavItem, type NavGroup } from './types'
  22. export function NavGroup({ title, items }: NavGroup) {
  23. const { setOpenMobile } = useSidebar()
  24. const href = useLocation({ select: (location) => location.href })
  25. return (
  26. <SidebarGroup>
  27. <SidebarGroupLabel>{title}</SidebarGroupLabel>
  28. <SidebarMenu>
  29. {items.map((item) => {
  30. if (!item.items) {
  31. return (
  32. <SidebarMenuItem key={item.title}>
  33. <SidebarMenuButton
  34. asChild
  35. isActive={checkIsActive(href, item)}
  36. tooltip={item.title}
  37. >
  38. <Link to={item.url} onClick={() => setOpenMobile(false)}>
  39. {item.icon && <item.icon />}
  40. <span>{item.title}</span>
  41. {item.badge && <NavBadge>{item.badge}</NavBadge>}
  42. </Link>
  43. </SidebarMenuButton>
  44. </SidebarMenuItem>
  45. )
  46. }
  47. return (
  48. <Collapsible
  49. key={item.title}
  50. asChild
  51. defaultOpen={checkIsActive(href, item, true)}
  52. className='group/collapsible'
  53. >
  54. <SidebarMenuItem>
  55. <CollapsibleTrigger asChild>
  56. <SidebarMenuButton tooltip={item.title}>
  57. {item.icon && <item.icon />}
  58. <span>{item.title}</span>
  59. {item.badge && <NavBadge>{item.badge}</NavBadge>}
  60. <ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
  61. </SidebarMenuButton>
  62. </CollapsibleTrigger>
  63. <CollapsibleContent className='CollapsibleContent'>
  64. <SidebarMenuSub>
  65. {item.items.map((subItem) => (
  66. <SidebarMenuSubItem key={subItem.title}>
  67. <SidebarMenuSubButton
  68. asChild
  69. isActive={checkIsActive(href, subItem)}
  70. >
  71. <Link
  72. to={subItem.url}
  73. onClick={() => setOpenMobile(false)}
  74. >
  75. {subItem.icon && <subItem.icon />}
  76. <span>{subItem.title}</span>
  77. {subItem.badge && (
  78. <NavBadge>{subItem.badge}</NavBadge>
  79. )}
  80. </Link>
  81. </SidebarMenuSubButton>
  82. </SidebarMenuSubItem>
  83. ))}
  84. </SidebarMenuSub>
  85. </CollapsibleContent>
  86. </SidebarMenuItem>
  87. </Collapsible>
  88. )
  89. })}
  90. </SidebarMenu>
  91. </SidebarGroup>
  92. )
  93. }
  94. const NavBadge = ({ children }: { children: ReactNode }) => (
  95. <Badge className='text-xs rounded-full px-1 py-0'>{children}</Badge>
  96. )
  97. function checkIsActive(href: string, item: NavItem, mainNav = false) {
  98. return (
  99. href === item.url || // /endpint?search=param
  100. href.split('?')[0] === item.url || // endpoint
  101. !!item?.items?.filter((i) => i.url === href).length || // if child nav is active
  102. (mainNav &&
  103. href.split('/')[1] !== '' &&
  104. href.split('/')[1] === item?.url?.split('/')[1])
  105. )
  106. }