defmodule PlausibleWeb.Live.Components.Form do @moduledoc """ Generic components stolen from mix phx.new templates """ use Phoenix.Component @doc """ Renders an input with label and error messages. A `Phoenix.HTML.FormField` may be passed as argument, which is used to retrieve the input name, id, and values. Otherwise all attributes may be passed explicitly. ## Examples <.input field={@form[:email]} type="email" /> <.input name="my-input" errors={["oh no!"]} /> """ attr(:id, :any, default: nil) attr(:name, :any) attr(:label, :string, default: nil) attr(:value, :any) attr(:type, :string, default: "text", values: ~w(checkbox color date datetime-local email file hidden month number password range radio search select tel text textarea time url week) ) attr(:field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]" ) attr(:errors, :list, default: []) attr(:checked, :boolean, doc: "the checked flag for checkbox inputs") attr(:prompt, :string, default: nil, doc: "the prompt for select inputs") attr(:options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2") attr(:multiple, :boolean, default: false, doc: "the multiple flag for select inputs") attr(:rest, :global, include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength multiple pattern placeholder readonly required rows size step) ) slot(:inner_block) def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do assigns |> assign(field: nil, id: assigns.id || field.id) |> assign(:errors, Enum.map(field.errors, &translate_error(&1))) |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) |> assign_new(:value, fn -> field.value end) |> input() end # All other inputs text, datetime-local, url, password, etc. are handled here... def input(assigns) do ~H"""
Min <%= @minimum %> characters
""" end defp pop_strength_errors(errors) do Enum.reduce(errors, {[], []}, fn {_, meta} = error, {detected, other_errors} -> cond do meta[:validation] == :required -> {[:required | detected], other_errors} meta[:validation] == :length and meta[:kind] == :min -> {[:length | detected], other_errors} meta[:validation] == :strength -> {[:strength | detected], other_errors} true -> {detected, [error | other_errors]} end end) end attr(:score, :integer, default: 0) attr(:warning, :string, default: "") attr(:suggestions, :list, default: []) def strength_meter(assigns) do color = cond do assigns.score <= 1 -> ["bg-red-500", "dark:bg-red-500"] assigns.score == 2 -> ["bg-red-300", "dark:bg-red-300"] assigns.score == 3 -> ["bg-indigo-300", "dark:bg-indigo-300"] assigns.score >= 4 -> ["bg-indigo-600", "dark:bg-indigo-500"] end feedback = cond do assigns.warning != "" -> assigns.warning <> "." assigns.suggestions != [] -> List.first(assigns.suggestions) true -> nil end assigns = assigns |> assign(:color, color) |> assign(:feedback, feedback) ~H"""Password is too weak
<%= @feedback %>
""" end @doc """ Renders a label. """ attr(:for, :string, default: nil) slot(:inner_block, required: true) def label(assigns) do ~H""" """ end @doc """ Generates a generic error message. """ slot(:inner_block, required: true) def error(assigns) do ~H"""<%= render_slot(@inner_block) %>
""" end 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 end