defmodule Phoenix.UI.Components.TextInput do
@moduledoc """
Provides text input component.
"""
import Phoenix.HTML.Form, only: [text_input: 3]
import Phoenix.UI.Components.{ErrorTag, FormGroup, HelperText, Heroicon, Label}
use Phoenix.UI, :component
@default_full_width false
@default_icon_color "inherit"
@default_phx_debounce "blur"
@default_type "text"
@default_variant "simple"
@doc """
Renders text input component.
## Examples
```
<.text_input />
```
"""
@spec text_input(Socket.assigns()) :: Rendered.t()
def text_input(raw) do
assigns =
raw
|> assign_new(:"phx-debounce", fn -> @default_phx_debounce end)
|> assign_new(:full_width, fn -> @default_full_width end)
|> assign_new(:type, fn -> @default_type end)
|> assign_new(:variant, fn -> @default_variant end)
|> assign(:invalid?, has_error?(raw))
|> build_text_input_attrs()
|> build_start_icon_attrs()
|> build_end_icon_attrs()
~H"""
<.form_group extend_class={assigns[:extend_class]}>
<%= if assigns[:form] && assigns[:label] == nil do %>
<.label field={assigns[:field]} form={assigns[:form]} />
<% end %>
<%= if assigns[:form] && assigns[:label] do %>
<.label field={assigns[:field]} form={assigns[:form]}>
<%= @label %>
<% end %>
<%= if assigns[:start_icon] do %>
<.heroicon {@start_icon_attrs} />
<% end %>
<%= if assigns[:form] do %>
<%= text_input(@form, @field, @text_input_attrs) %>
<% else %>
<% end %>
<%= if assigns[:end_icon] do %>
<.heroicon {@end_icon_attrs} />
<% end %>
<%= if assigns[:invalid?] do %>
<.error_tag field={@field} form={@form} />
<% end %>
<%= if assigns[:helper_text] && !assigns[:invalid?] do %>
<.helper_text>
<%= @helper_text %>
<% end %>
"""
end
### Text Input Attrs ##########################
defp build_text_input_attrs(assigns) do
class = build_class(~w(
block py-2 px-3 text-slate-700 dark:text-slate-300 text-base outline-none focus:outline-none
placeholder-slate-400 dark:placeholder-slate-400 transition-all ease-in-out duration-200
#{classes(:background, assigns)}
#{classes(:end_icon, assigns)}
#{classes(:rounded, assigns)}
#{classes(:start_icon, assigns)}
#{classes(:variant, assigns)}
#{classes(:width, assigns)}
#{Map.get(assigns, :extend_class)}
))
attrs =
assigns
|> assigns_to_attributes([
:end_icon,
:extend_class,
:field,
:form,
:invalid?,
:start_icon,
:variant
])
|> Keyword.put_new(:class, class)
assign(assigns, :text_input_attrs, attrs)
end
### Start Icon Attrs ##########################
defp build_start_icon_attrs(%{start_icon: icon} = assigns) do
attrs =
icon
|> assigns_to_attributes()
|> Keyword.put_new(:color, @default_icon_color)
assign(assigns, :start_icon_attrs, attrs)
end
defp build_start_icon_attrs(assigns), do: assigns
### End Icon Attrs ##########################
defp build_end_icon_attrs(%{end_icon: icon} = assigns) do
attrs =
icon
|> assigns_to_attributes()
|> Keyword.put_new(:color, @default_icon_color)
assign(assigns, :end_icon_attrs, attrs)
end
defp build_end_icon_attrs(assigns), do: assigns
### CSS Classes ##########################
# Background
defp classes(:background, %{variant: "solid"}) do
"bg-slate-100 dark:bg-slate-800 focus:bg-transparent dark:focus:bg-transparent"
end
defp classes(:background, %{variant: "underline"}), do: "bg-transparent"
defp classes(:background, _assigns), do: "bg-white dark:bg-slate-900"
# End Icon
defp classes(:end_icon, %{end_icon: _}), do: "pr-12"
defp classes(:end_icon, _assigns), do: "pr-3"
# Rounded
defp classes(:rounded, %{variant: "underline"}), do: nil
defp classes(:rounded, %{variant: "unstyled"}), do: nil
defp classes(:rounded, _assigns), do: "rounded-md"
# Start Icon
defp classes(:start_icon, %{start_icon: _}), do: "pl-12"
defp classes(:start_icon, _assigns), do: "pl-3"
# Variant - Simple
defp classes(:variant, %{variant: "simple"}) do
"border border-slate-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-opacity-50 focus:ring-blue-200"
end
# Variant - Solid
defp classes(:variant, %{variant: "solid"}) do
"shadow-sm border border-transparent focus:border-slate-500"
end
# Variant - Underline
defp classes(:variant, %{variant: "underline"}) do
"border-b-2 border-slate-300 shadow-sm focus:border-slate-600"
end
# Variant - Unstyled
defp classes(:variant, %{variant: "unstyled"}) do
"border border-slate-300 shadow-sm ring-0 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
end
# Width
defp classes(:width, %{full_width: true}), do: "w-full"
defp classes(_rule_group, _assigns), do: nil
end