defmodule <%= @module %> do @moduledoc """ <%= @module %> component renders a toggle field with customizable options. This module provides functionality for creating a toggle switch UI element that can be integrated into forms. It supports various attributes to tailor the appearance and behavior of the toggle, including size, color, and error handling. The toggle field includes support for accessibility features and can display error messages when validation fails. It is designed to be used within Phoenix LiveView applications, enabling dynamic interactions. """ use Phoenix.Component alias Phoenix.HTML.Form @doc """ The `toggle_field` component is a customizable toggle switch input, often used for binary on/off choices like enabling or disabling a feature. ## Examples ```elixir <.toggle_field id="name1" color="danger" label="This is label" /> <.toggle_field id="name2" color="dark" label="This is label" size="extra_large"/> <.toggle_field id="name3" color="warning" label="This is label" size="extra_small" checked={true} /> <.toggle_field id="name4" color="success" label="This is label" size="small"/> ``` """ @doc type: :component attr :id, :string, default: nil, doc: "A unique identifier is used to manage state and interaction" attr :class, :string, default: nil, doc: "Custom CSS class for additional styling" attr :color, :string, default: "primary", doc: "Determines color theme" attr :rounded, :string, default: "full", doc: "Determines border radius" attr :description, :string, default: nil, doc: "Determines a short description" attr :border, :string, default: "extra_small", doc: "Determines border style" attr :labe_class, :string, default: nil, doc: "Determines the labe class" attr :size, :string, default: "medium", doc: "Determines the overall size of the elements, including padding, font size, and other items" attr :checked, :boolean, doc: "" attr :ring, :boolean, default: true, doc: "Determines a ring border on focused input, utilities for creating outline rings with box-shadows." attr :reverse, :boolean, default: false, doc: "Switches the order of the element and label" attr :error_icon, :string, default: nil, doc: "Icon to be displayed alongside error messages" attr :label, :string, default: nil, doc: "Specifies text for the label" attr :errors, :list, default: [], doc: "List of error messages to be displayed" attr :name, :any, doc: "Name of input" attr :value, :any, default: nil attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form" attr :rest, :global, include: ~w(autocomplete disabled form indeterminate readonly required title autofocus), doc: "Global attributes can define defaults which are merged with attributes provided by the caller" @spec toggle_field(map()) :: Phoenix.LiveView.Rendered.t() def toggle_field(%{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, &translate_error(&1))) |> assign_new(:name, fn -> field.name end) |> assign_new(:value, fn -> field.value end) |> toggle_field() end def toggle_field(assigns) do assigns = assign_new(assigns, :checked, fn -> Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) end) ~H"""
<.label for={@id}>{@label}
{@description}
<.error :for={msg <- @errors} icon={@error_icon}>{msg}
""" end def toggle_check(field, %{params: params, data: data} = _form) do if params[Atom.to_string(field)] do Form.normalize_value("checkbox", params[Atom.to_string(field)]) else Map.get(data, field, false) end end @doc type: :component attr :for, :string, default: nil, doc: "Specifies the form which is associated with" attr :class, :string, default: nil, doc: "Custom CSS class for additional styling" slot :inner_block, required: true, doc: "Inner block that renders HEEx content" defp label(assigns) do ~H""" """ end @doc type: :component attr :icon, :string, default: nil, doc: "Icon displayed alongside of an item" slot :inner_block, required: true, doc: "Inner block that renders HEEx content" defp error(assigns) do ~H"""

<.icon :if={!is_nil(@icon)} name={@icon} class="shrink-0" /> {render_slot(@inner_block)}

