Navigation menu

v0.0.10

v0.0.10StableMobStatelessApache-2.0
Installation mix mishka.ui.gen.mob navigation_menu
Source View Source

The Mishka Chelekom Mob Navigation Menu is a site navigation bar for iOS and Android screens built with Mob, where expandable items share one content area. It is the native port of the headless Navigation Menu.

Overview of the Mob Navigation Menu component for iOS and Android

A navigation menu looks like a menubar but does a different job. A Menubar holds commands such as File, Edit and View, and its panels list actions. A navigation menu is site navigation, and its items are destinations. Some are plain links, and some open a richer panel. All panels share one viewport.

On the web, the shared viewport morphs the popup's size from the old content to the new. Mob has no animation primitive, so the viewport shows the active item's content. The menu has one content area, so panels cannot overlap or render twice.

Your screen owns value, the item that is open. toggle/2 gives the next value, because one viewport can show only one item. A trigger shows ▸ when it is closed and ▾ when it is open.

In a horizontal menu each item hugs its label, so every item is drawn side by side. In a vertical menu each item fills the width, so a tap anywhere on the line counts.

Items

An item is a map with label, value and, for a trigger, content, a list of nodes. An item with content opens a panel, and an item without it is a plain link. An item can also set disabled.

elixir
[
  %{
    label: "Products",
    value: :products,
    content: [
      ~MOB"""
      <Column fill_width={true}>
        <Text text="Chelekom" text_size={:base} text_color={:on_surface} font_weight={:semibold} />
        <Spacer size={4} />
        <Text text="Headless components for Phoenix and Mob." text_size={:sm} text_color={:muted} />
      </Column>
      """
    ]
  },
  %{label: "Blog", value: :blog}
]

Generate the navigation menu component

Run this from your Mob app's root to add the component to lib/<app>/components/:

bash
mix mishka.ui.gen.mob navigation_menu

The generator registers the <NavigationMenu> tag so you can use it in ~MOB markup. It also copies the shared Event and Anchored support modules. The examples on this page use the mishka_ prefix of the showcase app (--module-prefix mishka_), so the tag is <MishkaNavigationMenu>. Without a prefix the tag is <NavigationMenu>.

Helper functions

The generated MishkaNavigationMenu module exposes these functions:

FunctionReturns
trigger?(item)true when the item has content and opens a panel, false for a plain link.
toggle(value, current)nil when value is already open, otherwise value.
elixir
MishkaNavigationMenu.trigger?(%{label: "Docs", value: :docs})  #=> false
MishkaNavigationMenu.toggle(:docs, :docs)                      #=> nil
MishkaNavigationMenu.toggle(:docs, :blog)                      #=> :docs

Props

PropTypeDefaultDescription
itemslist of item maps[]The menu items.
valuevaluenilWhich item is open.
orientation:horizontal or :vertical:horizontalA row of items that hug their labels, or a stack of items that fill the width.
disabledbooleanfalseDisables the whole nav.
on_openevent tag (atom)NoneSends {:tap, {tag, value}} when a trigger is tapped.
on_linkevent tag (atom)NoneSends {:tap, {tag, value}} when a plain link is tapped.

Events

Your screen receives {:tap, {tag, value}} in handle_info/2 from both on_open and on_link. The value is the item's own value. A disabled item sends nothing. Use toggle/2 to compute the next open item.

elixir
def handle_info({:tap, {:open, value}}, socket) do
  open = MishkaNavigationMenu.toggle(value, socket.assigns.open)
  {:noreply, Mob.Socket.assign(socket, :open, open)}
end

Differences from the web component

These parts of the headless component are not ported:

  • delay and close_delay, because hover has no touch equivalent.
  • side, align and arrow, because the viewport is one shared area in the layout flow, with no side to take and nothing to point an arrow at.
  • The morph animation, because Mob has no animation primitive.