defmodule PineUi.TextInput do
@moduledoc """
Provides text input components for forms and user input.
The TextInput module offers three main variants:
- `basic/1` - Standard text input with optional prefix/suffix
- `with_icon/1` - Text input with an icon
- `textarea/1` - Multiline text input
All text input components support labels, hints, error messages, and
are fully integrated with Phoenix LiveView.
## Examples
..."}
placeholder="Search..."
/>
## Accessibility
All text input components include proper labeling, focus styles, and
ARIA attributes for accessibility.
"""
use Phoenix.Component
@doc """
Renders a basic text input component.
This component supports various input types and can include prefix/suffix
text alongside the input.
## Examples
<.basic id="username" label="Username" />
<.basic
id="price"
label="Price"
type="number"
prefix="$"
hint="Enter amount in dollars"
/>
<.basic
id="email"
label="Email"
type="email"
error="Please enter a valid email address"
required={true}
/>
## Options
* `:id` - The ID for the input element (required)
* `:name` - The name attribute (optional, defaults to ID)
* `:label` - Label text (optional)
* `:type` - Input type (optional, defaults to "text")
* `:value` - Current input value (optional)
* `:placeholder` - Placeholder text (optional)
* `:prefix` - Text to display before the input (optional)
* `:suffix` - Text to display after the input (optional)
* `:hint` - Help text displayed below the input (optional)
* `:error` - Error message displayed below the input (optional)
* `:required` - Whether the field is required (optional, defaults to false)
* `:disabled` - Whether the field is disabled (optional, defaults to false)
* `:readonly` - Whether the field is read only (optional, defaults to false)
* `:autofocus` - Whether the field should autofocus (optional, defaults to false)
* `:phx_change` - Phoenix change event name (optional)
* `:phx_blur` - Phoenix blur event name (optional)
* `:phx_focus` - Phoenix focus event name (optional)
* `:phx_debounce` - Phoenix debounce setting (optional)
* `:class` - Additional CSS classes for the input element (optional)
* `:container_class` - CSS classes for the container div (optional)
"""
def basic(assigns) do
~H"""
<%= if Map.get(assigns, :label, nil) do %>
<% end %>
<%= if Map.get(assigns, :prefix, nil) do %>
<%= @prefix %>
<% end %>
<%= if Map.get(assigns, :suffix, nil) do %>
<%= @suffix %>
<% end %>
<%= if Map.get(assigns, :hint, nil) do %>
<%= @hint %>
<% end %>
<%= if Map.get(assigns, :error, nil) do %>
<%= @error %>
<% end %>
"""
end
defp get_input_classes(assigns) do
base_classes =
"block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
error_classes =
if Map.get(assigns, :error, nil),
do:
"border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500",
else: ""
prefix_classes = if Map.get(assigns, :prefix, nil), do: "pl-9", else: ""
suffix_classes = if Map.get(assigns, :suffix, nil), do: "pr-9", else: ""
extra_classes = Map.get(assigns, :class, "")
[base_classes, error_classes, prefix_classes, suffix_classes, extra_classes]
|> Enum.filter(&(&1 != ""))
|> Enum.join(" ")
end
@doc """
Renders a text input with an icon on the left side.
This component is ideal for search inputs or any field that benefits from
a visual indicator of its purpose.
## Examples
<.with_icon
id="search"
icon={~H""}
placeholder="Search..."
/>
<.with_icon
id="email"
icon={~H""}
label="Email"
error={@form.errors[:email]}
/>
## Options
* `:id` - The ID for the input element (required)
* `:name` - The name attribute (optional, defaults to ID)
* `:label` - Label text (optional)
* `:icon` - The icon to display (required, as HEEx or HTML string)
* `:type` - Input type (optional, defaults to "text")
* `:value` - Current input value (optional)
* `:placeholder` - Placeholder text (optional)
* `:hint` - Help text displayed below the input (optional)
* `:error` - Error message displayed below the input (optional)
* `:required` - Whether the field is required (optional, defaults to false)
* `:disabled` - Whether the field is disabled (optional, defaults to false)
* `:readonly` - Whether the field is read only (optional, defaults to false)
* `:autofocus` - Whether the field should autofocus (optional, defaults to false)
* `:phx_change` - Phoenix change event name (optional)
* `:phx_blur` - Phoenix blur event name (optional)
* `:phx_focus` - Phoenix focus event name (optional)
* `:phx_debounce` - Phoenix debounce setting (optional)
* `:class` - Additional CSS classes for the input element (optional)
* `:container_class` - CSS classes for the container div (optional)
"""
def with_icon(assigns) do
~H"""
<%= if Map.get(assigns, :label, nil) do %>
<% end %>
<%= @icon %>
<%= if Map.get(assigns, :hint, nil) do %>
<%= @hint %>
<% end %>
<%= if Map.get(assigns, :error, nil) do %>
<%= @error %>
<% end %>
"""
end
@doc """
Renders a textarea component for multiline text input.
## Examples
<.textarea
id="description"
label="Description"
rows={4}
/>
<.textarea
id="feedback"
label="Your Feedback"
placeholder="Tell us what you think..."
hint="Your feedback helps us improve our service"
rows={8}
/>
## Options
* `:id` - The ID for the textarea element (required)
* `:name` - The name attribute (optional, defaults to ID)
* `:label` - Label text (optional)
* `:value` - Current textarea value (optional)
* `:placeholder` - Placeholder text (optional)
* `:rows` - Number of visible rows (optional, defaults to 4)
* `:hint` - Help text displayed below the textarea (optional)
* `:error` - Error message displayed below the textarea (optional)
* `:required` - Whether the field is required (optional, defaults to false)
* `:disabled` - Whether the field is disabled (optional, defaults to false)
* `:readonly` - Whether the field is read only (optional, defaults to false)
* `:autofocus` - Whether the field should autofocus (optional, defaults to false)
* `:phx_change` - Phoenix change event name (optional)
* `:phx_blur` - Phoenix blur event name (optional)
* `:phx_debounce` - Phoenix debounce setting (optional)
* `:class` - Additional CSS classes for the textarea element (optional)
* `:container_class` - CSS classes for the container div (optional)
"""
def textarea(assigns) do
~H"""
<%= if Map.get(assigns, :label, nil) do %>
<% end %>
<%= if Map.get(assigns, :hint, nil) do %>
<%= @hint %>
<% end %>
<%= if Map.get(assigns, :error, nil) do %>