A drop-down menu for selecting actions to be performed, including keyboard navigation.
A drop-down menu is created using the menu()
factory function.
By clicking on the module created by menuButton
or Space if the menuButton
focused, the selection list is
displayed.
You can navigate within the selection list using the keyboard. By Enter, Space or a mouse click an item can be selected. If the menu looses focus or the user clicks outside the selection list, the box is hidden.
The selection list is generated by the menuItems
building block factory. Within this scope, menuItem
defines the
individual options defined.
Beware: Entries cannot be removed from the menu. Once an entry is added to the menu by menuItem
,
it will remain inside forever. So never use some reactive pattern for populating the menu as some Flow<List<T>>
combined with some call to renderEach
or alike!
If the entries change, you must re-render the whole component.
data class MenuEntry(val label: String, val icon: String)
val entries = listOf(
MenuEntry("Duplicate", HeroIcons.duplicate),
MenuEntry("Archive", HeroIcons.archive),
MenuEntry("Move", HeroIcons.share),
)
val action = storeOf("", job = Job())
menu {
menuButton {
+"Open Menu"
}
menuItems {
entries.forEach { entry ->
menuItem {
svg { content(entry.icon) }
+entry.label
selected.map { entry.label } handledBy action.update
}
}
}
}
Do not forget to include a portalRoot()
-call at the end of your initial RenderContext
as explained
here!
If a menuItem
is navigated to by keyboard or mouse movement, it is active
.
In the scope of a menuItem
, active
is available as Flow<Boolean>
to apply styling or to render
or hide certain elements accordingly to the state:
menuItem {
className(active.map {
if (it) "bg-violet-500 text-white"
else "text-gray-900"
})
svg { content(entry.icon) }
+entry.label
selected.map { entry.label } handledBy action.update
}
The individual items in the selection list can be disabled statically or dynamically. In the scope of a menuItem
the Handler<Boolean>
disable
is available for this. It can be called up directly or used to dynamically set the
disabled status of an item depending on an external data flow. The disabled state of the item is also available
as Flow<Boolean>
to style the item depending on it, for example.
data class MenuEntry(val label: String, val icon: String, val disabled: Boolean = false)
val entries = listOf(
MenuEntry("Duplicate", HeroIcons.duplicate, true),
MenuEntry("Archive", HeroIcons.archive),
MenuEntry("Move", HeroIcons.share),
MenuEntry("Delete", HeroIcons.trash),
MenuEntry("Edit", HeroIcons.pencil),
MenuEntry("Copy", HeroIcons.clipboard_copy, true),
MenuEntry("Encrypt", HeroIcons.key)
)
val action = storeOf("", job = Job())
menu {
menuButton { /* ... */ }
menuItems {
entries.forEach { entry ->
menuItem {
className(active.combine(disabled) { a, d ->
if (a && !d) "bg-violet-500 text-white"
else {
if (d) "text-gray-300" else "text-gray-900"
}
})
svg { content(entry.icon) }
+entry.label
if (entry.disabled) disable(true)
selected.map { entry.label } handledBy action.update
}
}
}
}
Menu is an OpenClose
component. There are different Flow
s and Handler
like opened
in its scope available to control the open state of a menu based on state changes.
The opening state of the menu can be bound to an external store
via data binding, e.g. to always display the menu
list.
Showing and hiding the menu can be easily animated with the help of transition
:
menuItems {
transition(opened,
enter = "transition duration-100 ease-out",
enterStart = "opacity-0 scale-95",
enterEnd = "opacity-100 scale-100",
leave = "transition duration-100 ease-in",
leaveStart = "opacity-100 scale-100",
leaveEnde = "opacity-0 scale-95"
)
characters.forEach { (entry, disabledState) ->
listboxItem(entry) {
//...
}
}
}
menuItems
is a PopUpPanel
and therefore provides in its scope some
configuration options, e.g. to control the position or distance of the list box from the menuButton
as a reference element:
menuItems {
placement = PlacementValues.top
addMiddleware(offset(20))
menuItem {
//...
}
//...
}
If the selection list of the menu is open, the focus is on the menuItems
element. If it loses focus again, the
select list is closed and the focus returns to the menuButton
.
A click on the menuButton
toggles the state of the selection list. A click outside the opened selection list closes
this. If the mouse is moved over an item in the open list, it becomes active marked. Clicking on an item when the list
is open selects it and closes the list.
Command | Description |
---|---|
Enter Space when menuButton is focused |
Opens menu and activates first not disabled item |
⬆ ⬇ ︎when menu is open | Activates previous / next item |
Home End ︎when menu is open | Activates first / last item |
A-Z a-z when menu is open | Activates first item beginning with according letter |
Esc when menu is open | Closes menu |
Enter Space when menu is open | Selects active item |
menu {
// inherited by `OpenClose`
val openState: DatabindingProperty<Boolean>
val opened: Flow<Boolean>
val close: SimpleHandler<Unit>
val open: SimpleHandler<Unit>
val toggle: SimpleHandler<Unit>
menuButton() { }
menuItems() {
// inherited by `PopUpPanel`
var placement: Placement
var strategy: Strategy
var flip: Boolean
var skidding: Int
var distance: int
// for each T {
MenuItem {
val index: Int
val selected: Flow<Boolean>
val active: Flow<Boolean>
val disabled: Flow<Boolean>
val disable: SimpleHandler<Boolean>
}
// }
}
}
Parameters: classes
, id
, scope
, tag
, initialize
Default-Tag: div
Scope property | Typ | Description |
---|---|---|
openState |
DatabindingProperty<Boolean> |
Optional (two-way) data binding for opening and closing. |
opened |
Flow<Boolean> |
Data stream that provides Boolean values related to the "open" state. Quite useless within a menu, as it is always true |
close |
SimpleHandler<Unit> |
Handler to close the list box from inside. Should not be used, as the component handles this internally. |
open |
SimpleHandler<Unit> |
handler to open; does not make sense to use within a menu! |
toggle |
SimpleHandler<Unit> |
handler for switching between open and closed; does not make sense to use within a menu. |
Available in the scope of: menu
Parameters: classes
, scope
, tag
, initialize
Default-Tag: button
Available in the scope of: menu
Parameters: classes
, scope
, tag
, initialize
Default-Tag: div
Scope property | Typ | Description |
---|---|---|
placement |
Placement |
Defines the position of the building block, e.g. Placement.top , Placement.bottomRight , etc. Default is Placement.auto . The presumably best position is determined automatically based on the available visible space. |
strategy |
Strategy |
Determines whether the block should be positioned absolute (default) or fixed . |
flip |
Boolean |
If the block comes too close to the edge of the visible area, the position automatically changes to the other side if more space is available there. |
skidding |
Int |
Defines the shifting of the menu along the reference element in pixels. The default value is 0. |
distance |
Int |
Defines the distance of the menu from the reference element in pixels. The default value is 10. |
Available in the scope of: menuItems
Parameters: classes
, scope
, tag
, initialize
Default-Tag: div
Scope property | Typ | Description |
---|---|---|
index |
Int |
The index within the items. |
selected |
Flow<Boolean> |
This data stream provides the selection state of the managed option: true the option is selected, false if not. Quite useless within a menu. |
active |
Flow<Boolean> |
This data stream indicates whether an item has focus: true the option has focus, false if not. Only one option can have focus at a time. |
disabled |
Flow<Boolean> |
This data stream indicates whether an entry is active (false ) or inactive (true ). |
disable |
SimpleHandler<Boolean> |
This handler enables or disables an entry. |