Overview
SIGEAC’s sidebar provides role-based navigation with collapsible menu items, nested submenus, and responsive behavior. It adapts between desktop (persistent sidebar) and mobile (sheet overlay).
Main navigation menu component with role-based filtering.
Location : components/sidebar/Menu.tsx:26
Props
isOpen
boolean | undefined
required
Controls sidebar expanded/collapsed state
Type Definition
interface MenuProps {
isOpen : boolean | undefined ;
}
Usage Example
import { Menu } from '@/components/sidebar/Menu' ;
import { useState } from 'react' ;
export function Sidebar () {
const [ isOpen , setIsOpen ] = useState ( true );
return (
< aside className = { cn (
"fixed left-0 top-0 z-40 h-screen transition-all" ,
isOpen ? "w-64" : "w-16"
) } >
< Menu isOpen = { isOpen } />
</ aside >
);
}
Features
Role-based menus : Filters menu items based on user roles
Nested navigation : Support for submenus with CollapseMenuButton
Tooltips : Show labels when sidebar is collapsed
Active state : Highlights current route
Scroll area : Handles long menu lists
Memoized : Prevents unnecessary recalculations
Menus are organized into groups with labels:
const menuList = [
{
groupLabel: 'Dashboard' ,
menus: [
{
href: '/dashboard' ,
label: 'Dashboard' ,
icon: LayoutDashboard ,
active: pathname === '/dashboard' ,
submenus: [],
},
],
},
{
groupLabel: 'Gestión' ,
menus: [
{
href: '/employees' ,
label: 'Empleados' ,
icon: Users ,
active: pathname . startsWith ( '/employees' ),
submenus: [
{
href: '/employees/list' ,
label: 'Lista' ,
active: pathname === '/employees/list' ,
},
{
href: '/employees/create' ,
label: 'Crear' ,
active: pathname === '/employees/create' ,
},
],
},
],
},
];
Collapsible menu item with submenus.
Location : components/sidebar/CollapseMenuButton.tsx:44
Props
Icon component from lucide-react
Display text for the menu item
Whether this menu or any submenu is active
isOpen
boolean | undefined
required
Sidebar expanded/collapsed state
Type Definition
type Submenu = {
href : string ;
label : string ;
active : boolean ;
};
interface CollapseMenuButtonProps {
icon : LucideIcon ;
label : string ;
active : boolean ;
submenus : Submenu [];
isOpen : boolean | undefined ;
}
Usage Example
import { CollapseMenuButton } from '@/components/sidebar/CollapseMenuButton' ;
import { Users } from 'lucide-react' ;
const submenus = [
{ href: '/employees/list' , label: 'Lista' , active: false },
{ href: '/employees/create' , label: 'Crear' , active: false },
];
< CollapseMenuButton
icon = { Users }
label = "Empleados"
active = { pathname . startsWith ( '/employees' ) }
submenus = { submenus }
isOpen = { isOpen }
/>
Behavior
Button to toggle sidebar open/closed state.
Location : components/sidebar/SidebarToggle.tsx
import { SidebarToggle } from '@/components/sidebar/SidebarToggle' ;
const [ isOpen , setIsOpen ] = useState ( true );
< SidebarToggle isOpen = { isOpen } setIsOpen = { setIsOpen } />
Mobile overlay menu using Sheet component.
Location : components/sidebar/SheetMenu.tsx
import { SheetMenu } from '@/components/sidebar/SheetMenu' ;
// Automatically shown on mobile viewports
< SheetMenu />
Menus are filtered based on user roles:
const { user } = useAuth ();
const userRoles = user ?. roles ?. map ( role => role . name ) || [];
// Menu list generator with role filtering
const menuList = useMemo (() => {
return getMenuList ( pathname , selectedCompany , userRoles );
}, [ pathname , selectedCompany , user ?. roles ]);
export function getMenuList (
pathname : string ,
company : Company | null ,
userRoles : string []
) {
const allMenus = [
{
groupLabel: 'Dashboard' ,
menus: [
{
href: '/dashboard' ,
label: 'Dashboard' ,
icon: LayoutDashboard ,
active: pathname === '/dashboard' ,
submenus: [],
allowedRoles: [ 'admin' , 'manager' , 'user' ],
},
],
},
{
groupLabel: 'Administración' ,
menus: [
{
href: '/users' ,
label: 'Usuarios' ,
icon: Users ,
active: pathname . startsWith ( '/users' ),
submenus: [],
allowedRoles: [ 'admin' ],
},
],
},
];
// Filter menus based on user roles
return allMenus
. map ( group => ({
... group ,
menus: group . menus . filter ( menu =>
menu . allowedRoles . some ( role => userRoles . includes ( role ))
),
}))
. filter ( group => group . menus . length > 0 );
}
Expanded State
Desktop - Expanded
Desktop - Collapsed
Mobile - Sheet
// Width: 256px (w-64)
< aside className = "w-64 fixed left-0 top-0 h-screen" >
< Menu isOpen = { true } />
</ aside >
Active State Detection
Highlight active menu items based on current route:
import { usePathname } from 'next/navigation' ;
const pathname = usePathname ();
const menus = [
{
href: '/employees' ,
label: 'Empleados' ,
// Exact match
active: pathname === '/employees' ,
submenus: [],
},
{
href: '/reports' ,
label: 'Reportes' ,
// Starts with (includes subroutes)
active: pathname . startsWith ( '/reports' ),
submenus: [
{
href: '/reports/maintenance' ,
label: 'Mantenimiento' ,
active: pathname === '/reports/maintenance' ,
},
],
},
];
Show labels when sidebar is collapsed:
< TooltipProvider disableHoverableContent >
< Tooltip delayDuration = { 100 } >
< TooltipTrigger asChild >
< Button variant = "ghost" >
< Icon size = { 18 } />
< p className = { cn (
isOpen === false
? "-translate-x-96 opacity-0"
: "translate-x-0 opacity-100"
) } >
{ label }
</ p >
</ Button >
</ TooltipTrigger >
{ isOpen === false && (
< TooltipContent side = "right" >
{ label }
</ TooltipContent >
) }
</ Tooltip >
</ TooltipProvider >
For long menu lists, use ScrollArea:
import { ScrollArea } from '@/components/ui/scroll-area' ;
< ScrollArea className = "[&>div>div[style]]:!block" >
< nav className = "mt-8 h-full w-full" >
< ul className = "flex flex-col items-start space-y-1 px-2" >
{ /* Menu items */ }
</ ul >
</ nav >
</ ScrollArea >
Group Labels
Organize menus with section headers:
{ menuList . map (({ groupLabel , menus }, index ) => (
< li key = { index } className = { cn ( "w-full" , groupLabel ? "pt-4" : "" ) } >
{ ( isOpen && groupLabel ) || isOpen === undefined ? (
< p className = "text-sm font-medium text-muted-foreground px-4 pb-2" >
{ groupLabel }
</ p >
) : ! isOpen && groupLabel ? (
< TooltipProvider >
< Tooltip delayDuration = { 100 } >
< TooltipTrigger className = "w-full" >
< Ellipsis className = "h-5 w-5" />
</ TooltipTrigger >
< TooltipContent side = "right" >
< p > { groupLabel } </ p >
</ TooltipContent >
</ Tooltip >
</ TooltipProvider >
) : (
< p className = "pb-2" ></ p >
) }
{ /* Render menus */ }
</ li >
))}
Responsive Behavior
Desktop
Persistent sidebar with toggle button
Smooth width transitions
Tooltips in collapsed state
Keyboard navigation
Mobile
Hidden by default
Opens as full-width Sheet overlay
Hamburger menu trigger in navbar
Swipe to close gesture
import { useState } from 'react' ;
import { Menu } from '@/components/sidebar/Menu' ;
import { SidebarToggle } from '@/components/sidebar/SidebarToggle' ;
import { cn } from '@/lib/utils' ;
export function Sidebar () {
const [ isOpen , setIsOpen ] = useState ( true );
return (
< aside
className = { cn (
"fixed left-0 top-0 z-40 h-screen transition-all duration-300" ,
"bg-background border-r" ,
isOpen ? "w-64" : "w-16"
) }
>
< div className = "relative h-full flex flex-col" >
{ /* Logo/Brand */ }
< div className = "h-16 flex items-center justify-between px-4 border-b" >
{ isOpen ? (
< h1 className = "text-xl font-bold" > SIGEAC </ h1 >
) : (
< div className = "w-8 h-8 bg-primary rounded" />
) }
</ div >
{ /* Navigation Menu */ }
< Menu isOpen = { isOpen } />
{ /* Toggle Button */ }
< div className = "absolute bottom-4 left-4" >
< SidebarToggle isOpen = { isOpen } setIsOpen = { setIsOpen } />
</ div >
</ div >
</ aside >
);
}
Best Practices
Memoize menu calculations
Provide keyboard navigation
Ensure all menu items are keyboard accessible with proper focus management.
Use ScrollArea for menus with many items to prevent layout issues.
Verify tooltips and dropdown menus work correctly when sidebar is collapsed.
Implement role-based access
Filter menu items based on user permissions to show only relevant options.
Highlight the current route and expand relevant submenus automatically.
Navigation - Navbar and breadcrumb components
Tables - Pages accessed from sidebar navigation