Feature

Reusable toggleable LiveView buttons

Preview

A lot of web apps need some sort of toggle buttons. So I have made a reusable toggleable LiveView buttons. As you can see they can have different kind of content and styling.

lib/phoenix_features_web/live/components/toggle_buttons_simple.ex
defmodule PhoenixFeaturesWeb.Components.ToggleButtonsSimple do
  use PhoenixFeaturesWeb, :live_component

  alias PhoenixFeaturesWeb.Components.ToggleButton

  def mount(socket) do
    {:ok,
      socket
      |> assign(:buttons, %{"toggle-text" => false, "toggle-star" => false, "toggle-lock" => true, "toggle-like" => true})
    }
  end

  def update(assigns, socket) do
    {:ok,
      socket
      |> assign(assigns)
    }
  end

  def handle_event("toggle-active", %{"button" => button}, socket) do
    button_state = !Map.get(socket.assigns.buttons, button, false)

    buttons =
      socket.assigns.buttons
      |> Map.put(button, button_state)

    {:noreply, assign(socket, buttons: buttons)}
  end
end
<div id="<%= @id %>" class="flex justify-between mx-auto max-w-sm my-12">

  <%= live_component @socket, ToggleButton, active: (@buttons["toggle-text"] || false) do %>
    <button
      type="button"
      phx-target="#<%= @id %>"
      phx-click="toggle-active"
      phx-value-button="toggle-text"
      <%= if @active do %>
        class="inline-block text-sm bg-green-100 p-2 rounded shadow text-green-600 border border-green-200 hover:text-green-800 hover:bg-green-100 focus:outline-none"
      <% else %>
        class="inline-block text-sm bg-orange-100 p-2 rounded shadow text-orange-600 border border-orange-200 hover:text-orange-800 hover:bg-orange-100 focus:outline-none"
      <% end %>
    >

      <%= if @active do %>
        Approved
      <% else %>
        Pending
      <% end %>

    </button>
  <% end %>

  <%= live_component @socket, ToggleButton, active: (@buttons["toggle-star"] || false) do %>
    <button
      type="button"
      phx-target="#<%= @id %>"
      phx-click="toggle-active"
      phx-value-button="toggle-star"
      class="inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200 hover:text-gray-800 hover:bg-gray-100 focus:outline-none">

      <%= if @active do %>
        <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
        </svg>
      <% else %>
        <svg class="h-6 w-6" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor">
          <path d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path>
        </svg>
      <% end %>

    </button>
  <% end %>

  <%= live_component @socket, ToggleButton, active: (@buttons["toggle-lock"] || false) do %>
    <button
      type="button"
      phx-target="#<%= @id %>"
      phx-click="toggle-active"
      phx-value-button="toggle-lock"
      class="inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200 hover:text-gray-800 hover:bg-gray-100 focus:outline-none">

      <%= if @active do %>
        <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
          <path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"></path>
        </svg>
      <% else %>
        <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
          <path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path>
        </svg>
      <% end %>

    </button>
  <% end %>

  <%= live_component @socket, ToggleButton, active: (@buttons["toggle-like"] || false) do %>
    <button
      type="button"
      phx-target="#<%= @id %>"
      phx-click="toggle-active"
      phx-value-button="toggle-like"
      class="inline-block text-sm bg-white p-2 rounded shadow text-gray-600 border border-gray-200 hover:text-gray-800 hover:bg-gray-100 focus:outline-none">

      <%= if @active do %>
        <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
          <path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z"></path>
        </svg>
      <% else %>
        <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
          <path d="M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.105-1.79l-.05-.025A4 4 0 0011.055 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z"></path>
        </svg>
      <% end %>

    </button>
  <% end %>
</div>
defmodule PhoenixFeaturesWeb.Components.ToggleButton do
  use PhoenixFeaturesWeb, :live_component

  def render(assigns) do
    ~L"""
    <%= @inner_content.(active: @active) %>
    """
  end
end
defmodule PhoenixFeaturesWeb.Components.CalendarSimpleTest do
  use PhoenixFeaturesWeb.ConnCase
  import Phoenix.LiveViewTest

  describe "ToggleButtonsSimple" do
    test "shows the component", %{conn: conn} do
      {:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "toggle_buttons_simple"))

      assert view |> element("#toggle_buttons_simple") |> has_element?()
    end

    test "click the button toggles the button", %{conn: conn} do
      {:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "toggle_buttons_simple"))

      assert view |> element("[phx-value-button=\"toggle-text\"]") |> render() =~ "Pending"
      refute view |> element("[phx-value-button=\"toggle-text\"]") |> render() =~ "Approved"
      assert view |> element("[phx-value-button=\"toggle-text\"]") |> render_click()
      refute view |> element("[phx-value-button=\"toggle-text\"]") |> render() =~ "Pending"
      assert view |> element("[phx-value-button=\"toggle-text\"]") |> render() =~ "Approved"
    end
  end
end