defmodule Corex.NativeInput do @moduledoc ~S''' Unified native input component based on [Phoenix Core Components](https://hexdocs.pm/phoenix/components.html#corecomponents) Used for text, textarea, date, datetime-local, time, month, week, email, url, tel, search, color, number, password, checkbox, radio, and select. Optional icon slot and shared styling via data-scope="native-input". Uses same data-part structure as Corex components (root, label, control, input, error) for consistency. The icon slot is ignored for textarea, date/time types, color, number, checkbox, radio, and select. For text, email, url, tel, search, and password the icon slot is shown when provided. ## Examples ### Basic ```heex <.native_input type="text" id="name" name="user[name]" class="native-input"> <:label>Name ``` ### With icon ```heex <.native_input type="email" id="email" name="user[email]" class="native-input"> <:label>Email <:icon><.heroicon name="hero-envelope" class="icon" /> ``` ### Textarea (icon slot ignored) ```heex <.native_input type="textarea" id="bio" name="user[bio]" class="native-input"> <:label>Bio ``` ### Checkbox, select, radio ```heex <.native_input type="checkbox" name="user[agree]" class="native-input"> <:label>I agree <.native_input type="select" name="user[role]" options={["Admin": "admin", "User": "user"]} prompt="Choose..." class="native-input"> <:label>Role <.native_input type="radio" name="user[size]" options={["Small": "s", "Medium": "m", "Large": "l"]} value="m" class="native-input"> <:label>Size ``` ### With form field Use `field={f[:key]}` or `field={@form[:key]}` with a form built from an Ecto changeset. Set the form `id` in `to_form/2` and use `id={@form.id}` on `<.form>`. See the Checkbox or NumberInput component docs for the full Controller and Live View pattern. ```heex <.form :let={f} for={@form} id={@form.id}> <.native_input type="email" field={f[:email]} class="native-input"> <:label>Email <:error :let={msg}>{msg} ``` ## Styling Use data attributes to target elements: ```css [data-scope="native-input"][data-part="root"] {} [data-scope="native-input"][data-part="label"] {} [data-scope="native-input"][data-part="control"] {} [data-scope="native-input"][data-part="icon"] {} [data-scope="native-input"][data-part="input"] {} [data-scope="native-input"][data-part="error"] {} ``` The root has `data-no-icon` when no icon is shown (icon slot empty or type is textarea/date/time), so the input uses full-width padding. Use the class `native-input` for default Corex styling. If you wish to use the default Corex styling, you can use the class `native-input` on the component. This requires to install `Mix.Tasks.Corex.Design` first and import the component css file. ```css @import "../corex/main.css"; @import "../corex/tokens/themes/neo/light.css"; @import "../corex/components/native-input.css"; ``` ''' @doc type: :component use Phoenix.Component alias Phoenix.HTML.Form @types ~w(text textarea date datetime-local time month week email url tel search color number password checkbox radio select) attr(:type, :string, required: true, values: @types) attr(:id, :string, required: false) attr(:name, :string, required: false) attr(:value, :any) attr(:invalid, :boolean, default: false) attr(:errors, :list, default: [], doc: "List of error messages to display") attr(:class, :any, default: nil) attr(:prompt, :string, default: nil, doc: "Prompt for select inputs") attr(:options, :list, default: nil, doc: "Options for select and radio. Same format as options_for_select." ) attr(:multiple, :boolean, default: false, doc: "Multiple flag for select inputs") attr(:checked, :boolean, doc: "Checked flag for checkbox. Defaults from value.") attr(:field, Phoenix.HTML.FormField, doc: "A form field struct from the form, e.g. @form[:email]" ) attr(:rest, :global, include: ~w(autocomplete disabled maxlength minlength pattern placeholder readonly required cols rows list form min max step accept) ++ ~w(phx-change phx-blur phx-focus phx-target phx-debounce phx-throttle) ) slot :label, required: false do attr(:class, :string, required: false) end slot :icon, required: false, doc: "Optional. Ignored for textarea, date, datetime-local, time, month, week, color, number, checkbox, radio, select." do attr(:class, :string, required: false) end slot(:error, required: false) do attr(:class, :string, required: false) end def native_input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] assigns |> assign(field: nil, id: assigns[:id] || field.id) |> assign(:errors, Enum.map(errors, &Corex.Gettext.translate_error(&1))) |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) |> assign_new(:value, fn -> field.value end) |> native_input() end @types_ignoring_icon ~w(textarea date datetime-local time month week color number checkbox radio select) def native_input(%{type: "checkbox"} = assigns) do assigns = assigns |> assign_new(:id, fn -> "native-input-#{System.unique_integer([:positive])}" end) |> assign_new(:checked, fn -> Form.normalize_value("checkbox", assigns[:value]) end) ~H"""