componente · @solu30/ui-kit/navigation
SidebarNavigation
Navegación lateral completa, con grupos, badges, modo colapsado y cajón para pantallas chicas.
Seguir leyendo — 2 párrafos más
SidebarNavigation provides a responsive sidebar with collapsible mode, mobile drawer overlay, grouped items, badges, and keyboard navigation.
Theming via CSS variables:
- --ui-accent: active item highlight color
- --ui-bg-base: page background
- --ui-bg-raised: sidebar background
- --ui-bg-overlay: mobile overlay background
- --ui-content-primary: primary text color
- --ui-content-secondary: muted text color
- --ui-border: border color
9 sitios de import en 4 de 15 verticales — un archivo que la importa cuenta una vez, monte una instancia o veinte.
- carlosmartinpavon
- dietaly
- elliot
- zentrya
La pieza, con lugar de pantalla
Cargando el espécimen…
export function SidebarNavigationMatrix() {
const [activoDietaly, setActivoDietaly] = useState('datos')
const [activoZentrya, setActivoZentrya] = useState('comms-whatsapp')
/** `NAV_ITEMS` de `EmpresaClient.tsx:51-55` — la forma plana, sin grupos. */
const navItems: SidebarNavItem[] = MODULOS_EMPRESA.map((m) => ({
id: m.id,
label: m.label,
icon: m.icon,
}))
/** `CHANNEL_LEVEL_CHILD_GROUPS` de `admin-nav-data.ts:116-155`. */
const canales: SidebarNavChildGroup[] = [
{
title: 'CANALES',
items: [
{ id: 'comms-whatsapp', label: 'WhatsApp', icon: IconWhatsApp },
{ id: 'comms-webchat', label: 'Chat web', icon: IconGlobe },
{ id: 'comms-mail', label: 'Mail', icon: IconMail, disabled: true, badge: 'Pronto' },
{ id: 'comms-chat-interno', label: 'Chat interno', icon: IconChat, disabled: true, badge: 'Pronto' },
{ id: 'comms-instagram', label: 'Instagram', icon: IconInstagram, disabled: true, badge: 'Pronto' },
{ id: 'comms-meetings', label: 'Meetings', icon: IconVideo, disabled: true, badge: 'Pronto' },
],
},
]
/** `mainItems` de `admin-nav-data.ts:226-280`. */
const zentryaGroups: SidebarNavGroup[] = [
{
items: [
{ id: 'dashboard', label: 'Dashboard', icon: IconHome },
{ id: 'comms', label: 'Mensajería', icon: IconComms, childGroups: canales },
{ id: 'growth', label: 'Growth', icon: IconTarget },
{ id: 'blog', label: 'Blog', icon: IconBook },
{ id: 'seo', label: 'SEO', icon: IconRocket },
{
id: 'social',
label: 'Redes',
icon: IconInstagram,
childGroups: [
{
title: '',
items: [
{ id: 'social-publisher', label: 'Publicador', icon: IconRocket },
{ id: 'social-calendar', label: 'Calendario', icon: IconBook },
{ id: 'social-inbox', label: 'Inbox', icon: IconChat },
{ id: 'social-ai', label: 'Community AI', icon: IconLightning },
],
},
],
},
{ id: 'public-site', label: 'Sitio público', icon: IconExternalLink },
],
},
]
return (
<Stage label="SidebarNavigation · boxed + items planos (Dietaly) vs plain + childGroups (Zentrya)" tall wide>
<div className="flex w-full flex-wrap items-start justify-center gap-6 px-1 py-1">
<div className="flex flex-col gap-1.5">
<span className="text-[10px] uppercase tracking-wide text-content/50">
boxed · items planos — y su reemplazo móvil, que es otro componente
</span>
<BrowserFrame productName="dietaly" className="w-[320px]">
<div className="flex h-[360px] flex-col gap-3 overflow-hidden p-3">
{/* La tira de `Tabs` que el vertical monta con `lg:hidden`. Acá
va SIEMPRE visible, arriba, para que se vea que el sidebar y
la tira son el mismo menú en dos formas. */}
<div className="-mx-1 px-1">
<Tabs items={TABS_ITEMS} active={activoDietaly} onChange={setActivoDietaly} variant="underline" size="sm" />
</div>
<div className="min-h-0 flex-1 overflow-hidden">
<SidebarNavigation
items={navItems}
activeItemId={activoDietaly}
onItemClick={(item) => setActivoDietaly(item.id)}
className="h-full"
/>
</div>
</div>
</BrowserFrame>
</div>
<div className="flex flex-col gap-1.5">
<span className="text-[10px] uppercase tracking-wide text-content/50">
plain · childGroups — «Mensajería» y «Redes» se despliegan; badge acepta string
</span>
<BrowserFrame productName="zentrya" className="w-[300px]">
<div className="h-[360px] overflow-hidden">
<SidebarNavigation
groups={zentryaGroups}
activeItemId={activoZentrya}
onItemClick={(item) => setActivoZentrya(item.id)}
appearance="plain"
header={{ title: 'Zentrya', subtitle: 'Empresa activa' }}
className="h-full"
/>
</div>
</BrowserFrame>
</div>
</div>
</Stage>
)
}De specimens/navigation.tsx · SidebarNavigationMatrix: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Cómo se usa
import { SidebarNavigation } from '@solu30/ui-kit/navigation'Props
| Prop | Requerida | Tipo | Qué hace |
|---|---|---|---|
| groups | opcional | SidebarNavGroup[] | Navigation groups containing items (primary API). Provide either `groups` or `items`, not both. |
| items | opcional | SidebarNavItem[] | Flat items shorthand — automatically wrapped in a single unlabeled group. Provide either `items` or `groups`, not both. |
| activeItemId | opcional | string | ID of the currently active item |
| collapsed | opcional | boolean | Whether the sidebar is collapsed (icons only) — controlled mode |
| defaultCollapsed | opcional | boolean | Initial collapsed state for uncontrolled mode (ignored when `collapsed` is provided) |
| onCollapsedChange | opcional | (collapsed: boolean) => void | Callback when collapsed state changes |
| header | opcional | ReactNode | { title: string; subtitle?: string; } | Header slot (logo/brand area). Accepts either a ReactNode or a plain object `{ title, subtitle? }` for a default header layout. |
| footer | opcional | ReactNode | Footer slot (user menu area) |
| onItemClick | opcional | (item: SidebarNavItem) => void | Callback when a nav item is clicked |
| className | opcional | string | Additional class names |
| mobileOpen | opcional | boolean | Whether the mobile drawer is open |
| onMobileOpenChange | opcional | (open: boolean) => void | Callback when mobile drawer state changes |
| appearance | opcional | "boxed" | "plain" | Visual style variant for items. (ADR-079) - "boxed" (default): current behavior — icon in a rounded box bg, padded items. All existing consumers use this implicitly. Zero breaking change. - "plain": compact list style — icon rendered loose (no background box), reduced vertical padding, items tighter together. Active state is subtle (text-primary + very light bg tint). Enables `childGroups` section headers. ADR-003: both variants use only cmpPreset tokens. |
Ejemplo del paquete
<SidebarNavigation
groups={[
{
label: 'Main',
items: [
{ id: 'dashboard', label: 'Dashboard', href: '/', icon: HomeIcon },
{ id: 'orders', label: 'Orders', href: '/orders', icon: BoxIcon, badge: 5 },
],
},
]}
activeItemId="dashboard"
header={<Logo />}
footer={<UserMenu />}
/>Del @example del JSDoc de @solu30/ui-kit, sin editar: son 65 de los 541 símbolos. La sangría y los renglones en blanco son los del paquete.