feat(member): make overview columns sortable through accessible sort headers

This commit is contained in:
Simon 2026-07-06 10:48:28 +02:00
parent dfc616257d
commit 0c375234c8
9 changed files with 350 additions and 51 deletions

View file

@ -6,44 +6,57 @@ defmodule MvWeb.Components.SortHeaderComponent do
- label: string() # Column Heading (can be an heex template)
- sort_field: atom() | nil # current sort field from parent liveview
- sort_order: :asc | :desc | nil # current sorting order
- sort_hint: string() | nil # optional; overrides the generic "click
# to sort" tooltip/aria-label for columns
# whose sort key is non-obvious (e.g. the
# Name column sorts by last name)
"""
use MvWeb, :live_component
@impl true
def update(assigns, socket) do
{:ok, assign(socket, assigns)}
{:ok, socket |> assign(assigns) |> assign_new(:sort_hint, fn -> nil end)}
end
# Check if we can add the aria-sort label directly to the daisyUI header
# aria-sort={aria_sort(@field, @sort_field, @sort_order)}
@impl true
def render(assigns) do
assigns =
assigns
|> assign(:tooltip_id, "sort-tooltip-#{assigns.field}")
|> assign(:hint, assigns.sort_hint || gettext("Click to sort"))
~H"""
<div>
<.tooltip content={aria_sort(@field, @sort_field, @sort_order)} position="bottom">
<.button
type="button"
variant="ghost"
aria-label={aria_sort(@field, @sort_field, @sort_order)}
class="select-none"
phx-click="sort"
phx-value-field={@field}
data-testid={@field}
>
{@label}
<%= if @sort_field == @field do %>
<.icon
name={if @sort_order == :asc, do: "hero-chevron-up", else: "hero-chevron-down"}
class="sort-icon"
/>
<% else %>
<.icon
name="hero-chevron-up-down"
class="sort-icon opacity-40"
/>
<% end %>
</.button>
</.tooltip>
<button
id={"sort-btn-#{@field}"}
type="button"
aria-label={aria_sort(@field, @sort_field, @sort_order, @hint)}
aria-describedby={@tooltip_id}
class="link link-hover no-underline w-full flex items-center gap-1 text-left text-current select-none"
phx-click="sort"
phx-value-field={@field}
phx-hook="SortTooltip"
data-tooltip-id={@tooltip_id}
data-testid={@field}
>
{@label}
<%= if @sort_field == @field do %>
<.icon
name={if @sort_order == :asc, do: "hero-chevron-up", else: "hero-chevron-down"}
class="sort-icon"
/>
<% else %>
<.icon
name="hero-chevron-up-down"
class="sort-icon opacity-40"
/>
<% end %>
</button>
<div id={@tooltip_id} popover="manual" role="tooltip" class="sort-tooltip">
{aria_sort(@field, @sort_field, @sort_order, @hint)}
</div>
</div>
"""
end
@ -51,13 +64,13 @@ defmodule MvWeb.Components.SortHeaderComponent do
# -------------------------------------------------
# Hilfsfunktionen für ARIA Attribute & Icon SVG
# -------------------------------------------------
defp aria_sort(field, sort_field, dir) when field == sort_field do
defp aria_sort(field, sort_field, dir, hint) when field == sort_field do
case dir do
:asc -> gettext("ascending")
:desc -> gettext("descending")
nil -> gettext("Click to sort")
nil -> hint
end
end
defp aria_sort(_, _, _), do: gettext("Click to sort")
defp aria_sort(_, _, _, hint), do: hint
end