Skip to content

Sidebar

Componente de navegación lateral para aplicaciones empresariales. Recibe el menú desde tu API o base de datos, filtra ítems por permisos (RBAC), soporta hasta 3 niveles de profundidad, temas personalizables e integración con cualquier router.

Integración

El Sidebar no incluye router. Tu aplicación conecta activePath y onNavigate con React Router u otro sistema. Ver Integración con routing.

Importación

tsx
import { Sidebar, sidebarThemes } from 'glubox';
import type {
  SidebarProps,
  MenuConfig,
  MenuItem,
  MenuSubItem,
  SidebarTheme,
  IconResolver,
  SidebarBrandComponent,
} from 'glubox';
import 'glubox/style.css';

Props

PropTipoDefaultDescripción
menuMenuConfigEstructura del menú (API/BD). Requerido.
userPermissionsPermission[]Permisos del usuario autenticado. Requerido.
activePathstringRuta activa actual (desde el router). Resalta ítem y ancestros.
onNavigate(path: string) => voidCallback al hacer clic en un ítem con path.
collapsedbooleanfalseSidebar en modo rail (solo iconos).
widthnumber | string240pxAncho expandido; en colapsado suele usarse 64.
onCollapsedChange(collapsed: boolean) => voidAl pulsar el botón contraer/expandir.
showCollapseButtonbooleantrue si hay onCollapsedChangeMuestra u oculta el botón de colapsar.
theme'dark' | 'light' | SidebarTheme'dark'Preset o tokens de color personalizados.
renderIconIconResolverResuelve iconos del menú por nombre (Lucide, Iconify, etc.).
brandSidebarBrandComponentComponente de logo / nombre de empresa.
collapseOthersOnSelectbooleanfalseAcordeón al expandir/contraer módulos de nivel 1.
collapseOnNavigatebooleanfalseAl navegar, sincroniza y puede cerrar módulos abiertos.

Comportamiento de expansión

EscenariocollapseOthersOnSelectcollapseOnNavigate
Clic en chevron de otro móduloCierra los demás módulos
Clic en Ajustes / Ayuda (sin hijos)No afectaNo cierra menús abiertos
Navegar a /facturacion/facturas/emitirSolo abre ancestros; no cierra otros
Navegar con ambas props en trueDeja abiertos solo los ancestros de la ruta

Los ítems de bottom (Ajustes, Ayuda) nunca provocan cierre de módulos aunque collapseOnNavigate esté activo.

Arquitectura

mermaid
flowchart TB
  API["API / BD → MenuConfig"]
  PERM["Permisos del usuario"]
  API --> FILTER["filterVisibleMenu()"]
  PERM --> FILTER
  FILTER --> SIDEBAR["Sidebar"]
  ROUTER["Router (app consumidora)"]
  SIDEBAR -->|"onNavigate(path)"| ROUTER
  ROUTER -->|"activePath = pathname"| SIDEBAR
  1. El backend entrega MenuConfig con path, permissions, children, etc.
  2. El Sidebar filtra ítems según userPermissions (regla OR).
  3. La app conecta clics con el router y devuelve la ruta activa.

Modelo de menú

Contrato completo en Esquema del menú (API).

Niveles

NivelRolpathComportamiento
1 — MóduloSección principalOpcional (inicio del módulo)Icono, posición top/bottom, puede tener hijos
2 — OpciónAgrupadorOpcionalSolo expande; navega si tiene path
3 — AcciónPantalla finalRequeridoNavega al hacer clic

Módulo con página de inicio

Si un módulo tiene path y children, el clic en el nombre navega y el chevron expande/contrae por separado:

json
{
  "id": "facturacion",
  "label": "Facturación",
  "icon": "receipt",
  "path": "/facturacion",
  "permissions": ["facturacion:read"],
  "children": [ "..."]
}

Posiciones

  • position: "top" (default) — lista principal, scrollable.
  • position: "bottom" — fijado al pie (Ajustes, Ayuda, perfil).

Ejemplo simplificado (Facturación SRI)

json
{
  "items": [
    {
      "id": "facturacion",
      "label": "Facturación",
      "icon": "receipt",
      "path": "/facturacion",
      "permissions": ["facturacion:read"],
      "children": [
        {
          "id": "facturacion-facturas",
          "label": "Facturas",
          "permissions": ["facturacion:facturas:read"],
          "children": [
            {
              "id": "facturacion-facturas-emitir",
              "label": "Emitir",
              "path": "/facturacion/facturas/emitir",
              "permissions": ["facturacion:facturas:emitir"]
            }
          ]
        }
      ]
    },
    {
      "id": "ajustes",
      "label": "Ajustes",
      "icon": "settings",
      "path": "/ajustes",
      "permissions": ["ajustes:read"],
      "position": "bottom"
    }
  ]
}

RBAC (permisos)

Reglas aplicadas por filterVisibleMenu:

CasoVisible
Ítem sin permissionsSí, para todos
Ítem con permissions: ["a", "b"]Sí si el usuario tiene al menos uno (OR)
Opción/módulo con hijosSolo si queda al menos un hijo visible
Hijo sin permisoSe oculta; si todos los hijos se ocultan, el padre también
tsx
const visibleMenu = filterVisibleMenu(menu, userPermissions);
// El Sidebar aplica esto internamente; puedes usar la misma función
// en guards de ruta para coherencia UI ↔ URL.

