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
|
|
@ -101,9 +101,25 @@ defmodule MvWeb.RoleLive.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
|
||||
|
||||
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}
|
||||
|
||||
defp handle_delete_role(role, socket) do
|
||||
if role.is_system_role do
|
||||
{:noreply,
|
||||
|
|
@ -167,6 +183,12 @@ defmodule MvWeb.RoleLive.Show do
|
|||
recalculate_user_count(role, actor)
|
||||
end
|
||||
|
||||
defp close_delete_modal_and_restore_focus(socket) do
|
||||
socket
|
||||
|> assign(:show_delete_modal, false)
|
||||
|> push_event("focus_restore", %{id: "delete-role-trigger"})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
|
|
@ -198,94 +220,102 @@ defmodule MvWeb.RoleLive.Show do
|
|||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.list>
|
||||
<:item title={gettext("Name")}>{@role.name}</:item>
|
||||
<:item title={gettext("Description")}>
|
||||
<%= if @role.description do %>
|
||||
{@role.description}
|
||||
<% else %>
|
||||
<span class="text-base-content/70 italic">{gettext("No description")}</span>
|
||||
<% end %>
|
||||
</:item>
|
||||
<:item title={gettext("Permission Set")}>
|
||||
<.badge variant={permission_set_badge_variant(@role.permission_set_name)}>
|
||||
{@role.permission_set_name}
|
||||
</.badge>
|
||||
</:item>
|
||||
<:item title={gettext("System Role")}>
|
||||
<.badge :if={@role.is_system_role} variant="warning">
|
||||
{gettext("Yes")}
|
||||
</.badge>
|
||||
<.badge :if={!@role.is_system_role} variant="neutral">
|
||||
{gettext("No")}
|
||||
</.badge>
|
||||
</:item>
|
||||
</.list>
|
||||
<div
|
||||
id="role-show-focus-root"
|
||||
phx-hook="FocusRestore"
|
||||
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
||||
>
|
||||
<.list>
|
||||
<:item title={gettext("Name")}>{@role.name}</:item>
|
||||
<:item title={gettext("Description")}>
|
||||
<%= if @role.description do %>
|
||||
{@role.description}
|
||||
<% else %>
|
||||
<span class="text-base-content/70 italic">{gettext("No description")}</span>
|
||||
<% end %>
|
||||
</:item>
|
||||
<:item title={gettext("Permission Set")}>
|
||||
<.badge variant={permission_set_badge_variant(@role.permission_set_name)}>
|
||||
{@role.permission_set_name}
|
||||
</.badge>
|
||||
</:item>
|
||||
<:item title={gettext("System Role")}>
|
||||
<.badge :if={@role.is_system_role} variant="warning">
|
||||
{gettext("Yes")}
|
||||
</.badge>
|
||||
<.badge :if={!@role.is_system_role} variant="neutral">
|
||||
{gettext("No")}
|
||||
</.badge>
|
||||
</:item>
|
||||
</.list>
|
||||
|
||||
<%!-- Danger zone: canonical pattern (same as member show) --%>
|
||||
<%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %>
|
||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
||||
<h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
|
||||
{gettext("Danger zone")}
|
||||
</h2>
|
||||
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
||||
<p class="text-base-content/70 mb-4">
|
||||
{gettext(
|
||||
"Deleting this role cannot be undone. Users assigned to this role must be reassigned first."
|
||||
)}
|
||||
</p>
|
||||
<.button
|
||||
variant="danger"
|
||||
phx-click="open_delete_modal"
|
||||
data-testid="role-delete"
|
||||
aria-label={gettext("Delete role %{name}", name: @role.name)}
|
||||
>
|
||||
<.icon name="hero-trash" class="size-4" />
|
||||
{gettext("Delete role")}
|
||||
</.button>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete Role Confirmation Modal (WCAG: focus moves into modal, keyboard confirm/cancel) --%>
|
||||
<%= if assigns[:show_delete_modal] do %>
|
||||
<dialog
|
||||
id="delete-role-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="delete-role-modal-title"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 id="delete-role-modal-title" class="text-lg font-bold">{gettext("Delete Role")}</h3>
|
||||
<p class="py-4">
|
||||
{gettext(
|
||||
"Are you sure you want to delete the role %{name}? This action cannot be undone.",
|
||||
name: @role.name
|
||||
)}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<%!-- Danger zone: canonical pattern (same as member show) --%>
|
||||
<%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %>
|
||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
||||
<h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
|
||||
{gettext("Danger zone")}
|
||||
</h2>
|
||||
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
||||
<p class="text-base-content/70 mb-4">
|
||||
{gettext(
|
||||
"Deleting this role cannot be undone. Users assigned to this role must be reassigned first."
|
||||
)}
|
||||
</p>
|
||||
<.button
|
||||
type="button"
|
||||
variant="neutral"
|
||||
phx-click="cancel_delete_modal"
|
||||
phx-mounted={JS.focus()}
|
||||
id="delete-role-modal-cancel"
|
||||
aria-label={gettext("Cancel")}
|
||||
>
|
||||
{gettext("Cancel")}
|
||||
</.button>
|
||||
<.button
|
||||
type="button"
|
||||
id="delete-role-trigger"
|
||||
variant="danger"
|
||||
phx-click={JS.push("delete", value: %{id: @role.id})}
|
||||
aria-label={gettext("Delete role")}
|
||||
phx-click="open_delete_modal"
|
||||
data-testid="role-delete"
|
||||
aria-label={gettext("Delete role %{name}", name: @role.name)}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
<.icon name="hero-trash" class="size-4" />
|
||||
{gettext("Delete role")}
|
||||
</.button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete Role Confirmation Modal (WCAG: focus moves into modal, keyboard confirm/cancel) --%>
|
||||
<%= if assigns[:show_delete_modal] do %>
|
||||
<dialog
|
||||
id="delete-role-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="delete-role-modal-title"
|
||||
phx-keydown="dialog_keydown"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 id="delete-role-modal-title" class="text-lg font-bold">{gettext("Delete Role")}</h3>
|
||||
<p class="py-4">
|
||||
{gettext(
|
||||
"Are you sure you want to delete the role %{name}? This action cannot be undone.",
|
||||
name: @role.name
|
||||
)}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<.button
|
||||
type="button"
|
||||
variant="neutral"
|
||||
phx-click="cancel_delete_modal"
|
||||
phx-mounted={JS.focus()}
|
||||
id="delete-role-modal-cancel"
|
||||
aria-label={gettext("Cancel")}
|
||||
>
|
||||
{gettext("Cancel")}
|
||||
</.button>
|
||||
<.button
|
||||
type="button"
|
||||
variant="danger"
|
||||
phx-click={JS.push("delete", value: %{id: @role.id})}
|
||||
aria-label={gettext("Delete role")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue