defmodule <%= @module %>.Components.Input do @moduledoc """ Form-aware Input component integrating with `Phoenix.HTML.FormField`. Ejected from PhiaUI — owns this copy of the source. Customise freely. Renders a labeled text input with optional description text and inline error messages derived from the changeset. Designed for use inside `Phoenix.Component.form/1` or `Phoenix.HTML.Form`. ## Example <.form for={@form}> <.phia_input field={@form[:email]} label="Email" description="We'll never share it." /> <.phia_input field={@form[:password]} type="password" label="Password" phx-debounce="blur" /> ## Error handling Errors are read directly from `field.errors` (a list of `{msg, opts}` tuples) and interpolated via the built-in `translate_error/1`. For Gettext-aware translations, replace `translate_error/1` with: defp translate_error({msg, opts}) do if count = opts[:count] do Gettext.dngettext(<%= @module %>.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(<%= @module %>.Gettext, "errors", msg, opts) end end """ use Phoenix.Component import <%= @module %>.ClassMerger, only: [cn: 1] attr :field, Phoenix.HTML.FormField, required: true, doc: "A `Phoenix.HTML.FormField` struct (e.g., `@form[:email]`)" attr :label, :string, default: nil, doc: "Label text displayed above the input" attr :description, :string, default: nil, doc: "Helper text displayed below the label" attr :type, :string, default: "text", doc: "HTML input type" attr :class, :string, default: nil, doc: "Additional CSS classes for the input element" attr :rest, :global, include: ~w(autocomplete readonly disabled step max min placeholder phx-debounce), doc: "HTML attributes forwarded to the input element" def phia_input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do assigns = assigns |> assign(:errors, Enum.map(field.errors, &translate_error/1)) |> assign_new(:id, fn -> field.id end) |> assign_new(:name, fn -> field.name end) |> assign_new(:value, fn -> field.value end) ~H"""

<%= @description %>

<%= error %>

""" end # --------------------------------------------------------------------------- # Private helpers # --------------------------------------------------------------------------- defp base_class do "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 " <> "text-sm ring-offset-background file:border-0 file:bg-transparent " <> "file:text-sm file:font-medium placeholder:text-muted-foreground " <> "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " <> "focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" end defp error_class([]), do: nil defp error_class(_errors), do: "border-destructive focus-visible:ring-destructive" # Lightweight interpolation — replace with Gettext for i18n (see @moduledoc). defp translate_error({msg, opts}) do Enum.reduce(opts, msg, fn {key, value}, acc when is_binary(acc) -> String.replace(acc, "%{#{key}}", to_string(value)) _other, acc -> acc end) end end