diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index a23381d..f0a9fdb 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -95,9 +95,11 @@ defmodule MvWeb.CoreComponents do <.button>Send! <.button phx-click="go" variant="primary">Send! <.button navigate={~p"/"}>Home + <.button disabled={true}>Disabled """ attr :rest, :global, include: ~w(href navigate patch method) attr :variant, :string, values: ~w(primary) + attr :disabled, :boolean, default: false, doc: "Whether the button is disabled" slot :inner_block, required: true def button(%{rest: rest} = assigns) do @@ -105,14 +107,34 @@ defmodule MvWeb.CoreComponents do assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant])) if rest[:href] || rest[:navigate] || rest[:patch] do + # For links, we can't use disabled attribute, so we use btn-disabled class + # DaisyUI's btn-disabled provides the same styling as :disabled on buttons + link_class = + if assigns[:disabled], + do: ["btn", assigns.class, "btn-disabled"], + else: ["btn", assigns.class] + + # Prevent interaction when disabled + link_attrs = + if assigns[:disabled] do + Map.merge(rest, %{tabindex: "-1", "aria-disabled": "true"}) + else + rest + end + + assigns = + assigns + |> assign(:link_class, link_class) + |> assign(:link_attrs, link_attrs) + ~H""" - <.link class={["btn", @class]} {@rest}> + <.link class={@link_class} {@link_attrs}> {render_slot(@inner_block)} """ else ~H""" - """ diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index fbeb416..8e8d18b 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -3,23 +3,29 @@ {gettext("Members")} <:actions> <.button - :if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} + class="secondary" id="copy-emails-btn" phx-hook="CopyToClipboard" phx-click="copy_emails" + disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} aria-label={gettext("Copy email addresses of selected members")} > <.icon name="hero-clipboard-document" /> - {gettext("Copy emails")} ({Enum.count(@members, &MapSet.member?(@selected_members, &1.id))}) + {gettext("Copy email addresses")} ({Enum.count( + @members, + &MapSet.member?(@selected_members, &1.id) + )}) <.button - :if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} + class="secondary" + id="open-email-btn" href={ "mailto:?bcc=" <> (MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members) |> Enum.join(", ") |> URI.encode()) } + disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} aria-label={gettext("Open email program with BCC recipients")} > <.icon name="hero-envelope" /> diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs index 30b61c7..5b826bd 100644 --- a/test/mv_web/member_live/index_test.exs +++ b/test/mv_web/member_live/index_test.exs @@ -410,14 +410,6 @@ defmodule MvWeb.MemberLive.IndexTest do assert render(view) =~ "1" end - test "copy button is not visible when no members are selected", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members") - - # Ensure no members are selected (default state) - refute has_element?(view, "#copy-emails-btn") - end - test "copy button is visible when members are selected", %{ conn: conn, member1: member1