style: consistent save buttons and active tab
This commit is contained in:
parent
82962a2f2a
commit
ba08434604
15 changed files with 486 additions and 438 deletions
|
|
@ -46,14 +46,14 @@ Every authenticated page should follow the same structure:
|
||||||
**MUST:** Use `<.header>` on every page (except login/public pages).
|
**MUST:** Use `<.header>` on every page (except login/public pages).
|
||||||
**SHOULD:** Put short explanations into `<:subtitle>` rather than sprinkling random text blocks.
|
**SHOULD:** Put short explanations into `<:subtitle>` rather than sprinkling random text blocks.
|
||||||
|
|
||||||
### 2.2 Edit/New form header: Back button left (mandatory)
|
### 2.2 Edit/New form header and footer buttons (mandatory)
|
||||||
|
|
||||||
For LiveViews that render an edit or new form (e.g. member, group, role, user, custom field, membership fee type):
|
For LiveViews that render an edit or new form (e.g. member, group, role, user, custom field, membership fee type):
|
||||||
|
|
||||||
- **MUST:** Provide a **Back** button on the **left** side of the header using the `<:leading>` slot (same as data fields: Back left, title next, primary action on the right).
|
- **MUST:** Provide a **Back** button on the **left** side of the header using the `<:leading>` slot (same as data fields: Back left, title next).
|
||||||
- **MUST:** Use the same pattern everywhere: Back button with `variant="neutral"`, arrow-left icon, and label “Back”. It navigates to the previous context (e.g. detail page or index) via a `return_path`-style helper.
|
- **MUST:** Use the same pattern everywhere: Back button with `variant="neutral"`, arrow-left icon, and label “Back”. It navigates to the previous context (e.g. detail page or index) via a `return_path`-style helper.
|
||||||
- **SHOULD:** Place the primary action (e.g. “Save”) in `<:actions>` on the right.
|
- **MUST:** Place **exactly one** form button bar **below all form fields**, inside the `<.form>`, with: **Abbrechen** (Cancel) left, **Speichern** (Save) right. Use `gettext("Cancel")`, `gettext("Save <ressourcename>")`, `phx-disable-with={gettext("Saving...")}` on the submit button. No submit button in the header; no duplicate submit buttons.
|
||||||
- **Rationale:** Users expect a consistent way to leave the form without submitting; Back left matches the data fields edit view and keeps primary actions on the right.
|
- **Rationale:** Users expect a consistent way to leave the form without submitting; Back left. One primary action (Save) per form, in the footer, avoids double submits and matches the reference (member edit form).
|
||||||
|
|
||||||
**Template for form pages:**
|
**Template for form pages:**
|
||||||
```heex
|
```heex
|
||||||
|
|
@ -66,15 +66,20 @@ For LiveViews that render an edit or new form (e.g. member, group, role, user, c
|
||||||
</:leading>
|
</:leading>
|
||||||
Page title (e.g. “Edit Member” or “New User”)
|
Page title (e.g. “Edit Member” or “New User”)
|
||||||
<:subtitle>Short explanation.</:subtitle>
|
<:subtitle>Short explanation.</:subtitle>
|
||||||
<:actions>
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
|
|
||||||
{gettext("Save")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
```
|
|
||||||
|
|
||||||
If the `<.header>` is outside the `<.form>`, the submit button must reference the form via the `form` attribute (e.g. `form="user-form"`).
|
<.form for={@form} id="..." phx-change="validate" phx-submit="save">
|
||||||
|
<%!-- form sections and fields --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
|
<.button navigate={return_path(@return_to, @resource)} variant="neutral" type="button">
|
||||||
|
{gettext("Abbrechen")}
|
||||||
|
</.button>
|
||||||
|
<.button type="submit" phx-disable-with={gettext("Speichern...")} variant="primary">
|
||||||
|
{gettext("Speichern")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
</.form>
|
||||||
|
```
|
||||||
|
|
||||||
## 3) Typography (system)
|
## 3) Typography (system)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -577,7 +577,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
WCAG 2.2 AA: Tab list inactive tab text contrast (4.5:1)
|
Member detail tabs (show + edit): inactive vs active contrast
|
||||||
|
WCAG 2.2 AA: inactive tab text contrast (4.5:1)
|
||||||
|
Active tab: visible border (DaisyUI tabs-bordered) and weight so which tab is selected is clear.
|
||||||
============================================ */
|
============================================ */
|
||||||
#member-tablist .tab:not(.tab-active) {
|
#member-tablist .tab:not(.tab-active) {
|
||||||
color: oklch(0.35 0.02 285);
|
color: oklch(0.35 0.02 285);
|
||||||
|
|
@ -586,6 +588,13 @@
|
||||||
color: oklch(0.72 0.02 257);
|
color: oklch(0.72 0.02 257);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Active tab: stronger underline (DaisyUI --tab-border-color) and font weight */
|
||||||
|
#member-tablist .tab.tab-active,
|
||||||
|
#member-tablist .tab[aria-selected="true"] {
|
||||||
|
--tab-border-color: var(--color-base-content);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
WCAG 2.2 AA: Link contrast - primary and accent
|
WCAG 2.2 AA: Link contrast - primary and accent
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ defmodule MvWeb.Components.FieldVisibilityDropdownComponent do
|
||||||
id="field-visibility-menu"
|
id="field-visibility-menu"
|
||||||
icon="hero-adjustments-horizontal"
|
icon="hero-adjustments-horizontal"
|
||||||
button_label={gettext("Show/Hide Columns")}
|
button_label={gettext("Show/Hide Columns")}
|
||||||
|
button_class="btn-secondary"
|
||||||
items={@all_items}
|
items={@all_items}
|
||||||
checkboxes={true}
|
checkboxes={true}
|
||||||
selected={@selected_fields}
|
selected={@selected_fields}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id={@id} class="mb-8 border shadow-xl card border-base-300">
|
<div id={@id} class="mb-8 max-w-2xl border shadow-xl card border-base-300">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center gap-4 mb-4">
|
<div class="flex items-center gap-4 mb-4">
|
||||||
<.button
|
<.button
|
||||||
|
|
@ -98,6 +98,20 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
|
||||||
label={gettext("Show in overview")}
|
label={gettext("Show in overview")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<%!-- Buttons: below all form fields, above Danger zone --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
|
<.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}>
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save Data Field")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%= if @custom_field do %>
|
<%= if @custom_field do %>
|
||||||
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
||||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
||||||
|
|
@ -125,15 +139,6 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="justify-end mt-4 card-actions">
|
|
||||||
<.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}>
|
|
||||||
{gettext("Cancel")}
|
|
||||||
</.button>
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
|
||||||
{gettext("Save Data Field")}
|
|
||||||
</.button>
|
|
||||||
</div>
|
|
||||||
</.form>
|
</.form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ defmodule MvWeb.GroupLive.Form do
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||||
<.form for={@form} id="group-form" phx-change="validate" phx-submit="save">
|
<.form class="max-w-2xl" for={@form} id="group-form" phx-change="validate" phx-submit="save">
|
||||||
<.header>
|
<.header>
|
||||||
<:leading>
|
<:leading>
|
||||||
<.button navigate={return_path(@return_to, @group)} variant="neutral">
|
<.button navigate={return_path(@return_to, @group)} variant="neutral">
|
||||||
|
|
@ -86,11 +86,6 @@ defmodule MvWeb.GroupLive.Form do
|
||||||
</.button>
|
</.button>
|
||||||
</:leading>
|
</:leading>
|
||||||
{@page_title}
|
{@page_title}
|
||||||
<:actions>
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
|
|
||||||
{gettext("Save")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div class="mt-6 space-y-6">
|
<div class="mt-6 space-y-6">
|
||||||
|
|
@ -104,6 +99,20 @@ defmodule MvWeb.GroupLive.Form do
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%!-- Buttons: below all form fields, Abbrechen left (secondary), Speichern right (primary) --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
|
<.button navigate={return_path(@return_to, @group)} variant="neutral" type="button">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save group")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
||||||
<%= if @group && can?(@current_user, :destroy, @group) do %>
|
<%= if @group && can?(@current_user, :destroy, @group) do %>
|
||||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
||||||
|
|
|
||||||
|
|
@ -118,12 +118,13 @@ defmodule MvWeb.GroupLive.Show do
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="group-show-focus-root"
|
id="group-show-focus-root"
|
||||||
class="mt-6 space-y-6"
|
class="mt-6"
|
||||||
phx-hook="FocusRestore"
|
phx-hook="FocusRestore"
|
||||||
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
||||||
>
|
>
|
||||||
|
<div class="max-w-2xl space-y-6">
|
||||||
<%!-- Group Information --%>
|
<%!-- Group Information --%>
|
||||||
<div class="max-w-2xl space-y-6 mb-6">
|
<div class="space-y-6 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold mb-2">{gettext("Description")}</h2>
|
<h2 class="text-lg font-semibold mb-2">{gettext("Description")}</h2>
|
||||||
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
|
||||||
|
|
@ -345,7 +346,7 @@ defmodule MvWeb.GroupLive.Show do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Danger zone: canonical pattern (same as member show) --%>
|
<%!-- Danger zone: same width as form (max-w-2xl) --%>
|
||||||
<%= if can?(@current_user, :destroy, @group) do %>
|
<%= if can?(@current_user, :destroy, @group) do %>
|
||||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
<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">
|
<h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
|
||||||
|
|
@ -371,6 +372,7 @@ defmodule MvWeb.GroupLive.Show do
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Delete Confirmation Modal (WCAG: focus in modal, aria-labelledby) --%>
|
<%!-- Delete Confirmation Modal (WCAG: focus in modal, aria-labelledby) --%>
|
||||||
<%= if assigns[:show_delete_modal] do %>
|
<%= if assigns[:show_delete_modal] do %>
|
||||||
|
|
|
||||||
|
|
@ -166,12 +166,17 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="justify-end mt-4 card-actions">
|
<%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
<.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}>
|
<.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}>
|
||||||
{gettext("Cancel")}
|
{gettext("Cancel")}
|
||||||
</.button>
|
</.button>
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
<.button
|
||||||
{gettext("Save Field")}
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save datafield")}
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
<div class="mt-6 space-y-6">
|
<div class="mt-6 space-y-6">
|
||||||
<%!-- Tab navigation: same styling as member show; only Contact Data tab (no Membership Fees on edit) --%>
|
<%!-- Tab navigation: same styling as member show; only Contact Data tab (no Membership Fees on edit) --%>
|
||||||
<div
|
<div
|
||||||
|
id="member-tablist"
|
||||||
role="tablist"
|
role="tablist"
|
||||||
class="tabs tabs-bordered tabs-lg bg-base-200/60 rounded-box p-1 w-fit"
|
class="tabs tabs-bordered tabs-lg bg-base-200/60 rounded-box p-1 w-fit"
|
||||||
>
|
>
|
||||||
|
|
@ -259,13 +260,17 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
</.form_section>
|
</.form_section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Bottom Action Buttons --%>
|
<%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%>
|
||||||
<div class="flex justify-end gap-4 mt-6">
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
<.button navigate={return_path(@return_to, @member)} variant="neutral" type="button">
|
<.link navigate={return_path(@return_to, @member)} class="btn btn-secondary">
|
||||||
{gettext("Cancel")}
|
{gettext("Cancel")}
|
||||||
</.button>
|
</.link>
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
|
<.button
|
||||||
{gettext("Save Member")}
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
class="btn btn-primary"
|
||||||
|
>
|
||||||
|
{gettext("Save member")}
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,13 @@
|
||||||
query={@query}
|
query={@query}
|
||||||
placeholder={gettext("Search...")}
|
placeholder={gettext("Search...")}
|
||||||
/>
|
/>
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.Components.FieldVisibilityDropdownComponent}
|
||||||
|
id="field-visibility-dropdown"
|
||||||
|
all_fields={@all_available_fields}
|
||||||
|
custom_fields={@all_custom_fields}
|
||||||
|
selected_fields={@user_field_selection}
|
||||||
|
/>
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MvWeb.Components.MemberFilterComponent}
|
module={MvWeb.Components.MemberFilterComponent}
|
||||||
id="member-filter"
|
id="member-filter"
|
||||||
|
|
@ -86,13 +93,6 @@
|
||||||
</span>
|
</span>
|
||||||
</.button>
|
</.button>
|
||||||
</.tooltip>
|
</.tooltip>
|
||||||
<.live_component
|
|
||||||
module={MvWeb.Components.FieldVisibilityDropdownComponent}
|
|
||||||
id="field-visibility-dropdown"
|
|
||||||
all_fields={@all_available_fields}
|
|
||||||
custom_fields={@all_custom_fields}
|
|
||||||
selected_fields={@user_field_selection}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- On desktop (lg:), only the table area scrolls; header and filters stay visible. On mobile, normal flow. --%>
|
<%!-- On desktop (lg:), only the table area scrolls; header and filters stay visible. On mobile, normal flow. --%>
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,6 @@ defmodule MvWeb.MembershipFeeSettingsLive do
|
||||||
<:subtitle>
|
<:subtitle>
|
||||||
{gettext("Configure fee types for membership fees.")}
|
{gettext("Configure fee types for membership fees.")}
|
||||||
</:subtitle>
|
</:subtitle>
|
||||||
<:actions>
|
|
||||||
<.button variant="primary" navigate={~p"/membership_fee_settings/new_fee_type"}>
|
|
||||||
<.icon name="hero-plus" /> {gettext("New Membership Fee Type")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<%!-- One card: default setting + fee types table --%>
|
<%!-- One card: default setting + fee types table --%>
|
||||||
|
|
@ -220,13 +215,6 @@ defmodule MvWeb.MembershipFeeSettingsLive do
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="flex-shrink-0 ml-auto border-l border-base-300 pl-6">
|
|
||||||
<.button type="submit" variant="primary">
|
|
||||||
<.icon name="hero-check" class="size-5" />
|
|
||||||
{gettext("Save Settings")}
|
|
||||||
</.button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="text-sm text-base-content/60 list-disc list-inside space-y-0.5">
|
<ul class="text-sm text-base-content/60 list-disc list-inside space-y-0.5">
|
||||||
|
|
@ -237,12 +225,24 @@ defmodule MvWeb.MembershipFeeSettingsLive do
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<%!-- Save button: below default settings form, no icon (consistent with other Save buttons) --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end">
|
||||||
|
<.button type="submit" phx-disable-with={gettext("Saving...")} variant="primary">
|
||||||
|
{gettext("Save default settings")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<%!-- Fee types table: row click opens edit --%>
|
<%!-- Fee types section: heading and "New" button on same line --%>
|
||||||
|
<div class="flex items-center justify-between gap-4 flex-wrap">
|
||||||
<h2 class="text-lg font-semibold">{gettext("Membership Fee Types")}</h2>
|
<h2 class="text-lg font-semibold">{gettext("Membership Fee Types")}</h2>
|
||||||
|
<.button variant="primary" navigate={~p"/membership_fee_settings/new_fee_type"}>
|
||||||
|
<.icon name="hero-plus" /> {gettext("New Membership Fee Type")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
<.table
|
<.table
|
||||||
id="membership_fee_types"
|
id="membership_fee_types"
|
||||||
rows={@membership_fee_types}
|
rows={@membership_fee_types}
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,6 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do
|
||||||
</.button>
|
</.button>
|
||||||
</:leading>
|
</:leading>
|
||||||
{@page_title}
|
{@page_title}
|
||||||
<:actions>
|
|
||||||
<.button
|
|
||||||
form="membership-fee-type-form"
|
|
||||||
phx-disable-with={gettext("Saving...")}
|
|
||||||
variant="primary"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{gettext("Save")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.form
|
<.form
|
||||||
|
|
@ -139,13 +129,22 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do
|
||||||
rows="3"
|
rows="3"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-4">
|
<%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%>
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
{gettext("Save Membership Fee Type")}
|
<.button
|
||||||
</.button>
|
navigate={return_path(@return_to, @membership_fee_type)}
|
||||||
<.button navigate={return_path(@return_to, @membership_fee_type)} type="button">
|
variant="neutral"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
{gettext("Cancel")}
|
{gettext("Cancel")}
|
||||||
</.button>
|
</.button>
|
||||||
|
<.button
|
||||||
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save membership fee type")}
|
||||||
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,6 @@ defmodule MvWeb.RoleLive.Form do
|
||||||
</.button>
|
</.button>
|
||||||
</:leading>
|
</:leading>
|
||||||
{@page_title}
|
{@page_title}
|
||||||
<:actions>
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
|
|
||||||
{gettext("Save")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div class="mt-6 space-y-6">
|
<div class="mt-6 space-y-6">
|
||||||
|
|
@ -85,6 +80,20 @@ defmodule MvWeb.RoleLive.Form do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
|
<.button navigate={return_path(@return_to, @role)} variant="neutral" type="button">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save role")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,7 @@ defmodule MvWeb.RoleLive.Show do
|
||||||
phx-hook="FocusRestore"
|
phx-hook="FocusRestore"
|
||||||
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
||||||
>
|
>
|
||||||
|
<div class="max-w-xl mt-6 space-y-6">
|
||||||
<.list>
|
<.list>
|
||||||
<:item title={gettext("Name")}>{@role.name}</:item>
|
<:item title={gettext("Name")}>{@role.name}</:item>
|
||||||
<:item title={gettext("Description")}>
|
<:item title={gettext("Description")}>
|
||||||
|
|
@ -272,6 +273,7 @@ defmodule MvWeb.RoleLive.Show do
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Delete Role Confirmation Modal (WCAG: focus moves into modal, keyboard confirm/cancel) --%>
|
<%!-- Delete Role Confirmation Modal (WCAG: focus moves into modal, keyboard confirm/cancel) --%>
|
||||||
<%= if assigns[:show_delete_modal] do %>
|
<%= if assigns[:show_delete_modal] do %>
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,6 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</.button>
|
</.button>
|
||||||
</:leading>
|
</:leading>
|
||||||
{@page_title}
|
{@page_title}
|
||||||
<:actions>
|
|
||||||
<.button
|
|
||||||
form="user-form"
|
|
||||||
phx-disable-with={gettext("Saving...")}
|
|
||||||
variant="primary"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{gettext("Save User")}
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -309,6 +299,20 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%!-- Buttons: below all form fields, above Danger zone --%>
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-4">
|
||||||
|
<.button navigate={return_path(@return_to, @user)} variant="neutral" type="button">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
type="submit"
|
||||||
|
phx-disable-with={gettext("Saving...")}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{gettext("Save user")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
<%!-- Danger zone: canonical pattern (same as member form) --%>
|
||||||
<%= if @user && can?(@current_user, :destroy, @user) && !SystemActor.system_user?(@user) do %>
|
<%= if @user && can?(@current_user, :destroy, @user) && !SystemActor.system_user?(@user) do %>
|
||||||
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
|
||||||
|
|
@ -378,15 +382,6 @@ defmodule MvWeb.UserLive.Form do
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<.button navigate={return_path(@return_to, @user)} variant="neutral">
|
|
||||||
{gettext("Cancel")}
|
|
||||||
</.button>
|
|
||||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
|
||||||
{gettext("Save User")}
|
|
||||||
</.button>
|
|
||||||
</div>
|
|
||||||
</.form>
|
</.form>
|
||||||
</div>
|
</div>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ defmodule MvWeb.UserLive.Show do
|
||||||
phx-hook="FocusRestore"
|
phx-hook="FocusRestore"
|
||||||
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil}
|
||||||
>
|
>
|
||||||
|
<div class="max-w-xl mt-6 space-y-6">
|
||||||
<.list>
|
<.list>
|
||||||
<:item title={gettext("Email")}>{@user.email}</:item>
|
<:item title={gettext("Email")}>{@user.email}</:item>
|
||||||
<:item title={gettext("Role")}>{@user.role.name}</:item>
|
<:item title={gettext("Role")}>{@user.role.name}</:item>
|
||||||
|
|
@ -120,6 +121,7 @@ defmodule MvWeb.UserLive.Show do
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Delete User Confirmation Modal (WCAG: focus in modal, keyboard confirm/cancel) --%>
|
<%!-- Delete User Confirmation Modal (WCAG: focus in modal, keyboard confirm/cancel) --%>
|
||||||
<%= if assigns[:show_delete_modal] do %>
|
<%= if assigns[:show_delete_modal] do %>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue