Elements
Menu
A menu is a list of actions its trigger opens. One structure serves every device, presented as each expects:
- On the desktops and in desktop browsers, a popup at the trigger; a
Submenucascades beside its item as the pointer comes over it or presses it. - On phones and tablets, a sheet at the bottom of the screen over a backdrop; a
Submenushows its items in the sheet in place of the menu’s, with a row of yours leading back.
Choosing an item, a press outside, Escape and the Android back button close it. ContextMenu is the same menu opened
by the secondary button (a right click) at the pointer, or by a finger held on its trigger on touch screens.
use nsh_base::{menu, menu_item, menu_separator, submenu};
use nsh_core::ui::prelude::*;
fn item(label: &'static str, on_select: impl Fn() + 'static) -> impl IntoElement {
menu_item()
.on_select(on_select)
.px_3()
.py_2()
.rounded_md()
.child(text(label))
}
fn file_menu() -> impl IntoElement {
menu()
.offset(px(4.0))
.trigger(view().px_3().py_2().child(text("File")))
.child(item("New", || log::info!("new")))
.child(item("Open…", || log::info!("open")))
.child(menu_separator().h(px(1.0)).my_1().bg(rgb(0xe5e7eb)))
.child(
submenu()
.trigger(view().px_3().py_2().child(text("Share ›")))
.back(view().px_3().py_2().child(text("‹ Back")))
.child(item("Mail", || log::info!("mail")))
.child(item("Copy link", || log::info!("link"))),
)
}import { Text, View } from "nsh";
import { Menu, MenuItem, MenuSeparator, Submenu } from "nsh/base";
function Item(props: { label: string; onSelect: () => void }) {
return (
<MenuItem onSelect={props.onSelect} class="rounded-md px-3 py-2 hover:bg-gray-100 active:bg-gray-200">
<Text>{props.label}</Text>
</MenuItem>
);
}
function FileMenu() {
return (
<Menu
offset={4}
popupClass="min-w-48 rounded-lg bg-white p-1 shadow-lg"
backdropClass="bg-black/40"
trigger={<View class="px-3 py-2"><Text>File</Text></View>}
>
<Item label="New" onSelect={newFile} />
<Item label="Open…" onSelect={openFile} />
<MenuSeparator class="my-1 h-px bg-gray-200" />
<Submenu
trigger={<View class="px-3 py-2"><Text>Share ›</Text></View>}
back={<View class="px-3 py-2"><Text>‹ Back</Text></View>}
>
<Item label="Mail" onSelect={mail} />
<Item label="Copy link" onSelect={copyLink} />
</Submenu>
</Menu>
);
}All five come from nsh/base. The items reach their menu on their own, so you arrange them
freely: groups in views, labels, items that come and go with If. popupClass styles the popup on desktops and the
sheet on phones; a style that differs between them can compare platform.idiom (nsh/device) with "desktop".
#Context menus
import { ContextMenu, MenuItem } from "nsh/base";
<ContextMenu
popupClass="min-w-40 rounded-lg bg-white p-1 shadow-lg"
backdropClass="bg-black/40"
trigger={<PhotoTile photo={photo} />}
>
<MenuItem onSelect={() => share(photo)}><Text>Share</Text></MenuItem>
<MenuItem onSelect={() => remove(photo)}><Text>Delete</Text></MenuItem>
</ContextMenu>
On the desktops the secondary button opens it where the pointer is. On touch screens a finger held on the trigger opens it as a sheet; the trigger’s own presses and the scroll views around it keep working, and moving the finger cancels the hold.
#Open state
Without open, a menu keeps its own state (from defaultOpen for Menu). With open, the app owns it and changes
it in onOpenChange(open, reason): "triggerPress", "select", "pressOutside", "backdropPress", "cancel",
"contextMenu" or "longPress".
#Props
#Menu
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
open | .open(..) | boolean | none | Open, controlled by the app. |
defaultOpen | .default_open(..) | boolean | false | Open at first, without open. |
placement | .placement(..) | "top" | "bottom" | "left" | "right" | "bottom" | Desktops: the side of the trigger it goes on. |
align | .align(..) | "start" | "center" | "end" | "start" | Desktops: how it lines up with the trigger. |
offset | .offset(..) | number | 0 | Desktops: the gap between the trigger and the popup. |
margin | .margin(..) | number | 0 | Desktops: the distance it keeps from the edges of the safe area. |
popupClass, popupStyle | .popup_class(..), .popup_style(..) | string, StyleProp<ViewStyle> | none | The popup (desktops) or the sheet (phones), and the submenus’ popups. |
backdropClass, backdropStyle | .backdrop_class(..), .backdrop_style(..) | string, StyleProp<ViewStyle> | none | Phones: the backdrop behind the sheet. |
trigger | .trigger(..) | JSX.Element | none | What opens it when pressed. |
children | .child(..) | JSX.Element | none | The items, made each time it opens. |
#ContextMenu
open, popupClass / popupStyle, backdropClass / backdropStyle, trigger and children as for Menu, and:
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
disabled | .disabled(..) | boolean | false | The secondary button and held fingers do nothing. |
offset | .offset(..) | number | 0 | Desktops: the gap between the pointer and the popup. |
margin | .margin(..) | number | 0 | Desktops: the distance it keeps from the edges of the safe area. |
#MenuItem
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
disabled | .disabled(..) | boolean | false | Not chosen; disabled: styles apply. |
closeOnSelect | .close_on_select(..) | boolean | true | Choosing it closes the menu. |
accessibilityLabel | .accessibility_label(..) | string | none | What screen readers say, when the content is not enough. |
children, class, style | .child(..), style methods | none | Its content and look, with hover: and active: states. |
#Submenu
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
trigger | .trigger(..) | JSX.Element | none | The item that opens it. |
back | .back(..) | JSX.Element | none | Phones: the row above its items that returns to the items around it. |
disabled | .disabled(..) | boolean | false | It does not open. |
children | .child(..) | JSX.Element | none | Its items. |
MenuSeparator is a view you style (class, style), shown with the items of its level.
#Events
| Event | Rust | Description |
|---|---|---|
onOpenChange (Menu, ContextMenu) | .on_open_change(|e| ..) | It opened or closed, with why. |
onOpen (Menu, ContextMenu) | .on_open(|| ..) | It is shown; as a sheet, once it came in. |
onClose (Menu, ContextMenu) | .on_close(|| ..) | It is gone; as a sheet, once it went. Also when the app closed it. |
onCancel (Menu, ContextMenu) | .on_cancel(|| ..) | The user tried to close it without choosing (Escape or back, a press outside or on the backdrop). |
onSelect (MenuItem) | .on_select(|| ..) | The item was chosen. |
#Accessibility
The popup or sheet is a menu and its items menu items for screen readers; the trigger tells whether the menu is expanded. Only the items of the level shown are on screen, so screen readers read what is there.