defmodule LiveViewContinuity.Menu do
@moduledoc """
An unstyled action and navigation menu with patch-safe client interaction state.
See `MENU.md` for the observable interaction contract.
"""
use Phoenix.Component
alias Phoenix.LiveView.JS
attr(:id, :string, required: true)
attr(:on_action, :string, required: true)
attr(:class, :any, default: nil)
attr(:rest, :global)
slot :trigger, required: true do
attr(:class, :any)
end
slot :item, required: true do
attr(:id, :string, required: true)
attr(:disabled, :boolean)
attr(:class, :any)
attr(:close_on_action, :boolean)
attr(:navigate, :string)
attr(:typeahead_text, :string)
end
def menu(assigns) do
validate_items!(assigns.item)
assigns =
case assigns.trigger do
[trigger_entry] ->
assign(assigns, :trigger_entry, trigger_entry)
entries ->
raise ArgumentError, "menu requires exactly one trigger slot, got: #{length(entries)}"
end
~H"""
"-popup"}
role="menu"
popover="auto"
aria-labelledby={@id <> "-trigger"}
>
<.menu_item
:for={item <- @item}
:key={item.id}
menu_id={@id}
item={item}
/>
"""
end
attr(:menu_id, :string, required: true)
attr(:item, :map, required: true)
defp menu_item(assigns) do
~H"""
<.link
:if={@item[:navigate] && !@item[:disabled]}
id={@menu_id <> "-item-" <> @item.id}
navigate={@item.navigate}
role="menuitem"
tabindex="-1"
aria-disabled={to_string(@item[:disabled] || false)}
class={@item[:class]}
data-lvc-item
data-lvc-logical-id={@item.id}
data-lvc-typeahead={@item[:typeahead_text]}
data-lvc-close-on-action={to_string(Map.get(@item, :close_on_action, true))}
phx-mounted={JS.ignore_attributes(["tabindex", "data-lvc-active"])}
>
{render_slot(@item)}
"""
end
defp validate_items!(items) do
Enum.each(items, fn item ->
case item[:navigate] do
nil ->
:ok
navigate when is_binary(navigate) and navigate != "" ->
if Map.get(item, :close_on_action, true) == false do
raise ArgumentError,
"menu navigation item #{inspect(item.id)} cannot set close_on_action to false"
end
_ ->
raise ArgumentError,
"menu navigation item #{inspect(item.id)} requires a nonempty navigate path"
end
end)
end
end