Skip to content

Commit

Permalink
fix: display menu dropdown when sidebar collapsed (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
satnaing authored Dec 13, 2024
1 parent c1b7873 commit 8d3c83b
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 69 deletions.
189 changes: 130 additions & 59 deletions src/components/layout/nav-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,75 +18,35 @@ import {
useSidebar,
} from '@/components/ui/sidebar'
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) {
const { setOpenMobile } = useSidebar()
const { state } = useSidebar()
const href = useLocation({ select: (location) => location.href })
return (
<SidebarGroup>
<SidebarGroupLabel>{title}</SidebarGroupLabel>
<SidebarMenu>
{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 (
<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>
</SidebarGroup>
Expand All @@ -97,6 +57,117 @@ const NavBadge = ({ children }: { children: ReactNode }) => (
<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) {
return (
href === item.url || // /endpint?search=param
Expand Down
22 changes: 12 additions & 10 deletions src/components/layout/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ interface BaseNavItem {
icon?: React.ElementType
}

export type NavItem =
| (BaseNavItem & {
items: (BaseNavItem & { url: LinkProps['to'] })[]
url?: never
})
| (BaseNavItem & {
url: LinkProps['to']
items?: never
})
type NavLink = BaseNavItem & {
url: LinkProps['to']
items?: never
}

type NavCollapsible = BaseNavItem & {
items: (BaseNavItem & { url: LinkProps['to'] })[]
url?: never
}

type NavItem = NavCollapsible | NavLink

interface NavGroup {
title: string
Expand All @@ -39,4 +41,4 @@ interface SidebarData {
navGroups: NavGroup[]
}

export type { SidebarData, NavGroup }
export type { SidebarData, NavGroup, NavItem, NavCollapsible, NavLink }

0 comments on commit 8d3c83b

Please sign in to comment.