defmodule Skua.Components.Menu do
@moduledoc """
A dropdown menu — a trigger button that opens a top-layer menu of actions.
<.menu id="actions">
<:trigger>Actions</:trigger>
<.menu_label>Project</.menu_label>
<.menu_item icon="hero-pencil" shortcut="⌘R" phx-click="rename">Rename</.menu_item>
<.menu_item shortcut="⌘D" phx-click="duplicate">Duplicate</.menu_item>
<.menu_separator />
<.menu_item danger phx-click="delete">Delete project</.menu_item>
</.menu>
Built on the same top-layer `PanelStack` as the popover, with the W3C APG menu
keyboard model (Arrow up/down move between items, Enter/Space activate, Escape
closes, Home/End jump). Activating an item closes the menu.
"""
use Phoenix.Component
@doc "The menu: a trigger button + a top-layer `role=menu` panel of items."
attr :id, :string, required: true
attr :trigger_variant, :string, default: "secondary", values: ~w(primary secondary ghost danger)
attr :width, :string, default: nil
attr :placement, :string, default: "bottom", values: ~w(bottom right)
attr :class, :any, default: nil
attr :rest, :global
slot :trigger, required: true
slot :inner_block, required: true
def menu(assigns) do
~H"""
<span class="sk-anchor" style="position:relative;display:inline-flex">
<button
type="button"
id={"#{@id}-trigger"}
class={"sk-btn sk-btn--#{@trigger_variant} sk-focusable"}
phx-hook="SkuaMenu"
data-sk-panel={@id}
aria-haspopup="menu"
aria-expanded="false"
aria-controls={@id}
{@rest}
>
{render_slot(@trigger)}
<svg class="sk-glyph sk-chev" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path d="m6 9 6 6 6-6" />
</svg>
</button>
<div
id={@id}
role="menu"
class={["sk-panel sk-anim", @class]}
popover="manual"
data-state="closed"
data-placement={@placement}
style={@width && "min-width:#{@width}"}
>
<div class="sk-menu">{render_slot(@inner_block)}</div>
</div>
</span>
"""
end
@doc "A menu action. `danger` styles it destructive; `shortcut`/`icon` are optional."
attr :danger, :boolean, default: false
attr :icon, :string, default: nil, doc: "a heroicon name, e.g. \"hero-pencil\""
attr :shortcut, :string, default: nil
attr :class, :any, default: nil
attr :rest, :global, include: ~w(disabled)
slot :inner_block, required: true
def menu_item(assigns) do
~H"""
<button
type="button"
role="menuitem"
tabindex="-1"
class={["sk-item sk-menu-action", @danger && "sk-item--danger", @class]}
{@rest}
>
<span :if={@icon} class="sk-lead"><span class={[@icon, "sk-glyph"]} /></span>
{render_slot(@inner_block)}
<span :if={@shortcut} class="sk-kbd">{@shortcut}</span>
</button>
"""
end
@doc "A small uppercase section label inside a menu."
slot :inner_block, required: true
def menu_label(assigns) do
~H"""
<div class="sk-menu-label" role="presentation">{render_slot(@inner_block)}</div>
"""
end
@doc "A divider between menu sections."
def menu_separator(assigns) do
~H"""
<div class="sk-sep" role="separator"></div>
"""
end
end