lib/tailwind_live_components/toggle.ex

defmodule TailwindLiveComponents.Toggle do
  use Phoenix.Component

  alias TailwindLiveComponents.Label

  @doc """
  Renders the toggle

  ```
  <.toggle form={:basket} field={:fruit} label="Fruit" />
  ```

  ## Options

    * `form` - The form identifier
    * `field` - The field name
    * `label` - The text for the generated `<label>` element
    * `detail` - Optional detail shown below the input
    * `error` - Optional error message
    * `theme` - Optional theme to use for Tailwind classes
  """
  def toggle(assigns) do
    input_id = Phoenix.HTML.Form.input_id(assigns.form, assigns.field)
    label_id = input_id <> "-label"
    selected_value = Phoenix.HTML.Form.input_value(assigns.form, assigns.field)

    assigns =
      assigns
      |> assign_new(:input_id, fn -> input_id end)
      |> assign_new(:label_id, fn -> label_id end)
      |> assign_new(:selected_value, fn -> selected_value end)
      |> assign_new(:error, fn -> nil end)
      |> assign_new(:theme, fn -> %TailwindLiveComponents.Theme{} end)

    ~H"""
    <div
      x-data={"{ on: #{@selected_value == "true"} }"}
      class="flex items-center"
    >
      <%= Phoenix.HTML.Form.hidden_input(@form, @field, id: @input_id, "x-model": "on") %>

      <div
        @click="on = !on"
        @keydown.enter.prevent="on = !on"
        @keydown.space.prevent="on = !on"
        class={"#{background(@theme, @selected_value)} relative inline-flex flex-shrink-0 h-8 w-14 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:#{@theme.focus_ring_color}"}
        :class={"{
          '#{@theme.selected_bg_color}': on,
          '#{@theme.light_bg_color}': !(on)
        }"}
        role="switch"
        tabindex="0"
        :aria-checked="on.toString()"
        aria-labelledby={@label_id}
      >
        <span
          aria-hidden="true"
          class={"#{position(@selected_value)} pointer-events-none inline-block h-7 w-7 rounded-full #{@theme.bg_color} shadow transform ring-0 transition ease-in-out duration-200"}
          :class="{
            'translate-x-6': on,
            'translate-x-0': !(on)
          }"
        ></span>
      </div>

      <span class="ml-3 flex flex-col">
        <Label.label
          form={@form}
          field={@field}
          theme={@theme}
          label={@label}
          input_id={@input_id}
          label_id={@label_id}
          error={@error}
        />

        <%= if @detail do %>
          <span class={"#{@theme.light_text_color} text-sm pl-1"}>
            <%= @detail %>
          </span>
        <% end %>
      </span>
    </div>
    """
  end

  defp background(theme, "true"), do: theme.selected_bg_color
  defp background(theme, _), do: theme.light_bg_color

  defp position("true"), do: "translate-x-6"
  defp position(_), do: "translate-x-0"
end