fix: color contrast dark mode and keyboard moadals
This commit is contained in:
parent
5516c7fe62
commit
c71c7d6ed6
14 changed files with 1067 additions and 740 deletions
|
|
@ -55,18 +55,26 @@ defmodule MvWeb.MemberLive.Show do
|
|||
</:actions>
|
||||
</.header>
|
||||
|
||||
<div class="mt-6 space-y-6">
|
||||
<%!-- Tab Navigation: surface only behind buttons (inline); tabs-bordered; tabs-lg; both tabs keyboard-focusable --%>
|
||||
<div
|
||||
id="member-show-focus-root"
|
||||
class="mt-6 space-y-6"
|
||||
phx-hook="FocusRestore"
|
||||
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
||||
>
|
||||
<%!-- Tab Navigation: roving tabindex (only active tab tabindex="0"), ArrowLeft/ArrowRight (WCAG tab pattern) --%>
|
||||
<div
|
||||
id="member-tablist"
|
||||
role="tablist"
|
||||
class="tabs tabs-bordered tabs-lg bg-base-200/60 rounded-box p-1 w-fit"
|
||||
phx-hook="TabListKeydown"
|
||||
phx-keydown="tab_keydown"
|
||||
>
|
||||
<button
|
||||
id="member-tab-contact"
|
||||
role="tab"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
aria-selected={@active_tab == :contact}
|
||||
tabindex={if @active_tab == :contact, do: "0", else: "-1"}
|
||||
aria-selected={if @active_tab == :contact, do: "true", else: "false"}
|
||||
aria-controls="member-tabpanel-contact"
|
||||
class={[
|
||||
"tab flex items-center gap-2",
|
||||
|
|
@ -82,8 +90,8 @@ defmodule MvWeb.MemberLive.Show do
|
|||
id="member-tab-membership_fees"
|
||||
role="tab"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
aria-selected={@active_tab == :membership_fees}
|
||||
tabindex={if @active_tab == :membership_fees, do: "0", else: "-1"}
|
||||
aria-selected={if @active_tab == :membership_fees, do: "true", else: "false"}
|
||||
aria-controls="member-tabpanel-membership_fees"
|
||||
class={[
|
||||
"tab flex items-center gap-2",
|
||||
|
|
@ -315,6 +323,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
)}
|
||||
</p>
|
||||
<.button
|
||||
id="delete-member-trigger"
|
||||
variant="danger"
|
||||
phx-click="open_delete_modal"
|
||||
data-testid="member-delete"
|
||||
|
|
@ -338,6 +347,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="delete-member-modal-title"
|
||||
phx-keydown="dialog_keydown"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 id="delete-member-modal-title" class="text-lg font-bold">
|
||||
|
|
@ -434,6 +444,21 @@ defmodule MvWeb.MemberLive.Show do
|
|||
{:noreply, assign(socket, :active_tab, :membership_fees)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("tab_keydown", %{"key" => key}, socket)
|
||||
when key in ["ArrowLeft", "ArrowRight"] do
|
||||
new_tab =
|
||||
case {key, socket.assigns.active_tab} do
|
||||
{"ArrowRight", :contact} -> :membership_fees
|
||||
{"ArrowLeft", :membership_fees} -> :contact
|
||||
_ -> socket.assigns.active_tab
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :active_tab, new_tab)}
|
||||
end
|
||||
|
||||
def handle_event("tab_keydown", _params, socket), do: {:noreply, socket}
|
||||
|
||||
@impl true
|
||||
def handle_event("open_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, true)}
|
||||
|
|
@ -441,9 +466,26 @@ defmodule MvWeb.MemberLive.Show do
|
|||
|
||||
@impl true
|
||||
def handle_event("cancel_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, false)}
|
||||
{:noreply, close_delete_modal_and_restore_focus(socket)}
|
||||
end
|
||||
|
||||
# Escape closes modal (WCAG). phx-window-keydown ensures Escape is captured regardless of focus.
|
||||
def handle_event("window_keydown", %{"key" => key}, socket) when key in ["Escape", "Esc"] do
|
||||
if socket.assigns[:show_delete_modal] do
|
||||
{:noreply, close_delete_modal_and_restore_focus(socket)}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("window_keydown", _params, socket), do: {:noreply, socket}
|
||||
|
||||
def handle_event("dialog_keydown", %{"key" => key}, socket) when key in ["Escape", "Esc"] do
|
||||
{:noreply, close_delete_modal_and_restore_focus(socket)}
|
||||
end
|
||||
|
||||
def handle_event("dialog_keydown", _params, socket), do: {:noreply, socket}
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
member = socket.assigns.member
|
||||
|
|
@ -493,6 +535,13 @@ defmodule MvWeb.MemberLive.Show do
|
|||
{:noreply, assign(socket, :vereinfacht_receipts, response)}
|
||||
end
|
||||
|
||||
# WCAG 2.4.3: when modal closes, return focus to the trigger (Delete member button)
|
||||
defp close_delete_modal_and_restore_focus(socket) do
|
||||
socket
|
||||
|> assign(:show_delete_modal, false)
|
||||
|> push_event("focus_restore", %{id: "delete-member-trigger"})
|
||||
end
|
||||
|
||||
# Flash set in LiveComponent is not shown in parent layout; child sends this to display flash
|
||||
@impl true
|
||||
def handle_info({:put_flash, type, message}, socket) do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue