NavigationMenu
Import
import { NavigationMenu } from '@pzeta/vue-components'
import type { MenuStructure, MenuGroup } from '@pzeta/vue-components'
import type { MenuItem } from '@pzeta/vue-components'
Struttura dati
Il NavigationMenu riceve una struttura gerarchica che rappresenta uno o più moduli MFE:
interface MenuStructure {
manifest: MFEManifest // { name, version, icon?, description?, routes? }
menuGroups: MenuGroup[]
}
interface MenuGroup {
category: string // nome della categoria/gruppo
icon?: string
items: MenuItem[] // voci del gruppo (usano MenuItem standard)
}
manifest.description(opzionale) viene mostrato come sottotitolo sotto il nome del modulo, solo in stato esteso (dal 1.7.7).
Esempio Base
Props
| Prop | Tipo | Default | Descrizione |
|---|---|---|---|
menuStructure | MenuStructure[] | — (required) | Struttura completa del menu da renderizzare |
collapsed | boolean | false | Stato collapsed della sidebar (rail): nasconde label e badge, mostra solo icone |
activeKey | string | undefined | Selezione controllata disaccoppiata dal router (v-model:active-key). Se omessa, l'active deriva dalla route |
showCollapseToggle | boolean | false | Mostra il toggle interno di compressione/espansione del rail in testa al menu |
collapsible | boolean | false | Abilita l'accordion su header di modulo e label di categoria (dal 1.7.6) |
expandedModules | string[] | undefined | Moduli espansi controllati (v-model:expanded-modules) (dal 1.7.6) |
expandedCategories | string[] | undefined | Categorie espanse controllate (v-model:expanded-categories); chiave `${modulo}::${categoria}` (dal 1.7.6) |
forceExpanded | boolean | false | Forza tutto espanso ignorando il collasso: da abbinare alla ricerca esterna (dal 1.7.6) |
autoExpandActive | boolean | true | Tiene sempre aperti modulo e categoria che contengono la voce attiva (dal 1.7.7) |
persistKey | string | undefined | Persiste lo stato di collasso su localStorage sotto questa chiave (solo in modalità non controllata) (dal 1.7.7) |
highlightQuery | string | undefined | Evidenzia nel label la porzione che combacia con la query (highlight ricerca) (dal 1.7.7) |
scrollActiveIntoView | boolean | false | Scorre la voce attiva nel viewport al mount e a ogni cambio di selezione/route (dal 1.7.7) |
defaultExpand | 'all' | 'modules' | 'none' | 'all' | Stato di espansione iniziale (non controllato): tutto aperto / solo moduli aperti / tutto chiuso (dal 1.7.8) |
variant | 'default' | 'glass' | 'gradient' | 'default' | Preset visivo dello sfondo: glass (superficie translucida con backdrop blur, segue il tema) o gradient (gradiente primary scuro). Ignorato se background è valorizzata (dal 1.7.12) |
background | string | undefined | Sfondo custom: classi Tailwind arbitrarie applicate alla <nav> (gradiente, immagine, blur). Ha priorità sul preset variant (dal 1.7.12) |
appearance | 'auto' | 'on-dark' | 'on-light' | 'auto' | Palette delle voci rispetto allo sfondo: auto segue il tema, on-dark testi chiari e fill translucidi bianchi, on-light forza la palette chiara. Il preset gradient attiva on-dark automaticamente (dal 1.7.12) |
navClass | string | undefined | Classe CSS aggiuntiva per l'elemento <nav> |
Emits
| Evento | Payload | Descrizione |
|---|---|---|
navigate | path: string | Emesso al click su un elemento; passa il path della route |
update:activeKey | key: string | Aggiornamento della chiave attiva (v-model:active-key) |
select | item: MenuItem | Voce selezionata (utile per item command-only senza route) |
update:collapsed | value: boolean | Aggiornamento dello stato rail dal toggle interno (v-model:collapsed) |
update:expandedModules | value: string[] | Aggiornamento dei moduli espansi (v-model:expanded-modules) (dal 1.7.6) |
update:expandedCategories | value: string[] | Aggiornamento delle categorie espanse (v-model:expanded-categories) (dal 1.7.6) |
Slot
| Slot | Scope | Descrizione |
|---|---|---|
menu-item | { item: { path, label, icon?, order?, badge? }; active: boolean } | Template personalizzato per ogni voce del menu. active è calcolato automaticamente da Vue Router |
Metodi esposti (ref) (dal 1.7.8)
Accessibili via template ref (NavigationMenuExpose):
| Metodo | Descrizione |
|---|---|
expandAll() | Espande tutti i moduli e le categorie collassabili |
collapseAll() | Collassa tutti i moduli e le categorie collassabili (il ramo attivo resta aperto se autoExpandActive) |
<script setup lang="ts">
import { ref } from 'vue'
import { NavigationMenu } from '@pzeta/vue-components'
const nav = ref<InstanceType<typeof NavigationMenu> | null>(null)
</script>
<template>
<button @click="nav?.expandAll()">Espandi tutto</button>
<button @click="nav?.collapseAll()">Comprimi tutto</button>
<NavigationMenu ref="nav" :menu-structure="menu" collapsible default-expand="modules" />
</template>
Esempi
Modalità collapsed (sidebar compressa)
Con template voce personalizzato
Menu multi-modulo
<script setup lang="ts">
import type { MenuStructure } from '@pzeta/vue-components'
const menuStructure: MenuStructure[] = [
{
manifest: { name: 'CRM', version: '1.0.0', icon: 'users' },
menuGroups: [
{
category: 'Clienti',
items: [
{ label: 'Anagrafiche', icon: 'address-book', to: '/crm/anagrafiche' },
{ label: 'Opportunità', icon: 'chart-line', to: '/crm/opportunita' },
],
},
],
},
{
manifest: { name: 'Fatturazione', version: '1.0.0', icon: 'file-invoice' },
menuGroups: [
{
category: 'Documenti',
items: [
{ label: 'Fatture', icon: 'file-invoice', to: '/fatturazione/fatture' },
{ label: 'Note credito', icon: 'file-minus', to: '/fatturazione/note-credito' },
],
},
],
},
]
</script>
Accordion collassabile con ricerca (dal 1.7.6)
Con collapsible, header di modulo e categoria diventano toggle a fisarmonica. La ricerca resta esterna (filtra menuStructure): durante una ricerca attiva forceExpanded riespande i rami filtrati e highlightQuery evidenzia il testo trovato.
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { MenuStructure } from '@pzeta/vue-components'
const search = ref('')
const menuStructure: MenuStructure[] = [
{
manifest: { name: 'CRM', version: '2.0.0', icon: 'users', description: 'Gestione clienti' },
menuGroups: [
{ category: 'Clienti', items: [
{ label: 'Lista Clienti', icon: 'list', to: '/crm/clienti', itemKey: 'crm-clients' },
{ label: 'Nuovo Cliente', icon: 'plus', to: '/crm/nuovo', itemKey: 'crm-new' },
] },
{ category: 'Opportunità', items: [
{ label: 'Pipeline', icon: 'filter', to: '/crm/pipeline', itemKey: 'crm-pipeline' },
] },
],
},
]
// Filtro esterno: la libreria non conosce la ricerca, la esegue il consumer.
const filtered = computed<MenuStructure[]>(() => {
const q = search.value.trim().toLowerCase()
if (!q) return menuStructure
return menuStructure
.map((m) => ({
...m,
menuGroups: m.menuGroups
.map((g) => ({ ...g, items: g.items.filter((i) => i.label?.toLowerCase().includes(q)) }))
.filter((g) => g.items.length > 0),
}))
.filter((m) => m.menuGroups.length > 0)
})
</script>
<template>
<div class="w-64">
<input v-model="search" placeholder="Cerca voce..." class="w-full mb-2 px-3 py-2 rounded-lg" />
<NavigationMenu
:menu-structure="filtered"
collapsible
persist-key="crm-nav"
scroll-active-into-view
:force-expanded="!!search.trim()"
:highlight-query="search"
/>
</div>
</template>
autoExpandActive(defaulttrue): il modulo e la categoria che contengono la voce attiva restano sempre aperti, così la pagina corrente non è mai nascosta.persistKey: lo stato di collasso viene salvato inlocalStoragee ripristinato al reload (solo modalità non controllata).- Per il controllo esplicito usa
v-model:expanded-modules/v-model:expanded-categories.
Sfondo customizzabile (dal 1.7.12)
Il menu è trasparente di default (eredita lo sfondo del contenitore). Con variant si applica un preset pronto, con background classi Tailwind arbitrarie; appearance commuta la palette delle voci (testi, hover, active) per restare leggibile su qualsiasi sfondo.
Immagine sotto il glass: il backdrop-filter sfoca ciò che sta dietro la nav, quindi l'immagine va sul contenitore (o sulla contentClass della Sidebar), non sulla nav stessa:
Il frost del preset
glassè calibrato leggero (blur 6px, superficie al 50%) per lasciare riconoscibile l'immagine dietro. Per un frost più coprente su immagini molto contrastate usa la propbackground(es.bg-white/70 backdrop-blur-lg).
Comportamento
- L'indicatore
activeviene calcolato confrontandoroute.pathcon il campotodi ogni voce. - Una voce è attiva se
route.path === pathoppureroute.path.startsWith(path + '/'). - In modalità
collapsed, l'intestazione del modulo, le categorie e i label delle voci vengono nascosti; rimangono solo le icone. Iltitledel link viene impostato allabelper accessibilità. - I badge supportano sia valori statici (stringa) sia funzioni reattive
() => string | number. - Con
collapsible, moduli e categorie sono collassabili in modo indipendente; l'apertura è animata (grid-template-rows) e rispettaprefers-reduced-motion. In modalitàcollapsed(rail) l'accordion è disattivato e tutte le icone restano visibili. - La voce attiva mostra una barra accent verticale sul bordo sinistro oltre al fill; gli hover sono translucidi, quindi uniformi su sfondi chiari, scuri e immagini (dal 1.7.12).
- Se
backgroundè valorizzata il presetvariantviene ignorato; conappearance: 'auto'il presetgradientcommuta da solo la palette aon-dark(dal 1.7.12).
Accessibilità
- Usa
<nav>come contenitore principale. - Le voci sono
<RouterLink>con classhs-nav-menu__link--active/hs-nav-menu__link--inactiveearia-current="page"sulla voce attiva. - In modalità collapsed le icone hanno il
titlecorrispondente al label. - Con
collapsible, gli header di modulo/categoria sono<button>conaria-expandedearia-controls; i pannelli chiusi sono resiinert(fuori da tab-order e albero accessibile) e i toggle hannofocus-visible.
Theming (CSS variables) (dal 1.7.12)
Tutti i colori del menu passano dalle CSS variables --hs-nav-*, definite su .hs-nav-menu e ridefinite dal tema scuro e dai modifier di appearance. Per personalizzazioni avanzate (oltre variant/appearance) si possono sovrascrivere via CSS dell'app:
| Variable | Ruolo |
|---|---|
--hs-nav-heading | Testo header modulo |
--hs-nav-faint | Sottotitolo modulo, chevron |
--hs-nav-muted | Label categoria, toggle rail |
--hs-nav-link | Testo voce inattiva |
--hs-nav-hover-bg | Sfondo hover (translucido) |
--hs-nav-active-bg | Sfondo voce attiva |
--hs-nav-active-hover-bg | Sfondo hover voce attiva |
--hs-nav-active-text | Testo voce attiva |
--hs-nav-accent | Barra accent verticale della voce attiva |
--hs-nav-divider | Divider modulo e guida verticale dei gruppi annidati |
--hs-nav-ring | Focus ring di link e toggle |
--hs-nav-highlight-bg / --hs-nav-highlight-text | Evidenziazione highlightQuery |
/* Esempio: accent verde brand su tutta l'app */
.my-app .hs-nav-menu {
--hs-nav-accent: var(--color-success-500);
--hs-nav-active-text: var(--color-success-700);
}
TypeScript
import type {
NavigationMenuProps,
NavigationMenuEmits,
NavigationMenuSlots,
MenuStructure,
MenuGroup,
MFEManifest,
} from '@pzeta/vue-components'
MobileNav
Navigazione mobile con hamburger menu animato e overlay. Supporta v-model per controllo dello stato aperto/chiuso, posizione top o left, overlay scuro e slot per header, footer e item personalizzati.
PanelMenu
Menu accordion multi-livello con pannelli espandibili. Supporta controllo esterno tramite expandedKeys, espansione multipla simultanea, badge e slot per personalizzazione di icone e voci.