lib/petal_components/button.ex

defmodule PetalComponents.Button do
  use Phoenix.Component
  alias PetalComponents.Loading
  import PetalComponents.Link

  # <.button link_type="button|a|live_patch|live_redirect" />
  # prop label, :string
  # prop size, :string
  # prop loading, :boolean, default: false
  # prop disabled, :boolean, default: false
  # slot default
  def button(assigns) do
    assigns = assign_defaults(assigns)

    ~H"""
    <%= if @link_type == "button" do %>
      <button class={@classes} disabled={@disabled} {@button_opts}>
        <%= if @loading do %>
          <Loading.spinner show={true} size_class={get_spinner_classes(@size)} />
        <% end %>

        <%= if @inner_block do %>
          <%= render_slot(@inner_block) %>
        <% else %>
          <%= @label %>
        <% end %>
      </button>

    <% else %>
      <.link to={@to} link_type={@link_type} class={@classes} disabled={@disabled} {@button_opts}>
        <%= if @loading do %>
          <Loading.spinner show={true} size_class={get_spinner_classes(@size)} />
        <% end %>

        <%= if @inner_block do %>
          <%= render_slot(@inner_block) %>
        <% else %>
          <%= @label %>
        <% end %>
      </.link>
    <% end %>
    """
  end

  defp assign_defaults(assigns) do
    assigns
    |> assign_new(:link_type, fn -> "button" end)
    |> assign_new(:inner_block, fn -> nil end)
    |> assign_new(:loading, fn -> false end)
    |> assign_new(:disabled, fn -> false end)
    |> assign_new(:button_opts, fn -> get_button_opts(assigns) end)
    |> assign_new(:classes, fn -> button_classes(assigns) end)
  end

  defp button_classes(opts) do
    opts = %{
      size: opts[:size] || "md",
      variant: opts[:variant] || "solid",
      color: opts[:color] || "primary",
      loading: opts[:loading] || false,
      disabled: opts[:disabled] || false,
      icon: opts[:icon] || false
    }

    color_css = get_color_classes(opts)

    size_css =
      case opts[:size] do
        "xs" -> "text-xs leading-4 px-2.5 py-1.5"
        "sm" -> "text-sm leading-4 px-3 py-2"
        "md" -> "text-sm leading-5 px-4 py-2"
        "lg" -> "text-base leading-6 px-4 py-2"
        "xl" -> "text-base leading-6 px-6 py-3"
      end

    loading_css =
      if opts[:loading] do
        "flex gap-2 items-center whitespace-nowrap disabled cursor-not-allowed"
      else
        ""
      end

    disabled_css =
      if opts[:disabled] do
        "disabled cursor-not-allowed opacity-50"
      else
        ""
      end

    icon_css =
      if opts[:icon] do
        "flex gap-2 items-center whitespace-nowrap"
      else
        ""
      end

    """
      #{color_css}
      #{size_css}
      #{loading_css}
      #{disabled_css}
      #{icon_css}
      font-medium
      shadow-sm
      rounded-md
      inline-flex items-center justify-center
      border
      focus:outline-none
      transition duration-150 ease-in-out
    """
  end

  defp get_button_opts(assigns) do
    Map.drop(assigns, [
      :loading,
      :disabled,
      :link_type,
      :inner_block,
      :size,
      :variant,
      :color,
      :icon,
      :__changed__
    ])
  end

  defp get_color_classes(%{color: "primary", variant: variant}) do
    case variant do
      "outline" ->
        "border-primary-400 hover:border-primary-600 text-primary-600 hover:text-primary-700 active:bg-primary-200 hover:bg-primary-50 focus:border-primary-700 focus:shadow-outline-primary"

      _ ->
        "border-transparent text-white bg-primary-600 active:bg-primary-700 hover:bg-primary-700 focus:bg-primary-700 active:bg-primary-800 focus:shadow-outline-primary"
    end
  end

  defp get_color_classes(%{color: "secondary", variant: variant}) do
    case variant do
      "outline" ->
        "border-secondary-400 hover:border-secondary-600 text-secondary-600 hover:text-secondary-700 active:bg-secondary-200 hover:bg-secondary-50 focus:border-secondary-700 focus:shadow-outline-secondary"

      _ ->
        "border-transparent text-white bg-secondary-600 active:bg-secondary-700 hover:bg-secondary-700 focus:bg-secondary-700 active:bg-secondary-800 focus:shadow-outline-secondary"
    end
  end

  defp get_color_classes(%{color: "white", variant: variant}) do
    case variant do
      "outline" ->
        "border-gray-400 hover:border-gray-500 text-gray-600 hover:text-gray-700 active:bg-gray-100 hover:bg-gray-50 focus:bg-gray-50 focus:border-gray-500 active:border-gray-600"

      _ ->
        "text-gray-700 border-gray-300 hover:text-gray-900 hover:text-gray-900 hover:border-gray-400 hover:bg-gray-50 focus:outline-none focus:border-gray-400 focus:bg-gray-100 focus:text-gray-900 active:border-gray-400 active:bg-gray-200 active:text-black"
    end
  end

  defp get_color_classes(%{color: "success", variant: variant}) do
    case variant do
      "outline" ->
        "border-green-400 hover:border-green-500 text-green-500 hover:text-green-600 active:border-green-600 focus:text-green-600 active:text-green-700 active:bg-green-100 hover:bg-green-50 focus:border-green-500"

      _ ->
        "border-transparent text-white bg-green-500 active:bg-green-700 hover:bg-green-600 active:bg-green-700 focus:bg-green-600"
    end
  end

  defp get_color_classes(%{color: "danger", variant: variant}) do
    case variant do
      "outline" ->
        "border-red-400 hover:border-red-600 text-red-600 hover:text-red-700 active:bg-red-200 active:border-red-700 hover:bg-red-50 focus:border-red-600"

      _ ->
        "border-transparent text-white bg-red-500 active:bg-red-700 hover:bg-red-600 active:bg-green-700 focus:bg-red-600"
    end
  end

  defp get_spinner_classes("xs"), do: "h-3 w-3"
  defp get_spinner_classes("sm"), do: "h-4 w-4"
  defp get_spinner_classes("md"), do: "h-5 w-5"
  defp get_spinner_classes("lg"), do: "h-5 w-5"
  defp get_spinner_classes("xl"), do: "h-6 w-6"
end