130 lines
3.9 KiB
Elixir
130 lines
3.9 KiB
Elixir
defmodule MvWeb.Components.ViewSettingsDropdownComponent do
|
|
@moduledoc """
|
|
LiveComponent for the member-overview view settings (§1.20).
|
|
|
|
Renders an icon button with a tooltip that opens a dropdown of toggles:
|
|
|
|
* Compact mode — table row density (compact ↔ comfortable)
|
|
* Compact Member field — composite last+first name cell, with a sub-toggle
|
|
"include email"
|
|
* Compact address field — composite street / postal+city cell
|
|
|
|
Turning a composite field off surfaces the underlying separate columns in the
|
|
table. The component is display-only: it emits `{:view_setting_toggled, key}`
|
|
to the parent LiveView, which owns and persists the settings.
|
|
|
|
## Props
|
|
- `:density` — `:compact` or `:comfortable`
|
|
- `:compact_member` — boolean
|
|
- `:member_include_email` — boolean
|
|
- `:compact_address` — boolean
|
|
- `:id` — component id
|
|
"""
|
|
|
|
use MvWeb, :live_component
|
|
|
|
@impl true
|
|
def update(assigns, socket) do
|
|
{:ok,
|
|
socket
|
|
|> assign(assigns)
|
|
|> assign_new(:open, fn -> false end)}
|
|
end
|
|
|
|
@impl true
|
|
def render(assigns) do
|
|
~H"""
|
|
<div>
|
|
<.dropdown_menu
|
|
id="view-settings-menu"
|
|
icon="hero-cog-6-tooth"
|
|
button_label={gettext("View")}
|
|
open={@open}
|
|
phx_target={@myself}
|
|
menu_width="w-72"
|
|
testid="view-settings"
|
|
button_testid="view-settings-button"
|
|
>
|
|
<li role="none" class="mb-1 px-2">
|
|
<span class="font-semibold">{gettext("View settings")}</span>
|
|
</li>
|
|
<li role="separator" class="divider my-1"></li>
|
|
|
|
<.toggle_item
|
|
setting="density"
|
|
label={gettext("Compact mode")}
|
|
checked={@density == :compact}
|
|
target={@myself}
|
|
/>
|
|
|
|
<.toggle_item
|
|
setting="compact_member"
|
|
label={gettext("Compact Member field")}
|
|
checked={@compact_member}
|
|
target={@myself}
|
|
/>
|
|
|
|
<%!-- The email line lives inside the composite Member cell, so this
|
|
sub-toggle is only meaningful while that cell is compact. When the
|
|
composite is off, email is its own column and this toggle is omitted. --%>
|
|
<li :if={@compact_member} role="none" class="pl-6">
|
|
<.toggle_item
|
|
setting="member_include_email"
|
|
label={gettext("include email")}
|
|
checked={@member_include_email}
|
|
target={@myself}
|
|
/>
|
|
</li>
|
|
|
|
<.toggle_item
|
|
setting="compact_address"
|
|
label={gettext("Compact address field")}
|
|
checked={@compact_address}
|
|
target={@myself}
|
|
/>
|
|
</.dropdown_menu>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
attr :setting, :string, required: true
|
|
attr :label, :string, required: true
|
|
attr :checked, :boolean, required: true
|
|
attr :target, :any, required: true
|
|
|
|
defp toggle_item(assigns) do
|
|
~H"""
|
|
<button
|
|
type="button"
|
|
role="menuitemcheckbox"
|
|
aria-checked={to_string(@checked)}
|
|
aria-label={@label}
|
|
tabindex="0"
|
|
class="flex items-center gap-2 px-2 py-1 rounded w-full text-left cursor-pointer hover:bg-base-200"
|
|
phx-click="toggle"
|
|
phx-value-setting={@setting}
|
|
phx-target={@target}
|
|
data-testid={"view-setting-#{String.replace(@setting, "_", "-")}"}
|
|
>
|
|
<span class={if @checked, do: "text-primary", else: "text-base-300"} aria-hidden="true">
|
|
<.icon name="hero-check" class="size-4 shrink-0" />
|
|
</span>
|
|
<span>{@label}</span>
|
|
</button>
|
|
"""
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("toggle_dropdown", _params, socket) do
|
|
{:noreply, assign(socket, :open, !socket.assigns.open)}
|
|
end
|
|
|
|
def handle_event("close_dropdown", _params, socket) do
|
|
{:noreply, assign(socket, :open, false)}
|
|
end
|
|
|
|
def handle_event("toggle", %{"setting" => setting}, socket) do
|
|
send(self(), {:view_setting_toggled, String.to_existing_atom(setting)})
|
|
{:noreply, socket}
|
|
end
|
|
end
|