From f78b906f563722cb94c7c06859851586a8f07e69 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Tue, 16 Jul 2024 16:35:41 +0200 Subject: [PATCH 01/19] Don't depend on Phoenix.HTML.Tag.content_tag in Showcase --- lib/bitstyles_phoenix/showcase.ex | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/bitstyles_phoenix/showcase.ex b/lib/bitstyles_phoenix/showcase.ex index 12a797e..fc40916 100644 --- a/lib/bitstyles_phoenix/showcase.ex +++ b/lib/bitstyles_phoenix/showcase.ex @@ -1,8 +1,7 @@ defmodule BitstylesPhoenix.Showcase do @moduledoc false - import Phoenix.HTML, only: [safe_to_string: 1] - import Phoenix.HTML.Tag, only: [content_tag: 3] + import Phoenix.HTML, only: [safe_to_string: 1, attributes_escape: 1, html_escape: 1] defmacro story(name, doctest_iex_code, doctest_expected_results, opts \\ []) do default_version = BitstylesPhoenix.Bitstyles.default_version() |> String.to_atom() @@ -154,4 +153,13 @@ defmodule BitstylesPhoenix.Showcase do """ end end + + # copy-paste from phoenix_html_helpers because `content_tag` was removed in phoenix_html v4 + # and we don't want to depend on phoenix_html_helpers + defp content_tag(name, content, attrs) when is_list(attrs) do + name = to_string(name) + {:safe, escaped} = html_escape(content) + sorted_attrs = attrs |> Enum.sort() |> attributes_escape() |> elem(1) + {:safe, [?<, name, sorted_attrs, ?>, escaped, ?<, ?/, name, ?>]} + end end From f6062c6fb3af330fab65e52feb2249b307080da6 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Tue, 16 Jul 2024 16:43:49 +0200 Subject: [PATCH 02/19] Don't depend on Phoenix.HTML.Tag.content_tag in Heading --- lib/bitstyles_phoenix/component/heading.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bitstyles_phoenix/component/heading.ex b/lib/bitstyles_phoenix/component/heading.ex index c06e4d0..e8c10b1 100644 --- a/lib/bitstyles_phoenix/component/heading.ex +++ b/lib/bitstyles_phoenix/component/heading.ex @@ -282,9 +282,9 @@ defmodule BitstylesPhoenix.Component.Heading do ~H"""
- <%= Phoenix.HTML.Tag.content_tag @tag, class: classnames(["u-margin-0 u-margin-m-right u-break-text", assigns[:heading_class]]) do %> + <.dynamic_tag name={@tag} class={classnames(["u-margin-0 u-margin-m-right u-break-text", assigns[:heading_class]])}> <%= render_slot(@inner_block) %> - <% end %> + <%= assigns[:title_extra] && render_slot(@title_extra) %>
<%= if assigns[:action] do %> From 5cb5deb3325525311d1a8aaef07faed32a17fce2 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Tue, 16 Jul 2024 17:02:46 +0200 Subject: [PATCH 03/19] Copy-paste "humanize" for default labels --- lib/bitstyles_phoenix/component/form.ex | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/bitstyles_phoenix/component/form.ex b/lib/bitstyles_phoenix/component/form.ex index 7b542ae..36c6ab9 100644 --- a/lib/bitstyles_phoenix/component/form.ex +++ b/lib/bitstyles_phoenix/component/form.ex @@ -755,7 +755,18 @@ defmodule BitstylesPhoenix.Component.Form do """ end - defp default_label(field), do: PhxForm.humanize(field) + defp default_label(field) when is_atom(field), do: default_label(Atom.to_string(field)) + + defp default_label(field) when is_binary(field) do + bin = + if String.ends_with?(field, "_id") do + binary_part(field, 0, byte_size(field) - 3) + else + field + end + + bin |> String.replace("_", " ") |> :string.titlecase() + end defp required_label(%{required: value} = assigns) when value not in [nil, false, "false"] do From 791874525ddf385e92ed7b9295afd961dc10f13e Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Tue, 16 Jul 2024 17:09:15 +0200 Subject: [PATCH 04/19] Run tests with unlocked deps on CI --- .github/workflows/action.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index b9a243f..945f775 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -17,6 +17,21 @@ jobs: - run: mix docs - name: Check if versions showcase mix script finishes run: mix run scripts/generate_version_showcase.ex + + + test-unlocked-deps: + runs-on: ubuntu-latest + name: unit (unlocked deps) + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: '25.1' + elixir-version: '1.14.5' + - run: mix deps.unlock --all + - run: mix deps.get + - run: mix test + demo: runs-on: ubuntu-latest name: demo From 604f631c844e141de9a6902728a9fe1fe3f98709 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Tue, 16 Jul 2024 17:52:34 +0200 Subject: [PATCH 05/19] Don't depend on Phoenix.HTML.Form.label --- lib/bitstyles_phoenix/component/form.ex | 83 ++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/lib/bitstyles_phoenix/component/form.ex b/lib/bitstyles_phoenix/component/form.ex index 36c6ab9..7bad17d 100644 --- a/lib/bitstyles_phoenix/component/form.ex +++ b/lib/bitstyles_phoenix/component/form.ex @@ -84,6 +84,24 @@ defmodule BitstylesPhoenix.Component.Form do ''' ) + story( + "Text field with custom id with label", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} id={"the_name"} field={:name} /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + story( "Text required field with label", ''' @@ -243,7 +261,7 @@ defmodule BitstylesPhoenix.Component.Form do ) story( - "Search field with placholder", + "Search field with placeholder", ''' iex> assigns=%{form: form()} ...> render ~H""" @@ -347,6 +365,9 @@ defmodule BitstylesPhoenix.Component.Form do ''' ) + # TODO: if exported Phoenix.HTML.FormField ... (v4) + # then id can be read from field.id + def ui_input(assigns) do extra = assigns_to_attributes(assigns, @wrapper_assigns_keys ++ [:type]) @@ -651,7 +672,7 @@ defmodule BitstylesPhoenix.Component.Form do end defp wrapper_assigns(assigns) do - Map.take(assigns, @wrapper_assigns_keys ++ [:required]) + Map.take(assigns, @wrapper_assigns_keys ++ [:required, :id]) end @doc """ @@ -700,13 +721,16 @@ defmodule BitstylesPhoenix.Component.Form do classnames([label_opts[:class], {"u-sr-only", assigns[:hidden_label]}]) ) - assigns = assign(assigns, label_text: label_text, label_opts: label_opts) + assigns = + assigns + |> assign_new(:id, fn -> default_id(assigns.form, assigns.field) end) + |> assign(label_text: label_text, label_opts: label_opts) ~H""" - <%= PhxForm.label @form, @field, @label_opts do %> + <.label for={@id} {@label_opts} > <%= @label_text %> <.required_label {assigns_to_attributes(assigns)}/> - <% end %><%= render_slot(@inner_block) %> + <%= render_slot(@inner_block) %> <.ui_errors form={@form} field={@field} /> """ end @@ -742,19 +766,26 @@ defmodule BitstylesPhoenix.Component.Form do def ui_wrapped_input(assigns) do assigns = assigns + |> assign_new(:id, fn -> default_id(assigns.form, assigns.field) end) |> assign_new(:label, fn -> default_label(assigns.field) end) |> assign_new(:label_opts, fn -> [] end) ~H""" - <%= PhxForm.label @form, @field, @label_opts do %> + <.label for={@id} {@label_opts} > <%= render_slot(@inner_block) %> <%= @label %> <.required_label {assigns_to_attributes(assigns)} /> - <% end %> + <.ui_errors form={@form} field={@field} /> """ end + defp default_id(form, field) when is_atom(field), do: default_id(form, Atom.to_string(field)) + + defp default_id(form, field) when is_binary(field) do + Phoenix.HTML.Form.input_id(form, field) + end + defp default_label(field) when is_atom(field), do: default_label(Atom.to_string(field)) defp default_label(field) when is_binary(field) do @@ -790,4 +821,42 @@ defmodule BitstylesPhoenix.Component.Form do """ end + + @doc """ + Renders a label. Direct usage is discouraged in favor of `ui_input` that comes with a label. + + ## Attributes + + - `for` - id of the input the label belongs to. + """ + + story( + "Label", + ''' + iex> assigns=%{} + ...> render ~H""" + ...> <.label for="user_address_city" class="foo bar"> + ...> City + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + def label(assigns) do + extra = assigns_to_attributes(assigns, [:for]) + assigns = assign(assigns, extra: extra) + + ~H""" + + """ + end end From 6b8c535fba2df24a244f80c3faac48eaaf0bffb1 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Wed, 17 Jul 2024 12:56:43 +0200 Subject: [PATCH 06/19] Don't depend on Phoenix.HTML.Form.input --- lib/bitstyles_phoenix.ex | 2 +- lib/bitstyles_phoenix/component/form.ex | 197 ++++++++++++++++----- lib/bitstyles_phoenix/component/sidebar.ex | 2 +- 3 files changed, 150 insertions(+), 51 deletions(-) diff --git a/lib/bitstyles_phoenix.ex b/lib/bitstyles_phoenix.ex index ea62c15..9c677f9 100644 --- a/lib/bitstyles_phoenix.ex +++ b/lib/bitstyles_phoenix.ex @@ -59,7 +59,7 @@ defmodule BitstylesPhoenix do ### Translating error messages - Generated phoenix apps usally come with a helper for [translating error messages](https://github.com/phoenixframework/phoenix/blob/496123b66c03c9764be623d2c32b4af611837eb0/installer/templates/phx_web/views/error_helpers.ex#L23-L46) + Generated phoenix apps usually come with a helper for [translating error messages](https://github.com/phoenixframework/phoenix/blob/496123b66c03c9764be623d2c32b4af611837eb0/installer/templates/phx_web/views/error_helpers.ex#L23-L46) using `gettext`. This helper can be used to translate error messages rendered with `bitstyles_phoenix`. In order to do so you can configure the generated helper or write your own with the MFA configuration. diff --git a/lib/bitstyles_phoenix/component/form.ex b/lib/bitstyles_phoenix/component/form.ex index 7bad17d..c81c4d1 100644 --- a/lib/bitstyles_phoenix/component/form.ex +++ b/lib/bitstyles_phoenix/component/form.ex @@ -7,60 +7,48 @@ defmodule BitstylesPhoenix.Component.Form do @moduledoc """ Components for rendering input elements. + Use `ui_input`, `ui_select`, `ui_textarea` to render the respective inputs together with a label and errors. + Alternatively, this module also provides `input` and `label` functions that can be used to render only an input or only a label. + ## Common attributes - All helpers in this module accept the following attributes. + All `ui_*` components in this module accept the following attributes. - `form` *(required)* - The form to render the input form. - `field` *(required)* - The name of the field for the input. - `label` - The text to be used as label. Defaults to `Phoenix.HTML.Form.humanize/1`. - `label_opts` - The options passed to the label element generated with `Phoenix.HTML.Form.label/4`. - See `Phoenix.HTML.Form.form_for/4` or LiveView `form` component for details on how to render a form. + For details on how to render a form, see: + - `simple_form` core component in a freshly-generated Phoenix app, or + - `Phoenix.Component.form/1`, or + - `Phoenix.HTML.Form.form_for/4` if using phoenix_html v3 or phoenix_html_helpers """ - @input_mapping %{ - color: :color_input, - checkbox: :checkbox, - date: :date_input, - datetime_local: :datetime_local_input, - email: :email_input, - file: :file_input, - number: :number_input, - password: :password_input, - range: :range_input, - search: :search_input, - telephone: :telephone_input, - text: :text_input, - time: :time_input, - url: :url_input - } - @wrapper_assigns_keys [:field, :form, :label, :label_opts, :hidden_label] - @type_doc_table @input_mapping - |> Enum.map(fn {type, helper} -> - "| `:#{type}` | `Phoenix.HTML.Form.#{helper}/3` |\n" - end) - @doc """ Renders various types of `` element, with the associated ` - + """ ''' ) @@ -136,7 +124,7 @@ defmodule BitstylesPhoenix.Component.Form do - + is too short @@ -157,7 +145,7 @@ defmodule BitstylesPhoenix.Component.Form do - +
  • @@ -187,7 +175,7 @@ defmodule BitstylesPhoenix.Component.Form do - + """ ''' ) @@ -237,7 +225,7 @@ defmodule BitstylesPhoenix.Component.Form do * - + """ ''' ) @@ -255,7 +243,7 @@ defmodule BitstylesPhoenix.Component.Form do - + """ ''' ) @@ -278,7 +266,7 @@ defmodule BitstylesPhoenix.Component.Form do - + """ ''' ) @@ -299,7 +287,7 @@ defmodule BitstylesPhoenix.Component.Form do - + """ ''' @@ -336,7 +324,7 @@ defmodule BitstylesPhoenix.Component.Form do """