""" end defp rounded_size("extra_small"), do: "[&_.toggle-field-base]:rounded-sm [&_.toggle-field-circle]:rounded-sm" defp rounded_size("small"), do: "[&_.toggle-field-base]:rounded [&_.toggle-field-circle]:rounded" defp rounded_size("medium"), do: "[&_.toggle-field-base]:rounded-md [&_.toggle-field-circle]:rounded-md" defp rounded_size("large"), do: "[&_.toggle-field-base]:rounded-lg [&_.toggle-field-circle]:rounded-lg" defp rounded_size("extra_large"), do: "[&_.toggle-field-base]:rounded-xl [&_.toggle-field-circle]:rounded-xl" defp rounded_size("full"), do: "[&_.toggle-field-base]:rounded-full [&_.toggle-field-circle]:rounded-full" defp rounded_size(params) when is_binary(params), do: params <%= if is_nil(@size) or "extra_small" in @size do %> defp size_class("extra_small") do [ "[&_.toggle-field-base]:w-10 [&_.toggle-field-base]:h-6 [&_.toggle-field-circle]:size-4" ] end <% end %> <%= if is_nil(@size) or "small" in @size do %> defp size_class("small") do [ "[&_.toggle-field-base]:w-12 [&_.toggle-field-base]:h-7 [&_.toggle-field-circle]:size-5" ] end <% end %> <%= if is_nil(@size) or "medium" in @size do %> defp size_class("medium") do [ "[&_.toggle-field-base]:w-14 [&_.toggle-field-base]:h-8 [&_.toggle-field-circle]:size-6" ] end <% end %> <%= if is_nil(@size) or "large" in @size do %> defp size_class("large") do [ "[&_.toggle-field-base]:w-16 [&_.toggle-field-base]:h-9 [&_.toggle-field-circle]:size-7" ] end <% end %> <%= if is_nil(@size) or "extra_large" in @size do %> defp size_class("extra_large") do [ "[&_.toggle-field-base]:w-[72px] [&_.toggle-field-base]:h-10 [&_.toggle-field-circle]:size-8" ] end <% end %> defp size_class(params) when is_binary(params), do: params <%= if is_nil(@color) or "base" in @color do %> defp color_class("base") do [ "peer-checked:bg-[#e4e4e7] dark:peer-checked:bg-[#27272a]" ] end <% end %> <%= if is_nil(@color) or "white" in @color do %> defp color_class("white") do [ "peer-checked:bg-white" ] end <% end %> <%= if is_nil(@color) or "natural" in @color do %> defp color_class("natural") do [ "peer-checked:bg-[#4B4B4B] dark:peer-checked:bg-[#DDDDDD]" ] end <% end %> <%= if is_nil(@color) or "primary" in @color do %> defp color_class("primary") do [ "peer-checked:bg-[#007F8C] dark:peer-checked:bg-[#01B8CA]" ] end <% end %> <%= if is_nil(@color) or "secondary" in @color do %> defp color_class("secondary") do [ "peer-checked:bg-[#266EF1] dark:peer-checked:bg-[#6DAAFB]" ] end <% end %> <%= if is_nil(@color) or "success" in @color do %> defp color_class("success") do [ "peer-checked:bg-[#0E8345] dark:peer-checked:bg-[#06C167]" ] end <% end %> <%= if is_nil(@color) or "warning" in @color do %> defp color_class("warning") do [ "peer-checked:bg-[#CA8D01] dark:peer-checked:bg-[#FDC034]" ] end <% end %> <%= if is_nil(@color) or "danger" in @color do %> defp color_class("danger") do [ "peer-checked:bg-[#DE1135] dark:peer-checked:bg-[#FC7F79]" ] end <% end %> <%= if is_nil(@color) or "info" in @color do %> defp color_class("info") do [ "peer-checked:bg-[#0B84BA] dark:peer-checked:bg-[#3EB7ED]" ] end <% end %> <%= if is_nil(@color) or "misc" in @color do %> defp color_class("misc") do [ "peer-checked:bg-[#8750C5] dark:peer-checked:bg-[#BA83F9]" ] end <% end %> <%= if is_nil(@color) or "dawn" in @color do %> defp color_class("dawn") do [ "peer-checked:bg-[#A86438] dark:peer-checked:bg-[#DB976B]" ] end <% end %> <%= if is_nil(@color) or "silver" in @color do %> defp color_class("silver") do [ "peer-checked:bg-[#868686] dark:peer-checked:bg-[#A6A6A6]" ] end <% end %> <%= if is_nil(@color) or "dark" in @color do %> defp color_class("dark") do [ "peer-checked:bg-[#282828]" ] end <% end %> defp color_class(params) when is_binary(params), do: params defp translate_error({msg, opts}) do # When using gettext, we typically pass the strings we want # to translate as a static argument: # # # Translate the number of files with plural rules # dngettext("errors", "1 file", "%{count} files", count) # # However the error messages in our forms and APIs are generated # dynamically, so we need to translate them by calling Gettext # with our gettext backend as first argument. Translations are # available in the errors.po file (as we use the "errors" domain). if count = opts[:count] do Gettext.dngettext(<%= inspect(@web_module) %>.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(<%= inspect(@web_module) %>.Gettext, "errors", msg, opts) end end attr :name, :string, required: true, doc: "Specifies the name of the element" attr :class, :any, default: nil, doc: "Custom CSS class for additional styling" defp icon(%{name: "hero-" <> _, class: class} = assigns) when is_list(class) do ~H""" """ end defp icon(%{name: "hero-" <> _} = assigns) do ~H""" """ end end