Skip to main content

Overview

SIGEAC’s navigation components provide a complete solution for app-wide navigation including responsive headers, user profile menus, company selection, and theme toggles. Main header component with responsive layout. Location: components/layout/Navbar.tsx:14

Props

title
string
required
Page title displayed in the navbar

Usage Example

import { Navbar } from '@/components/layout/Navbar';

export function Layout({ children }) {
  return (
    <div>
      <Navbar title="Dashboard" />
      <main>{children}</main>
    </div>
  );
}

Features

  • Sticky positioning: Stays at top during scroll
  • Backdrop blur: Modern glass effect
  • Mobile menu: Sheet menu trigger for small screens
  • Company selector: Dropdown for multi-company users
  • Theme toggle: Light/dark mode switcher
  • User menu: Profile and logout options
  • Responsive: Adapts layout for mobile/desktop

Structure

<header className="sticky top-0 z-10 w-full bg-background/95 shadow backdrop-blur">
  <div className="mx-4 sm:mx-8 flex h-14 items-center justify-between">
    {/* Left: Menu + Title */}
    <div className="flex items-center gap-4">
      <SheetMenu />  {/* Mobile only */}
      <h1 className="text-xs sm:text-sm xl:text-base font-bold">
        {title}
      </h1>
    </div>

    {/* Center: Company Select (Desktop) */}
    <div className="hidden xl:flex items-center">
      <CompanySelect />
    </div>

    {/* Center: Company Select (Mobile Popover) */}
    <div className="flex xl:hidden items-center">
      <Popover>
        <PopoverTrigger asChild>
          <button className="flex items-center justify-center w-10 h-10">
            <ChevronDown className="w-5 h-5" />
          </button>
        </PopoverTrigger>
        <PopoverContent>
          <CompanySelect />
        </PopoverContent>
      </Popover>
    </div>

    {/* Right: Theme + User */}
    <div className="flex items-center gap-2">
      <ThemeToggler />
      <UserNav />
    </div>
  </div>
</header>

UserNav

User profile dropdown menu. Location: components/layout/UserNav.tsx
import { UserNav } from '@/components/layout/UserNav';

<UserNav />

Features

  • User avatar with initials
  • Display name and email
  • Profile link
  • Settings link
  • Logout action

Implementation

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useAuth } from '@/contexts/AuthContext';

export function UserNav() {
  const { user, logout } = useAuth();

  const initials = user
    ? `${user.first_name?.[0] || ''}${user.last_name?.[0] || ''}`
    : 'U';

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" className="relative h-10 w-10 rounded-full">
          <Avatar className="h-10 w-10">
            <AvatarImage src={user?.avatar} alt={user?.username} />
            <AvatarFallback>{initials}</AvatarFallback>
          </Avatar>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56" align="end">
        <DropdownMenuLabel>
          <div className="flex flex-col space-y-1">
            <p className="text-sm font-medium">
              {user?.first_name} {user?.last_name}
            </p>
            <p className="text-xs text-muted-foreground">
              {user?.email}
            </p>
          </div>
        </DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem asChild>
          <Link href="/profile">Perfil</Link>
        </DropdownMenuItem>
        <DropdownMenuItem asChild>
          <Link href="/settings">Configuración</Link>
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem onClick={logout}>
          Cerrar Sesión
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

ThemeToggler

Light/dark mode toggle button. Location: components/layout/ThemeToggler.tsx
import { ThemeToggler } from '@/components/layout/ThemeToggler';

<ThemeToggler />

Implementation

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';

export function ThemeToggler() {
  const { theme, setTheme } = useTheme();

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      aria-label="Toggle theme"
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  );
}

CompanySelect

Multi-company selector with search. Location: components/selects/CompanySelect.tsx
import CompanySelect from '@/components/selects/CompanySelect';

<CompanySelect />

Features

  • Search companies by name
  • Selected company indicator
  • Stores selection in global state
  • Updates context throughout app

Implementation

import { useState } from 'react';
import { Check, ChevronsUpDown } from 'lucide-react';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Button } from '@/components/ui/button';
import { useCompanyStore } from '@/stores/CompanyStore';
import { useGetCompanies } from '@/hooks/sistema/useGetCompanies';