URL directa

Ocultar un ítem en el Sidebar no impide escribir la URL manualmente. Valida permisos también en el router o en el backend. Ver Routing.

Iconos

Iconos del menú (renderIcon)

El paquete no incluye Lucide ni otras librerías. Los nombres de icono vienen de tu API ("receipt", "settings", etc.) y los resuelves con renderIcon:

tsx
import { Circle, Receipt, Settings, type LucideIcon } from 'lucide-react';
import type { IconResolver } from 'glubox';

const registry: Record<string, LucideIcon> = {
  receipt: Receipt,
  settings: Settings,
};

export const renderMenuIcon: IconResolver = (name, className) => {
  const Icon = registry[name] ?? Circle;
  return <Icon className={className} aria-hidden />;
};

El Sidebar aplica la clase sidebar__icon; el tamaño lo controla el CSS (incluido modo colapsado).

Iconos integrados (UI)

Estos nombres los resuelve el propio Sidebar; no hace falta registrarlos en renderIcon:

NombreUso
chevron-downExpandir submenús
panel-left-closeContraer sidebar
panel-left-openExpandir sidebar

Si no pasas renderIcon y el icono del menú no es built-in, se muestra un fallback con la inicial del nombre.

Marca (brand)

Componente React que recibe { collapsed: boolean }:

tsx
import type { SidebarBrandProps } from 'glubox';

export function AppBrand({ collapsed }: SidebarBrandProps) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <img src="/logo.svg" alt="" width={32} height={32} />
      {!collapsed && <span>Mi Empresa</span>}
    </div>
  );
}

<Sidebar brand={AppBrand} {...props} />

Temas

Presets

tsx
<Sidebar theme="dark" {...props} />
<Sidebar theme="light" {...props} />

Tema personalizado

tsx
import type { SidebarTheme } from 'glubox';

const corporate: SidebarTheme = {
  background: '#0f172a',
  text: '#f8fafc',
  icon: '#94a3b8',
  hoverBackground: 'rgba(255,255,255,0.06)',
  activeBackground: 'rgba(255,255,255,0.06)',
  activeText: '#38bdf8',
  activeIcon: '#38bdf8',
  mutedText: '#64748b',
  treeLine: 'rgba(255,255,255,0.1)',
  railActive: '#38bdf8',
};

<Sidebar theme={corporate} {...props} />

Tokens disponibles en SidebarTheme:

TokenDescripción
backgroundFondo del sidebar
textTexto principal
iconColor de iconos
hoverBackgroundFondo al hover
activeText / activeIconÍtem activo
mutedTextOpciones y acciones secundarias
treeLineLíneas del árbol de submenús
railActiveBarra lateral del ítem activo
iconFallbackBackgroundFondo del fallback de icono

Presets exportados: sidebarThemes con light, dark, modern-light, modern-dark, enterprise-light, enterprise-dark.

Temas globales CSS: Guía de temas.

Jerarquía visual

NivelEstilo
MóduloSemibold, icono; rail si hay descendiente activo
OpciónTexto muted, línea guía; sin fondo al abrir
Acción activaRail + color accent; única hoja resaltada

En modo colapsado (collapsed={true}):

  • Solo se muestran iconos de módulos e ítems de bottom.
  • Los submenús se ocultan.
  • Los iconos se centran en el rail (~22px).

Uso completo con router

tsx
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Sidebar } from 'glubox';
import 'glubox/style.css';

export function AppLayout({ menu, userPermissions, brand, renderIcon }) {
  const [collapsed, setCollapsed] = useState(false);
  const navigate = useNavigate();
  const { pathname } = useLocation();

  return (
    <div style={{ display: 'flex', minHeight: '100vh' }}>
      <Sidebar
        menu={menu}
        userPermissions={userPermissions}
        activePath={pathname}
        onNavigate={navigate}
        collapsed={collapsed}
        width={collapsed ? 64 : 240}
        onCollapsedChange={setCollapsed}
        collapseOthersOnSelect
        renderIcon={renderIcon}
        brand={brand}
        theme="dark"
      />
      {/* <Outlet /> o children */}
    </div>
  );
}

Guía detallada: Integración con routing.

TypeScript — tipos exportados

tsx
import type {
  SidebarProps,
  MenuConfig,
  MenuItem,
  MenuSubItem,
  MenuItemPosition,
  Permission,
  IconResolver,
  IconName,
  SidebarBrandProps,
  SidebarBrandComponent,
  SidebarTheme,
  SidebarThemePreset,
  SidebarThemeInput,
} from 'glubox';

Checklist de implementación

  1. Instalar glubox e importar glubox/style.css.
  2. Definir endpoint o JSON con MenuConfig (ver menu-api).
  3. Obtener userPermissions del login / token / API de roles.
  4. Implementar renderIcon con tu librería de iconos.
  5. Conectar activePath y onNavigate al router.
  6. (Opcional) Componente brand para logo.
  7. (Recomendado) Guard de permisos en rutas para URLs directas.
  8. Probar modo colapsado, acordeón y ítems bottom.

Referencia en el repositorio

RecursoUbicación
Menú demosrc/components/Sidebar/data/mockMenu.json
Permisos demosrc/components/Sidebar/mock/mockUserPermissions.ts
App demo con routingsrc/demo/

MIT License