defmodule Phoenix.UI.Components.Button do
@moduledoc """
Provides button component.
"""
use Phoenix.UI, :component
attr(:class, :string, doc: "Override the classes applied to the component.")
attr(:color, :string,
default: "blue",
doc: "The color of the component.",
values: Theme.colors()
)
attr(:element, :string, default: "button", doc: "The HTML element to use, such as `div`.")
attr(:extend_class, :string, doc: "Extend existing classes applied to the component.")
attr(:full_width, :boolean,
doc: "If true, the component will take up the full width of its container."
)
attr(:rest, :global,
doc: "Arbitrary HTML or phx attributes",
include:
~w(csrf_token disabled download form href hreflang method name navigate patch referrerpolicy rel replace target type value)
)
attr(:size, :string,
default: "md",
doc: "The size of the component.",
values: ["xs", "sm", "md", "lg"]
)
attr(:square, :boolean, default: false, doc: "If true, rounded corners are disabled.")
attr(:variant, :string,
default: "contained",
doc: "The variant to use.",
values: ["contained", "icon", "outlined", "text"]
)
slot(:inner_block, required: true)
@doc """
Renders button component.
## Examples
<.button>
Click me
</.button>
"""
@spec button(Socket.assigns()) :: Rendered.t()
def button(assigns) do
assigns
|> assign_class(~w(
button tracking-wider uppercase outline-none focus:outline-none text-center transition
duration-200 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed
#{classes(:color, assigns)}
#{classes(:full_width, assigns)}
#{classes(:size, assigns)}
#{classes(:square, assigns)}
#{classes(:variant, assigns)}
))
|> render_btn()
end
### Btn Markup ##########################
defp render_btn(%{rest: %{href: _}} = assigns) do
~H"""
<.link class={@class} {@rest}>
<%= render_slot(@inner_block) %>
</.link>
"""
end
defp render_btn(%{rest: %{navigate: _}} = assigns) do
~H"""
<.link class={@class} {@rest}>
<%= render_slot(@inner_block) %>
</.link>
"""
end
defp render_btn(%{rest: %{patch: _}} = assigns) do
~H"""
<.link class={@class} {@rest}>
<%= render_slot(@inner_block) %>
</.link>
"""
end
defp render_btn(assigns) do
~H"""
<.dynamic_tag class={@class} name={@element} {@rest}>
<%= render_slot(@inner_block) %>
</.dynamic_tag>
"""
end
### CSS Classes ##########################
# Color
defp classes(:color, %{color: color, variant: "contained"}) do
"bg-#{color}-500 hover:bg-#{color}-700 disabled:hover:bg-#{color}-500"
end
defp classes(:color, %{color: color, variant: "icon"}) do
"text-#{color}-500 hover:text-#{color}-700 dark:hover:text-#{color}-300 hover:bg-#{color}-500/20 disabled:bg-transparent disabled:text-#{color}-500"
end
defp classes(:color, %{color: color, variant: "outlined"}) do
"text-#{color}-500 border-#{color}-500 hover:text-#{color}-700 hover:border-#{color}-700 hover:bg-#{color}-700/20 disabled:bg-transparent disabled:border-#{color}-500 disabled:text-#{color}-500"
end
defp classes(:color, %{color: color, variant: "text"}) do
"text-#{color}-500 dark:text-#{color}-400 hover:text-#{color}-700 hover:bg-#{color}-500/20 disabled:bg-transparent disabled:text-#{color}-500"
end
# Full Width
defp classes(:full_width, %{full_width: true}), do: "w-full"
# Size
defp classes(:size, %{size: "xs", variant: "icon"}), do: "p-1 text-xs"
defp classes(:size, %{size: "sm", variant: "icon"}), do: "p-2 text-xs"
defp classes(:size, %{variant: "icon"}), do: "p-3 text-xs"
defp classes(:size, %{size: "xs"}), do: "py-1 px-1.5 text-xs"
defp classes(:size, %{size: "sm"}), do: "py-2 px-3 text-xs"
defp classes(:size, %{size: "md"}), do: "py-2 px-4 text-base font-semibold"
defp classes(:size, %{size: "lg"}), do: "py-3 px-5 text-xl font-bold"
# Square
defp classes(:square, %{variant: "icon"}), do: "rounded-full"
defp classes(:square, %{square: false}), do: "rounded"
# Variant
defp classes(:variant, %{variant: "contained"}), do: "text-white shadow"
defp classes(:variant, %{variant: "outlined"}), do: "bg-transparent border border-solid"
defp classes(:variant, %{variant: "text"}), do: "bg-transparent"
defp classes(_rule_group, _assigns), do: nil
end