Corex.Menu (Corex v0.1.0-alpha.29)

View Source

Phoenix implementation of Zag.js Menu.

Examples

List

You must use Corex.Tree.Item struct for items.

The value for each item is optional, useful for controlled mode and API to identify the item.

You can specify disabled for each item and nested children.

<.menu
  class="menu"
  items={[
    %Corex.Tree.Item{
      id: "edit",
      label: "Edit"
    },
    %Corex.Tree.Item{
      id: "duplicate",
      label: "Duplicate"
    },
    %Corex.Tree.Item{
      id: "delete",
      label: "Delete"
    }
  ]}
>
  <:trigger>Actions</:trigger>
  <:indicator>
    <.icon name="hero-chevron-down" />
  </:indicator>
</.menu>

Nested Menu

Use children in Corex.Tree.Item to create nested menus.

<.menu
  class="menu"
  items={[
    %Corex.Tree.Item{
      id: "new-tab",
      label: "New tab"
    },
    %Corex.Tree.Item{
      id: "share",
      label: "Share",
      children: [
        %Corex.Tree.Item{
          id: "messages",
          label: "Messages"
        },
        %Corex.Tree.Item{
          id: "airdrop",
          label: "Airdrop"
        },
        %Corex.Tree.Item{
          id: "whatsapp",
          label: "WhatsApp"
        }
      ]
    },
    %Corex.Tree.Item{
      id: "print",
      label: "Print..."
    }
  ]}
>
  <:trigger>Click me</:trigger>
</.menu>

Nested Menu with Custom Indicator

Use the :nested_indicator slot to customize the indicator shown on items with nested menus (defaults to arrow right →).

<.menu
  class="menu"
  items={[
    %Corex.Tree.Item{
      id: "share",
      label: "Share",
      children: [
        %Corex.Tree.Item{id: "messages", label: "Messages"}
      ]
    }
  ]}
>
  <:trigger>Click me</:trigger>
  <:nested_indicator>
    <.icon name="hero-arrow-right" />
  </:nested_indicator>
</.menu>

Grouped Items

Use group in Corex.Tree.Item to group related items. The group value is used as the section label (same as select).

<.menu
  class="menu"
  items={[
    %Corex.Tree.Item{
      id: "edit",
      label: "Edit",
      group: "Actions"
    },
    %Corex.Tree.Item{
      id: "duplicate",
      label: "Duplicate",
      group: "Actions"
    },
    %Corex.Tree.Item{
      id: "account-1",
      label: "Account 1",
      group: "Accounts"
    },
    %Corex.Tree.Item{
      id: "account-2",
      label: "Account 2",
      group: "Accounts"
    }
  ]}
>
  <:trigger>Actions</:trigger>
  <:indicator>
    <.icon name="hero-chevron-down" />
  </:indicator>
</.menu>

Controlled

Render a menu controlled by the server.

You must use the on_select event to handle selection on the server.

defmodule MyAppWeb.MenuLive do
use MyAppWeb, :live_view

def mount(_params, _session, socket) do
  {:ok, assign(socket, :open, false)}
end

def handle_event("on_select", %{"value" => value}, socket) do
  IO.inspect("Selected: #{value}")
  {:noreply, socket}
end

def handle_event("on_open_change", %{"open" => open}, socket) do
  {:noreply, assign(socket, :open, open)}
end

def render(assigns) do
  ~H"""
  <.menu
    id="my-menu"
    controlled
    open={@open}
    on_select="on_select"
    on_open_change="on_open_change"
    class="menu"
    items={[
      %Corex.Tree.Item{id: "edit", label: "Edit"},
      %Corex.Tree.Item{id: "duplicate", label: "Duplicate"},
      %Corex.Tree.Item{id: "delete", label: "Delete"}
    ]}
  >
    <:trigger>Actions</:trigger>
        <:indicator>
    <.icon name="hero-chevron-down" />
  </:indicator>
  </.menu>
  """
end
end

Use as Navigation

Set redirect so selecting an item navigates to the item's id (e.g. path). Per item: redirect: false disables redirect; new_tab: true opens in a new tab.

Controller

When not connected to LiveView, the hook automatically performs a full page redirect via window.location.

<.menu
  class="menu"
  redirect
  items={[
    %Corex.Tree.Item{id: "/", label: "Home"},
    %Corex.Tree.Item{id: "/docs", label: "Docs"},
    %Corex.Tree.Item{id: "https://example.com", label: "External", new_tab: true}
  ]}
>
  <:trigger>Navigate</:trigger>
  <:indicator>
    <.icon name="hero-chevron-down" />
  </:indicator>
</.menu>

LiveView

When connected to LiveView, use on_select and redirect in the callback. The payload includes value (the item id).

defmodule MyAppWeb.NavMenuLive do
  use MyAppWeb, :live_view

  def handle_event("handle_select", %{"value" => value}, socket) do
    {:noreply, push_navigate(socket, to: value)}
  end

  def render(assigns) do
    ~H"""
    <.menu
      class="menu"
      redirect
      on_select="handle_select"
      items={[
        %Corex.Tree.Item{id: "/", label: "Home"},
        %Corex.Tree.Item{id: "/docs", label: "Docs"}
      ]}
    >
      <:trigger>Navigate</:trigger>
      <:indicator>
        <.icon name="hero-chevron-down" />
      </:indicator>
    </.menu>
    """
  end
end

API Control

In order to use the API, you must use an id on the component

Client-side

<button phx-click={Corex.Menu.set_open("my-menu", true)}>
  Open Menu
</button>

Server-side

def handle_event("open_menu", _, socket) do
  {:noreply, Corex.Menu.set_open(socket, "my-menu", true)}
end

Styling

Use data attributes to target elements:

[data-scope="menu"][data-part="root"] {}
[data-scope="menu"][data-part="trigger"] {}
[data-scope="menu"][data-part="positioner"] {}
[data-scope="menu"][data-part="content"] {}
[data-scope="menu"][data-part="item"] {}
[data-scope="menu"][data-part="separator"] {}
[data-scope="menu"][data-part="item-group"] {}
[data-scope="menu"][data-part="item-group-label"] {}

If you wish to use the default Corex styling, you can use the class menu on the component. This requires to install Mix.Tasks.Corex.Design first and import the component css file.

@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/menu.css";

You can then use modifiers

<.menu class="menu menu--accent menu--lg">
</.menu>

Learn more about modifiers and Corex Design

Summary

Components

Renders a menu component.

API

Sets the menu open state from client-side. Returns a Phoenix.LiveView.JS command.

Sets the menu open state from server-side. Pushes a LiveView event.

Components

API

set_open(menu_id, open)

Sets the menu open state from client-side. Returns a Phoenix.LiveView.JS command.

Examples

<button phx-click={Corex.Menu.set_open("my-menu", true)}>
  Open Menu
</button>

set_open(socket, menu_id, open)

Sets the menu open state from server-side. Pushes a LiveView event.

Examples

def handle_event("open_menu", _params, socket) do
  socket = Corex.Menu.set_open(socket, "my-menu", true)
  {:noreply, socket}
end