Menu
In questa pagina
Un menu è un <details> con un pannello stilizzato. È una scelta voluta rispetto all’API popover: un popover vive nel livello superiore e non si può posizionare rispetto al proprio innesco senza l’anchor positioning, che non è ancora ovunque. Un <details> si posiziona correttamente già oggi e non ha bisogno di caricare nulla.
L’innesco è quel <summary>, stilizzato come un pulsante — quindi passi l’etichetta e le props del pulsante a menu() invece di passare un button() già renderizzato. Un summary è già interattivo, e un pulsante al suo interno annida due controlli dove c’è una sola azione: markup non valido, e due fermate di tabulazione per una cosa sola.
La chiusura al clic fuori e con Escape arrivano da un handler ontoggle che le importa la prima volta che un menu viene aperto — e solo allora. Se quel modulo non arriva mai, un menu si apre e si chiude comunque dal proprio summary.
Menu di base
menu({ trigger: 'Azioni' },
menuItem({ href: '#edit' }, 'Modifica'),
menuItem({ href: '#duplicate' }, 'Duplica'),
menuSeparator(),
menuItem({ href: '#delete' }, 'Elimina'),
)Allineamento
Un menu si apre dal bordo iniziale del proprio innesco. align: 'end' lo ribalta, che è ciò di cui ha bisogno un menu vicino al bordo destro di una barra.
stack({ direction: 'row', gap: 'xl', justify: 'space-between', style: 'width: 100%' },
menu({ trigger: 'Allineato all’inizio', variant: 'soft' },
menuItem({ href: '#a' }, 'Primo'),
menuItem({ href: '#b' }, 'Secondo'),
),
menu({ trigger: 'Allineato alla fine', variant: 'soft', align: 'end' },
menuItem({ href: '#c' }, 'Primo'),
menuItem({ href: '#d' }, 'Secondo'),
),
)Inneschi a icona
Un’icona senza testo in trigger ha bisogno di una label — diventa il nome accessibile che l’icona non può fornire.
stack({ direction: 'row', gap: 'md' },
menu({
align: 'end',
label: 'Altre azioni',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '#rename' }, 'Rinomina'),
menuItem({ href: '#move' }, 'Sposta'),
menuSeparator(),
menuItem({ href: '#archive' }, 'Archivia'),
),
)Elementi con icone
menu({ trigger: 'File' },
menuItem({
href: '#new',
icon: icon('plus'),
}, 'Nuova pagina'),
menuItem({
href: '#open',
icon: icon('folder'),
}, 'Apri…'),
menuSeparator(),
menuItem({
href: '#build',
icon: icon('zap'),
}, 'Costruisci il sito'),
)Pulsanti invece di link
Un elemento senza href renderizza un <button> — per un’azione che avviene nella pagina invece di una navigazione.
menu({ trigger: 'Esporta', variant: 'soft', color: 'primary' },
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Esportato come JSON.',{color:'success'}))" }, 'Come JSON'),
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Esportato come CSV.',{color:'success'}))" }, 'Come CSV'),
)In una barra applicazione
appBar({ brand: 'sitelo' },
appBarSpacer(),
appBarActions(
themeToggle(),
menu({
align: 'end',
label: 'Altro',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '/docs' }, 'Documentazione'),
menuItem({ href: '/examples' }, 'Esempi'),
menuSeparator(),
menuItem({ href: 'https://github.com/paul-browne/sitelo' }, 'GitHub'),
),
),
)Accessibilità
Il pannello è un role="menu" i cui elementi sono role="menuitem", e il summary porta aria-haspopup. Un <details> non è un widget menu nativo, quindi questa è un’approssimazione ragionevole più che una perfetta — per un semplice elenco di link, un nav dentro il details è altrettanto valido e promette meno.
Props
menu() — le props dell’innesco sono quelle del pulsante:
| Prop | Tipo | Predefinito | Descrizione |
|---|---|---|---|
trigger | Child | — | Etichetta visibile. Passa del testo, non un button() già renderizzato. |
icon | Child | — | Markup prima dell’etichetta, o da solo per un innesco di sola icona. |
label | string | — | Nome accessibile. Obbligatorio quando c’è un’icona e nessun testo in trigger. |
variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'outline' | Stile dell’innesco. |
color | 'primary' | 'neutral' | 'success' | 'warning' | 'danger' | 'neutral' | Da quale palette attinge l’innesco. |
size | 'sm' | 'md' | 'lg' | 'md' | Dimensione dell’innesco. |
align | 'start' | 'end' | 'start' | A quale bordo dell’innesco si allinea il pannello. |
triggerClass | string | — | Classi extra per l’innesco invece che per il details che lo avvolge. |
menuItem():
| Prop | Tipo | Predefinito | Descrizione |
|---|---|---|---|
href | string | — | Renderizza un’ancora; senza, un pulsante. |
icon | Child | — | Markup prima dell’etichetta. |
as | string | 'button' | Elemento da renderizzare quando non c’è href. |
menuSeparator() non accetta props — è il filetto fra gruppi di elementi.