defmodule Shino.UI.Form do @moduledoc """ Provides form related components. > Builds forms in a more semantic way. ## Examples Create a form for collecting post information: ```heex Title <.input field={field} type="text" /> Keep it short and simple. Body <.textarea field={field} /> Please write something meaningful.
保存
``` ## References * [shadcn/ui - Form](https://ui.shadcn.com/docs/components/form) * [@radix-ui/primitives - Form](https://www.radix-ui.com/primitives/docs/components/form) """ use Shino.UI, :component alias Shino.UI.Label alias Shino.UI.Button @doc """ The root contains all the parts of a form. """ attr :for, :any, required: true attr :as, :any, default: nil attr :rest, :global, include: ~w(autocomplete name rel action enctype method novalidate target multipart) slot :inner_block, required: true def root(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}> <%= render_slot(@inner_block, f) %> """ end @doc """ Renders a wrapper for wrapping parts of a field. """ attr :field, Phoenix.HTML.FormField, required: true attr :class, :string, default: nil attr :rest, :global slot :inner_block, required: true def field(assigns) do ~H"""
<%= render_slot(@inner_block, @field) %>
""" end @doc """ Renders a label for a field. """ attr :for, Phoenix.HTML.FormField, required: true attr :class, :string, default: nil attr :rest, :global slot :inner_block, required: true def label(%{for: field} = assigns) do errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] assigns = assign(assigns, :error?, errors != []) ~H""" <%= render_slot(@inner_block) %> """ end @doc """ I don't know how to use it, for now. """ attr :rest, :global slot :inner_block, required: true def control(assigns) do ~H"""
<%= render_slot(@inner_block) %>
""" end @doc """ Renders a description for a field. """ attr :class, :string, default: nil attr :rest, :global slot :inner_block, required: true def description(assigns) do ~H"""

<%= render_slot(@inner_block) %>

""" end @doc """ Renders errors for a field. ## Localization This component accepts a one-arity function as `:translate_error` attr. The argument is `{msg, opts}`. You can implement this function with gettext like this: def translate_error({msg, opts}) do if count = opts[:count] do Gettext.dngettext(DemoWeb.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(DemoWeb.Gettext, "errors", msg, opts) end end """ attr :field, Phoenix.HTML.FormField, required: true attr :class, :string, default: nil attr :translate_error, :any, default: &__MODULE__.translate_error/1 attr :rest, :global slot :inner_block def error(%{field: field} = assigns) do errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] assigns |> assign(:errors, Enum.map(errors, &apply(assigns.translate_error, [&1]))) |> render_error() end # credo:disable-for-next-line # TODO: how to check whether inner_block renders nothing? defp render_error(%{inner_block: inner_block} = assigns) when inner_block != [] do ~H"""

<%= render_slot(@inner_block) %>

""" end defp render_error(assigns) do ~H"""

<%= msg %>

""" end @doc false def translate_error({msg, opts}) do Enum.reduce(opts, msg, fn {key, value}, acc -> String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) end) end @doc """ Renders a submit button for the form. """ attr :type, :string, default: "submit" attr :rest, :global slot :inner_block, required: true def submit(assigns) do ~H""" <%= render_slot(@inner_block) %> """ end end