export default function CompanySelect() {
  const [open, setOpen] = useState(false);
  const { selectedCompany, setSelectedCompany } = useCompanyStore();
  const { data: companies, isLoading } = useGetCompanies();

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className="w-[250px] justify-between"
        >
          {selectedCompany ? selectedCompany.name : "Seleccione empresa..."}
          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[250px] p-0">
        <Command>
          <CommandInput placeholder="Buscar empresa..." />
          <CommandList>
            <CommandEmpty>No se encontraron empresas.</CommandEmpty>
            <CommandGroup>
              {companies?.map((company) => (
                <CommandItem
                  key={company.id}
                  value={company.name}
                  onSelect={() => {
                    setSelectedCompany(company);
                    setOpen(false);
                  }}
                >
                  <Check
                    className={cn(
                      "mr-2 h-4 w-4",
                      selectedCompany?.id === company.id
                        ? "opacity-100"
                        : "opacity-0"
                    )}
                  />
                  {company.name}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

SheetMenu

Mobile slide-out menu. Location: components/sidebar/SheetMenu.tsx
import { SheetMenu } from '@/components/sidebar/SheetMenu';

<SheetMenu />  {/* Shows only on mobile */}

Implementation

import { Menu as MenuIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
  Sheet,
  SheetContent,
  SheetTrigger,
} from '@/components/ui/sheet';
import { Menu } from '@/components/sidebar/Menu';

export function SheetMenu() {
  return (
    <Sheet>
      <SheetTrigger asChild className="lg:hidden">
        <Button variant="ghost" size="icon">
          <MenuIcon className="h-5 w-5" />
        </Button>
      </SheetTrigger>
      <SheetContent side="left" className="w-64 p-0">
        <Menu isOpen={true} />
      </SheetContent>
    </Sheet>
  );
}

Layout Components

ContentLayout

Standard page layout with navbar and content area.
import { ContentLayout } from '@/components/layout/ContentLayout';

export default function Page() {
  return (
    <ContentLayout title="Page Title">
      {/* Page content */}
    </ContentLayout>
  );
}

ProtectedLayout

Authentication-wrapped layout.
import { ProtectedLayout } from '@/components/layout/ProtectedLayout';

export default function DashboardPage() {
  return (
    <ProtectedLayout>
      <ContentLayout title="Dashboard">
        {/* Protected content */}
      </ContentLayout>
    </ProtectedLayout>
  );
}
Show navigation hierarchy:
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { ChevronRight } from 'lucide-react';

export function PageBreadcrumbs() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/">Inicio</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator>
          <ChevronRight className="h-4 w-4" />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbLink href="/employees">Empleados</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator>
          <ChevronRight className="h-4 w-4" />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbPage>Lista</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  );
}

Complete Layout Example

Full application layout combining all navigation components:
import { Sidebar } from '@/components/layout/Sidebar';
import { Navbar } from '@/components/layout/Navbar';
import { useState } from 'react';
import { cn } from '@/lib/utils';

export function AppLayout({ children, title }) {
  const [sidebarOpen, setSidebarOpen] = useState(true);

  return (
    <div className="flex h-screen overflow-hidden">
      {/* Sidebar */}
      <Sidebar isOpen={sidebarOpen} setIsOpen={setSidebarOpen} />

      {/* Main Content */}
      <div
        className={cn(
          "flex-1 flex flex-col transition-all",
          sidebarOpen ? "lg:ml-64" : "lg:ml-16"
        )}
      >
        {/* Navbar */}
        <Navbar title={title} />

        {/* Page Content */}
        <main className="flex-1 overflow-y-auto p-4 lg:p-8">
          {children}
        </main>

        {/* Footer */}
        <footer className="border-t p-4 text-center text-sm text-muted-foreground">
          © 2024 SIGEAC. Todos los derechos reservados.
        </footer>
      </div>
    </div>
  );
}

Responsive Patterns

Mobile Navigation

{/* Mobile: Sheet menu trigger */}
<div className="lg:hidden">
  <SheetMenu />
</div>

{/* Desktop: Persistent sidebar */}
<div className="hidden lg:block">
  <Sidebar />
</div>

Adaptive Title Sizing

<h1 className="text-xs sm:text-sm xl:text-base font-bold">
  {title}
</h1>

Conditional Company Select

{/* Desktop: Always visible */}
<div className="hidden xl:flex">
  <CompanySelect />
</div>

{/* Mobile: In popover */}
<div className="flex xl:hidden">
  <Popover>
    <PopoverTrigger><ChevronDown /></PopoverTrigger>
    <PopoverContent>
      <CompanySelect />
    </PopoverContent>
  </Popover>
</div>

Best Practices

Use the same navbar across all pages for familiar navigation experience.
Highlight active menu items and provide breadcrumbs for deep navigation.
Test navigation on small screens and ensure touch targets are adequate.
Store theme selection and sidebar state in localStorage.
Show skeleton loaders while company list or user data is loading.
Add hotkeys for common actions like theme toggle or company switch.
  • Sidebar - Main navigation menu
  • Forms - User profile and settings forms

Build docs developers (and LLMs) love