Submenus
A row becomes a submenu simply by having items. Nest as deep as you like:
Move to opens a panel that itself contains Projects. Where
items is a function that returns a promise, as on Add label, the
panel shows a loading state in the right place and swaps in the real list when it arrives.
Right click a message, then hover Move to
{
label: 'Move to',
items: [
{ label: 'Inbox' },
{ label: 'Archive' },
{
label: 'Projects',
items: [
{ label: 'Northwind' },
{ label: 'Contoso' },
],
},
],
}
A submenu passes its parent panel as the rectangle to avoid, so it flanks the whole panel rather than covering it, while still lining up with the row that opened it. Move the pointer diagonally towards an open submenu and the parent row keeps it open: the safe triangle covers the path your hand actually takes.
{
label: 'Add label',
items: async (meta) => {
const labels = await fetchLabels(meta.id)
return labels.map((label) => ({
type: 'checkbox',
label: label.name,
checked: label.applied,
}))
},
}
A resolver that settles immediately renders nothing extra. One that actually waits
shows a loading panel first, and one that rejects shows an error panel and emits
error rather than closing, because the user asked for this menu and
telling them it failed beats making the right click look broken.