Corex.Menu
(Corex v0.1.0-beta.5)
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 in maps passed to Corex.Tree.new/1 (auto-generated when omitted).
You can specify disabled for each item and nested children.
<.menu
class="menu"
items={[
%Corex.Tree.Item{
value: "edit",
label: "Edit"
},
%Corex.Tree.Item{
value: "duplicate",
label: "Duplicate"
},
%Corex.Tree.Item{
value: "delete",
label: "Delete"
}
]}
>
<:trigger>Actions</:trigger>
<:indicator>
<.heroicon 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{
value: "new-tab",
label: "New tab"
},
%Corex.Tree.Item{
value: "share",
label: "Share",
children: [
%Corex.Tree.Item{
value: "messages",
label: "Messages"
},
%Corex.Tree.Item{
value: "airdrop",
label: "Airdrop"
},
%Corex.Tree.Item{
value: "whatsapp",
label: "WhatsApp"
}
]
},
%Corex.Tree.Item{
value: "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{
value: "share",
label: "Share",
children: [
%Corex.Tree.Item{value: "messages", label: "Messages"}
]
}
]}
>
<:trigger>Click me</:trigger>
<:nested_indicator>
<.heroicon 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{
value: "edit",
label: "Edit",
group: "Actions"
},
%Corex.Tree.Item{
value: "duplicate",
label: "Duplicate",
group: "Actions"
},
%Corex.Tree.Item{
value: "account-1",
label: "Account 1",
group: "Accounts"
},
%Corex.Tree.Item{
value: "account-2",
label: "Account 2",
group: "Accounts"
}
]}
>
<:trigger>Actions</:trigger>
<:indicator>
<.heroicon name="hero-chevron-down" />
</:indicator>
</.menu>Use as Navigation
Set redirect on the component so selecting an item navigates to the item's value (e.g. path).
Per item, choose the navigation kind explicitly via the item's :redirect field:
:href(default) - full page redirect viawindow.location(safe everywhere):patch- LiveViewjs().patch(url)(caller asserts: same LV mount + matching live route):navigate- LiveViewjs().navigate(url)(caller asserts: another LV in the samelive_session)false- disable redirect for this item (e.g. let youron_selectserver handler decide)
Set new_tab: true on an item to open its destination in a new tab via window.open.
Breaking change
Earlier versions were no-ops when LiveView was connected (the server
handler was expected to call redirect/2). The hook now performs a hard
:href redirect by default. Opt back into the old behavior by setting
the per-item redirect: false, or opt into LV-aware navigation with
redirect: :patch / redirect: :navigate.
Controller
When not connected to LiveView, the hook always performs a full page redirect via window.location.
<.menu
class="menu"
redirect
items={[
%Corex.Tree.Item{value: "/", label: "Home"},
%Corex.Tree.Item{value: "/docs", label: "Docs"},
%Corex.Tree.Item{value: "https://example.com", label: "External", new_tab: true}
]}
>
<:trigger>Navigate</:trigger>
<:indicator>
<.heroicon 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 value).
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{value: "/", label: "Home"},
%Corex.Tree.Item{value: "/docs", label: "Docs"}
]}
>
<:trigger>Navigate</:trigger>
<:indicator>
<.heroicon name="hero-chevron-down" />
</:indicator>
</.menu>
"""
end
endAPI 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)}
endStyling
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>
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
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>
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