From 2ef9cbdca541b6a46cf6f5bd7d8e831e6a6b93e6 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Sat, 16 Mar 2024 18:40:23 +0100 Subject: [PATCH 01/15] Render static pages with showcases for all bitstyles versions --- .gitignore | 1 + lib/bitstyles_phoenix/bitstyles.ex | 4 +- lib/bitstyles_phoenix/component/error.ex | 18 +- lib/bitstyles_phoenix/component/form.ex | 48 +-- lib/bitstyles_phoenix/helper/abcdef.ex | 31 ++ lib/bitstyles_phoenix/helper/test_fixtures.ex | 16 + lib/mix/tasks/generate_versions_showcase.ex | 316 ++++++++++++++++++ mix.exs | 2 +- .../component/error_test.exs | 10 +- .../bitstyles_phoenix/component/form_test.exs | 12 +- test/support/component_case.ex | 24 +- 11 files changed, 404 insertions(+), 78 deletions(-) create mode 100644 lib/bitstyles_phoenix/helper/abcdef.ex create mode 100644 lib/bitstyles_phoenix/helper/test_fixtures.ex create mode 100644 lib/mix/tasks/generate_versions_showcase.ex diff --git a/.gitignore b/.gitignore index 863a391..afb2c71 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ erl_crash.dump # Ignore package tarball (built via "mix hex.build"). bitstyles_phoenix-*.tar +/versions_showcase diff --git a/lib/bitstyles_phoenix/bitstyles.ex b/lib/bitstyles_phoenix/bitstyles.ex index 94f8451..05474be 100644 --- a/lib/bitstyles_phoenix/bitstyles.ex +++ b/lib/bitstyles_phoenix/bitstyles.ex @@ -1,11 +1,13 @@ defmodule BitstylesPhoenix.Bitstyles do @moduledoc false + # TODO: write a document about how to do bitstyles upgrades in this repo + @default_version "4.3.0" @cdn_url "https://cdn.jsdelivr.net/npm/bitstyles" def cdn_url do - "#{@cdn_url}@#{@default_version}" + "#{@cdn_url}@#{version()}" end @doc """ diff --git a/lib/bitstyles_phoenix/component/error.ex b/lib/bitstyles_phoenix/component/error.ex index d330f4d..758ea51 100644 --- a/lib/bitstyles_phoenix/component/error.ex +++ b/lib/bitstyles_phoenix/component/error.ex @@ -24,32 +24,32 @@ defmodule BitstylesPhoenix.Component.Error do """ story("A single error", ''' - iex> assigns = %{form: @form_with_errors} + iex> assigns = %{form: form_with_errors()} ...> render ~H""" - ...> <.ui_errors form={@form} field={:single} /> + ...> <.ui_errors form={@form} field={:name} /> ...> """ """ - + is too short """ ''') story("Multiple errors", ''' - iex> assigns = %{form: @form_with_errors} + iex> assigns = %{form: form_with_errors()} ...> render ~H""" - ...> <.ui_errors form={@form} field={:multiple} /> + ...> <.ui_errors form={@form} field={:email} /> ...> """ """ diff --git a/lib/bitstyles_phoenix/component/form.ex b/lib/bitstyles_phoenix/component/form.ex index b9a3ce7..d9fdf3a 100644 --- a/lib/bitstyles_phoenix/component/form.ex +++ b/lib/bitstyles_phoenix/component/form.ex @@ -67,7 +67,7 @@ defmodule BitstylesPhoenix.Component.Form do """ story("Text field with label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:name} /> ...> """ @@ -80,7 +80,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text required field with label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:name} required/> ...> """ @@ -96,7 +96,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text field with error", ''' - iex> assigns=%{form: @form_with_errors} + iex> assigns=%{form: form_with_errors()} ...> render ~H""" ...> <.ui_input form={@form} field={:name} /> ...> """ @@ -112,7 +112,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text field with multiple errors", ''' - iex> assigns=%{form: @form_with_errors} + iex> assigns=%{form: form_with_errors()} ...> render ~H""" ...> <.ui_input form={@form} field={:email} /> ...> """ @@ -137,7 +137,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text field with hidden label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:name} hidden_label={true} /> ...> """ @@ -150,7 +150,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text field with label (without maxlength)", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:name} maxlength={false}/> ...> """ @@ -163,7 +163,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Text field with options", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input ...> form={@form} @@ -190,7 +190,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Email field with label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:email} type={:email} /> ...> """ @@ -203,7 +203,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Search field with placholder", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input ...> form={@form} @@ -221,7 +221,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("File field for pdfs", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> ...> <.ui_input form={form} field={:file} type={:file} accept="application/pdf" /> @@ -238,7 +238,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Checkbox", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:accept} type={:checkbox} /> ...> """ @@ -252,7 +252,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Checkbox required", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:accept} type={:checkbox} required/> ...> """ @@ -269,7 +269,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Checkbox with label class", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_input form={@form} field={:accept} type={:checkbox} label_opts={[class: "extra"]}/> ...> """ @@ -331,7 +331,7 @@ defmodule BitstylesPhoenix.Component.Form do """ story("Textarea", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_textarea form={@form} field={:about_me} /> ...> """ @@ -345,7 +345,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Textarea", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_textarea form={@form} field={:about_me} required/> ...> """ @@ -362,7 +362,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Textarea with options", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_textarea ...> form={@form} @@ -384,7 +384,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Textarea with hidden label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_textarea form={@form} field={:address} hidden_label/> ...> """ @@ -398,7 +398,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Textarea with error", ''' - iex> assigns=%{form: @form_with_errors} + iex> assigns=%{form: form_with_errors()} ...> render ~H""" ...> <.ui_textarea form={@form} field={:name} /> ...> """ @@ -443,7 +443,7 @@ defmodule BitstylesPhoenix.Component.Form do """ story("Select box", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_select form={@form} field={:week} options={1..2} /> ...> """ @@ -463,7 +463,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Select box required", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_select form={@form} field={:week} options={1..2} required /> ...> """ @@ -486,7 +486,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Select box without label", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_select form={@form} field={:week} options={1..2} hidden_label/> ...> """ @@ -506,7 +506,7 @@ defmodule BitstylesPhoenix.Component.Form do ''') story("Select box with options", ''' - iex> assigns=%{form: @form, options: [{"Ducks", "ducks"}, {"Cats", "cats"}]} + iex> assigns=%{form: form(), options: [{"Ducks", "ducks"}, {"Cats", "cats"}]} ...> render ~H""" ...> <.ui_select form={@form} field={:preference} options={@options} label="What do you like best?" label_opts={[class: "extra"]}/> ...> """ @@ -554,7 +554,7 @@ defmodule BitstylesPhoenix.Component.Form do """ story("Custom inputs", ''' - iex> assigns=%{form: @form_with_errors} + iex> assigns=%{form: form_with_errors()} ...> render ~H""" ...> <.ui_unwrapped_input form={@form} field={:name} label="Custom"> ...> Custom content @@ -605,7 +605,7 @@ defmodule BitstylesPhoenix.Component.Form do """ story("Custom wrapped inputs", ''' - iex> assigns=%{form: @form} + iex> assigns=%{form: form()} ...> render ~H""" ...> <.ui_wrapped_input form={@form} field={:name} label="Current name"> ...> diff --git a/lib/bitstyles_phoenix/helper/abcdef.ex b/lib/bitstyles_phoenix/helper/abcdef.ex new file mode 100644 index 0000000..2421947 --- /dev/null +++ b/lib/bitstyles_phoenix/helper/abcdef.ex @@ -0,0 +1,31 @@ +# TODO: rename me +defmodule BitstylesPhoenix.Helper.Abcdef do + defmacro __using__(_) do + quote do + use BitstylesPhoenix, js_mode: :none + alias Phoenix.HTML.Safe + import Phoenix.Component, only: [sigil_H: 2] + import Phoenix.HTML, only: [safe_to_string: 1] + import BitstylesPhoenix.Helper.TestFixtures + + defp render(%Phoenix.LiveView.Rendered{} = template) do + template + |> Safe.to_iodata() + |> IO.iodata_to_binary() + |> prettify_html() + end + + defp render(template) do + template + |> safe_to_string() + |> prettify_html() + end + + defp prettify_html(html) do + html + |> Floki.parse_fragment!() + |> Floki.raw_html(pretty: true) + end + end + end +end diff --git a/lib/bitstyles_phoenix/helper/test_fixtures.ex b/lib/bitstyles_phoenix/helper/test_fixtures.ex new file mode 100644 index 0000000..ec68b0d --- /dev/null +++ b/lib/bitstyles_phoenix/helper/test_fixtures.ex @@ -0,0 +1,16 @@ +defmodule BitstylesPhoenix.Helper.TestFixtures do + def form() do + Phoenix.Component.to_form(%{}, as: :user) + end + + def form_with_errors do + Phoenix.Component.to_form(%{}, + as: :user, + errors: [ + name: {"is too short", []}, + email: {"is invalid", []}, + email: "must end with @bitcrowd.net" + ] + ) + end +end diff --git a/lib/mix/tasks/generate_versions_showcase.ex b/lib/mix/tasks/generate_versions_showcase.ex new file mode 100644 index 0000000..0572cfe --- /dev/null +++ b/lib/mix/tasks/generate_versions_showcase.ex @@ -0,0 +1,316 @@ +# TODO: hide from library users +defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do + use Mix.Task + + import Phoenix.HTML, only: [safe_to_string: 1] + import Phoenix.HTML.Tag, only: [content_tag: 3] + + # TODO + @shortdoc "TODO" + @moduledoc @shortdoc + + # TODO: all versions + @all_supported_bitstyles_versions [ + "4.3.0", + "4.2.0", + "4.1.0", + "4.0.0", + "3.1.0", + "3.0.0", + "2.0.0", + "1.5.0", + "1.4.0", + "1.3.0" + ] + @doctest_entries ["iex>", "...>"] + @dir_name "versions_showcase" + + @impl true + def run(_args) do + # TODO: accept args and --only and --exclude version options + components = get_components() + + versions = @all_supported_bitstyles_versions + File.mkdir_p(@dir_name) + write_index_html(versions) + + IO.puts("Generating versions") + + versions + |> Enum.each(fn version -> + IO.puts(version) + + write_version_html(version, components) + + components + |> Enum.map(fn component -> + write_component_html(versions, version, component) + end) + end) + + IO.puts(IO.ANSI.green() <> "Done!" <> IO.ANSI.reset()) + end + + defp get_components() do + "lib/bitstyles_phoenix/component/*.ex" + |> Path.wildcard() + |> Enum.map(fn path -> + string = File.read!(path) + ast = Code.string_to_quoted!(string) + component_name = get_component_name(ast) + stories = get_stories(ast) + %{path: Path.expand(path), name: component_name, stories: stories} + end) + end + + defp get_component_name(ast) do + {_, component_name} = + Macro.prewalk(ast, nil, fn node, acc -> + if acc do + {node, acc} + else + case node do + {:defmodule, _meta, + [{:__aliases__, _, [:BitstylesPhoenix, :Component, component_name]}, _]} -> + {node, component_name} + + _ -> + {node, acc} + end + end + end) + + component_name + end + + defp get_stories(ast) do + {_, stories} = + Macro.prewalk(ast, [], fn node, acc -> + case node do + {:story, meta, args} -> + name = Enum.at(args, 0) + doctest = Enum.at(args, 1) + opts = Enum.at(args, 2) + + code = + doctest + |> to_string() + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.filter(fn line -> + Enum.any?(@doctest_entries, &String.starts_with?(line, &1)) + end) + |> Enum.map(fn line -> + Enum.reduce(@doctest_entries, String.trim(line), &String.trim_leading(&2, &1)) + end) + |> Enum.join("\n") + + {node, + [%{name: name, code: code, opts: opts || [], line: Keyword.get(meta, :line)} | acc]} + + _ -> + {node, acc} + end + end) + + Enum.reverse(stories) + end + + defp write_index_html(versions) do + path = Path.join(@dir_name, "index.html") + title = "BitstylesPhoenix" + + versions_links = + Enum.map(versions, fn version -> + ~s(
  • #{version}
  • ) + end) + + body = """ +

    #{title}

    +

    Bitstyles versions

    +
      + #{versions_links} +
    + """ + + File.write!(path, html(title, body)) + end + + defp write_version_html(version, components) do + path = Path.join(@dir_name, "v#{slugify_version(version)}.html") + title = "BitstylesPhoenix with Bitstyles v#{version}" + + component_links = + Enum.map(components, fn component -> + ~s(
  • #{component.name}
  • ) + end) + + body = """ +

    Back +

    #{title}

    +

    Components

    +
      + #{component_links} +
    + """ + + File.write!(path, html(title, body)) + end + + defp write_component_html(versions, version, component) do + dir_path = Path.join(@dir_name, "v#{slugify_version(version)}") + File.mkdir_p(dir_path) + path = Path.join(@dir_name, "v#{slugify_version(version)}/c#{component.name}.html") + title = "Bitstyles v#{version} #{component.name}" + + stories = + Enum.map(component.stories, fn story -> + """ +

    #{story.name}

    + #{render_story_with_version(component, story, version)} + """ + end) + + body = """ +

    Back

    +

    #{version_select(versions, version)}

    +

    BitstylesPhoenix with Bitstyles v#{version}

    +

    #{component.name}

    + #{stories} + """ + + File.write!(path, html(title, body)) + end + + defp html(title, body) do + """ + + + + #{title} + + + + #{body} + + + """ + end + + defp slugify_version(version) do + String.replace(version, ".", "_") + end + + defp render_story_with_version(component, story, version) do + Application.put_env(:bitstyles_phoenix, :bitstyles_version, version) + + extra_html = Keyword.get(story.opts, :extra_html, "") + transparent = Keyword.get(story.opts, :transparent, true) + + code = """ + defmodule BitstylesPhoenix.Component.#{component.name}.Showcase.V#{slugify_version(version)}.L#{story.line} do + use BitstylesPhoenix.Helper.Abcdef + use BitstylesPhoenix.Component + + def render_html do + #{story.code} + end + end + + BitstylesPhoenix.Component.#{component.name}.Showcase.V#{slugify_version(version)}.L#{story.line}.render_html() + """ + + {result, _} = Code.eval_string(code) + dist = BitstylesPhoenix.Bitstyles.cdn_url() + + style = + if transparent do + """ + html{ \ + background-color: transparent !important; \ + } \ + \ + @media (prefers-color-scheme: dark) { \ + body {color: #fff; } \ + } \ + """ + else + "" + end + + iframe_opts = + [ + srcdoc: + ~s(#{Enum.join([extra_html, result]) |> String.replace("\n", "")}), + style: "", + allowtransparency: if(transparent, do: "true", else: "false") + ] + |> Keyword.merge(iframe_style_opts(story.opts)) + + iframe = + if dist do + safe_to_string(content_tag(:iframe, "", iframe_opts)) + else + "" + end + + html_code = safe_to_string(content_tag(:code, result, [])) + + """ +

    Source

    +
    #{component.path}:#{story.line}
    +

    Output

    +
    #{html_code}
    +

    Preview

    +
    + #{iframe} +
    + """ + end + + @default_iframe_style """ + height:1px; \ + border:none; \ + overflow:hidden; \ + margin: 1em; \ + """ + + defp iframe_style_opts(opts) do + width = Keyword.get(opts, :width, "auto") + + Keyword.get(opts, :height) + |> case do + nil -> + [ + style: "#{@default_iframe_style}width: #{width}", + # https://stackoverflow.com/questions/819416/adjust-width-and-height-of-iframe-to-fit-with-content-in-it + onload: """ + javascript:(function(o) { \ + o.style.height=(o.contentWindow.document.body.scrollHeight)+"px"; \ + }(this)); \ + """ + ] + + height -> + [style: "#{@default_iframe_style}height: #{height}; width: #{width};"] + end + end + + defp version_select(versions, current_version) do + options = + versions + |> Enum.map(fn version -> + "" + end) + + """ + + + """ + end +end diff --git a/mix.exs b/mix.exs index 5367b8c..ce06faf 100644 --- a/mix.exs +++ b/mix.exs @@ -53,7 +53,7 @@ defmodule BitstylesPhoenix.MixProject do [ {:jason, "~> 1.0"}, {:phoenix_live_view, "~>0.18.8 or ~> 0.19.0 or ~> 0.20.0"}, - {:floki, "~> 0.32.0", only: :test}, + {:floki, "~> 0.32.0", only: [:test, :dev]}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:credo, ">= 0.0.0", only: :dev, runtime: false} ] diff --git a/test/bitstyles_phoenix/component/error_test.exs b/test/bitstyles_phoenix/component/error_test.exs index c6b4e70..c5c8db3 100644 --- a/test/bitstyles_phoenix/component/error_test.exs +++ b/test/bitstyles_phoenix/component/error_test.exs @@ -1,14 +1,6 @@ defmodule BitstylesPhoenix.Component.ErrorTest do use BitstylesPhoenix.ComponentCase, async: true - - @form_with_errors Phoenix.Component.to_form(%{}, - as: :user, - errors: [ - single: {"is too short", []}, - multiple: {"is simply bad", []}, - multiple: "not fun" - ] - ) + import BitstylesPhoenix.Helper.TestFixtures doctest BitstylesPhoenix.Component.Error end diff --git a/test/bitstyles_phoenix/component/form_test.exs b/test/bitstyles_phoenix/component/form_test.exs index 5bd1a1a..90b13f7 100644 --- a/test/bitstyles_phoenix/component/form_test.exs +++ b/test/bitstyles_phoenix/component/form_test.exs @@ -1,16 +1,6 @@ defmodule BitstylesPhoenix.Component.FormTest do use BitstylesPhoenix.ComponentCase, async: true + import BitstylesPhoenix.Helper.TestFixtures import Phoenix.Component - - @form Phoenix.Component.to_form(%{}, as: :user) - @form_with_errors Phoenix.Component.to_form(%{}, - as: :user, - errors: [ - name: {"is too short", []}, - email: {"is invalid", []}, - email: "must end with @bitcrowd.net" - ] - ) - doctest BitstylesPhoenix.Component.Form end diff --git a/test/support/component_case.ex b/test/support/component_case.ex index 7490caa..224dc36 100644 --- a/test/support/component_case.ex +++ b/test/support/component_case.ex @@ -5,29 +5,7 @@ defmodule BitstylesPhoenix.ComponentCase do using do quote do - use BitstylesPhoenix, js_mode: :none - alias Phoenix.HTML.Safe - import Phoenix.Component, only: [sigil_H: 2] - import Phoenix.HTML, only: [safe_to_string: 1] - - defp render(%Phoenix.LiveView.Rendered{} = template) do - template - |> Safe.to_iodata() - |> IO.iodata_to_binary() - |> prettify_html() - end - - defp render(template) do - template - |> safe_to_string() - |> prettify_html() - end - - defp prettify_html(html) do - html - |> Floki.parse_fragment!() - |> Floki.raw_html(pretty: true) - end + use BitstylesPhoenix.Helper.Abcdef end end end From 07b1da1a5562fd6fe97d665565458e7fecec598c Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 17:23:14 +0100 Subject: [PATCH 02/15] Add an --only flag --- lib/mix/tasks/generate_versions_showcase.ex | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/generate_versions_showcase.ex b/lib/mix/tasks/generate_versions_showcase.ex index 0572cfe..f463a6a 100644 --- a/lib/mix/tasks/generate_versions_showcase.ex +++ b/lib/mix/tasks/generate_versions_showcase.ex @@ -26,11 +26,19 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do @dir_name "versions_showcase" @impl true - def run(_args) do - # TODO: accept args and --only and --exclude version options + def run(args) do + {opts, _} = OptionParser.parse!(args, strict: [only: :keep]) + only_versions = Keyword.get_values(opts, :only) components = get_components() - versions = @all_supported_bitstyles_versions + versions = + if only_versions == [] do + @all_supported_bitstyles_versions + else + @all_supported_bitstyles_versions + |> Enum.filter(&(&1 in only_versions)) + end + File.mkdir_p(@dir_name) write_index_html(versions) From 80664a395e2c57de11a08cd20469233434a69753 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 17:33:53 +0100 Subject: [PATCH 03/15] Refactoring --- .../{abcdef.ex => component_rendering.ex} | 5 ++-- lib/bitstyles_phoenix/helper/test_fixtures.ex | 5 ++++ lib/mix/tasks/generate_versions_showcase.ex | 27 ++++++++++--------- test/support/component_case.ex | 2 +- 4 files changed, 24 insertions(+), 15 deletions(-) rename lib/bitstyles_phoenix/helper/{abcdef.ex => component_rendering.ex} (90%) diff --git a/lib/bitstyles_phoenix/helper/abcdef.ex b/lib/bitstyles_phoenix/helper/component_rendering.ex similarity index 90% rename from lib/bitstyles_phoenix/helper/abcdef.ex rename to lib/bitstyles_phoenix/helper/component_rendering.ex index 2421947..5eb2b4c 100644 --- a/lib/bitstyles_phoenix/helper/abcdef.ex +++ b/lib/bitstyles_phoenix/helper/component_rendering.ex @@ -1,5 +1,6 @@ -# TODO: rename me -defmodule BitstylesPhoenix.Helper.Abcdef do +defmodule BitstylesPhoenix.Helper.ComponentRendering do + @moduledoc false + defmacro __using__(_) do quote do use BitstylesPhoenix, js_mode: :none diff --git a/lib/bitstyles_phoenix/helper/test_fixtures.ex b/lib/bitstyles_phoenix/helper/test_fixtures.ex index ec68b0d..630bfdb 100644 --- a/lib/bitstyles_phoenix/helper/test_fixtures.ex +++ b/lib/bitstyles_phoenix/helper/test_fixtures.ex @@ -1,4 +1,9 @@ defmodule BitstylesPhoenix.Helper.TestFixtures do + @moduledoc "Static values helpful in writing doctests." + + # This module is in `/lib` and not in `/test` so that it can be used in the dev env + # in the mix task Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase + def form() do Phoenix.Component.to_form(%{}, as: :user) end diff --git a/lib/mix/tasks/generate_versions_showcase.ex b/lib/mix/tasks/generate_versions_showcase.ex index f463a6a..207af2e 100644 --- a/lib/mix/tasks/generate_versions_showcase.ex +++ b/lib/mix/tasks/generate_versions_showcase.ex @@ -5,11 +5,11 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do import Phoenix.HTML, only: [safe_to_string: 1] import Phoenix.HTML.Tag, only: [content_tag: 3] - # TODO - @shortdoc "TODO" + @dir_name "versions_showcase" + + @shortdoc "Generates static HTML pages in #{@dir_name} for manually testing bitstyles_phoenix with different bitstyles versions." @moduledoc @shortdoc - # TODO: all versions @all_supported_bitstyles_versions [ "4.3.0", "4.2.0", @@ -23,7 +23,6 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do "1.3.0" ] @doctest_entries ["iex>", "...>"] - @dir_name "versions_showcase" @impl true def run(args) do @@ -77,6 +76,7 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do if acc do {node, acc} else + # credo:disable-for-next-line Credo.Check.Refactor.Nesting case node do {:defmodule, _meta, [{:__aliases__, _, [:BitstylesPhoenix, :Component, component_name]}, _]} -> @@ -105,13 +105,8 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do |> to_string() |> String.split("\n") |> Enum.map(&String.trim/1) - |> Enum.filter(fn line -> - Enum.any?(@doctest_entries, &String.starts_with?(line, &1)) - end) - |> Enum.map(fn line -> - Enum.reduce(@doctest_entries, String.trim(line), &String.trim_leading(&2, &1)) - end) - |> Enum.join("\n") + |> Enum.filter(&doctest_line?/1) + |> Enum.map_join("\n", &extract_code_from_doctest_line/1) {node, [%{name: name, code: code, opts: opts || [], line: Keyword.get(meta, :line)} | acc]} @@ -124,6 +119,14 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do Enum.reverse(stories) end + defp doctest_line?(line) do + Enum.any?(@doctest_entries, &String.starts_with?(line, &1)) + end + + defp extract_code_from_doctest_line(line) do + Enum.reduce(@doctest_entries, String.trim(line), &String.trim_leading(&2, &1)) + end + defp write_index_html(versions) do path = Path.join(@dir_name, "index.html") title = "BitstylesPhoenix" @@ -220,7 +223,7 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do code = """ defmodule BitstylesPhoenix.Component.#{component.name}.Showcase.V#{slugify_version(version)}.L#{story.line} do - use BitstylesPhoenix.Helper.Abcdef + use BitstylesPhoenix.Helper.ComponentRendering use BitstylesPhoenix.Component def render_html do diff --git a/test/support/component_case.ex b/test/support/component_case.ex index 224dc36..b8e97f5 100644 --- a/test/support/component_case.ex +++ b/test/support/component_case.ex @@ -5,7 +5,7 @@ defmodule BitstylesPhoenix.ComponentCase do using do quote do - use BitstylesPhoenix.Helper.Abcdef + use BitstylesPhoenix.Helper.ComponentRendering end end end From b3c0b3b24799f306671a1a326d2271b4ebce7ac7 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 17:45:31 +0100 Subject: [PATCH 04/15] Hide showcase task from library users --- .../tasks => scripts}/generate_versions_showcase.ex | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) rename {lib/mix/tasks => scripts}/generate_versions_showcase.ex (96%) diff --git a/lib/mix/tasks/generate_versions_showcase.ex b/scripts/generate_versions_showcase.ex similarity index 96% rename from lib/mix/tasks/generate_versions_showcase.ex rename to scripts/generate_versions_showcase.ex index 207af2e..a38c18d 100644 --- a/lib/mix/tasks/generate_versions_showcase.ex +++ b/scripts/generate_versions_showcase.ex @@ -1,14 +1,10 @@ -# TODO: hide from library users -defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do - use Mix.Task - +defmodule Script.GenerateVersionsShowcase do import Phoenix.HTML, only: [safe_to_string: 1] import Phoenix.HTML.Tag, only: [content_tag: 3] @dir_name "versions_showcase" - @shortdoc "Generates static HTML pages in #{@dir_name} for manually testing bitstyles_phoenix with different bitstyles versions." - @moduledoc @shortdoc + @moduledoc "Generates static HTML pages in #{@dir_name} for manually testing bitstyles_phoenix with different bitstyles versions." @all_supported_bitstyles_versions [ "4.3.0", @@ -24,7 +20,6 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do ] @doctest_entries ["iex>", "...>"] - @impl true def run(args) do {opts, _} = OptionParser.parse!(args, strict: [only: :keep]) only_versions = Keyword.get_values(opts, :only) @@ -325,3 +320,5 @@ defmodule Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase do """ end end + +Script.GenerateVersionsShowcase.run(System.argv()) From 0979c3b8d29c41bdbd33fc666af0c499d5b7133f Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 17:48:27 +0100 Subject: [PATCH 05/15] Add to CI to ensure it continues working in the future --- .github/workflows/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 5b8ca56..c467ba3 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -15,6 +15,8 @@ jobs: - run: mix test - run: mix credo --strict - run: mix docs + - name: Check if versions showcase mix script finishes + run: mix run scripts/generate_versions_showcase.ex demo: runs-on: ubuntu-latest name: Demo From 0a5435663b5d74f794584661681b3e2adffed599 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 18:36:56 +0100 Subject: [PATCH 06/15] Describe how to approach upgrading bitstyles --- README.md | 6 +- docs/bitstyles_version_compatibility.md | 73 +++++++++++++++++++ lib/bitstyles_phoenix/bitstyles.ex | 2 - ...owcase.ex => generate_version_showcase.ex} | 2 +- 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 docs/bitstyles_version_compatibility.md rename scripts/{generate_versions_showcase.ex => generate_version_showcase.ex} (99%) diff --git a/README.md b/README.md index 3cd9af2..d8c07ab 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Basic helpers for [bitstyles](https://github.com/bitcrowd/bitstyles) for elixir ## Requirements -bitstyles must be installed separately into the asset generation. The helpers in this project just output classes for working with bitstyles. +Bitstyles must be installed separately into the asset generation. The helpers in this project just output classes for working with bitstyles. + +Bitstyles versions from 4.3.0 down to 1.3.0 are supported. ## Installation @@ -61,3 +63,5 @@ mix docs && fswatch -o lib | xargs -n1 -I {} mix docs ``` For running the demo app & integration tests check out the [demo README](demo/README.md). + +For guides on how to adapt bitstyles_phoenix to a higher bitstyles version, see [bitstyles version compatibility](docs/bitstyles_version_compatibility.md). diff --git a/docs/bitstyles_version_compatibility.md b/docs/bitstyles_version_compatibility.md new file mode 100644 index 0000000..be00213 --- /dev/null +++ b/docs/bitstyles_version_compatibility.md @@ -0,0 +1,73 @@ +# Bitstyles version compatibility + +General rules: + +- We don't drop bitstyles version support. Users of bitstyles_phoenix should be able to upgrade bitstyles_phoenix to the newest version without having to upgrade bitstyles. +- We don't skip bitstyles versions. Users of bitstyles_phoenix should be able to upgrade bitstyles to any version between the highest and lowest currently supported bitstyles version. + +## Doctests ("stories") + +Component doctests (created with a custom `story` macro) describe components as their would render for the highest currently supported bitstyles version (default version). + +## Versions showcase + +Because doctests can only show how a bitstyles_phoenix component looks like with the default bitstyles versions, they can't be used to preview components with other bitstyles versions. + +A showcase of components in different versions can be generated by running: + +```bash +mix run scripts/generate_version_showcase.ex +``` + +The script accepts multiple `--only` arguments if you want to limit the showcase to only a handful of versions: + +```bash +mix run scripts/generate_version_showcase.ex --only 4.3.0 --only 4.2.0 +``` + +This script will create a `versions_showcase` directory with static web pages. Open the starting page with: + +```bash +open versions_showcase/index.html +``` + +## How to upgrade the default bitstyles version in bitstyles_phoenix? + +1. Choose the smallest possible version jump (do not skip versions). +2. Read about the changes in version in [the bitstyles Changelog](https://github.com/bitcrowd/bitstyles/blob/main/CHANGELOG.md). +3. Update the highest supported version mentioned in the [README](../README.md). +4. Update the `@default_version` in [`BitstylesPhoenix.Bitstyles`](../lib/bitstyles_phoenix/bitstyles.ex). +5. Add the new version to the version lists in [`generate_version_showcase`](../scripts/generate_version_showcase.ex). +6. Handle classes renamed by bitstyles by adding new clauses of the `classname/2` function in [`BitstylesPhoenix.Bitstyles`](../lib/bitstyles_phoenix/bitstyles.ex). +7. If renaming classes is not enough to upgrade correctly, you can perform [a bitstyles version check in a component](#an-example-of-a-bitstyles-version-check-in-a-component). +8. Run `mix test`. Fix all doctests until `mix test` succeeds. +9. Run `mix docs` to preview components after making changes to them. +10. Use the [versions showcase](#versions-showcase) to test that you didn't break for older bitstyles versions. + +### An example of a bitstyles version check in a component + +```elixir +def ui_tricky_component(assigns) do + version = BitstylesPhoenix.Bitstyles.version() + + if version >= "5.0.0" do + ~H""" +

    ...

    + """ + else + ~H""" +
    +
    + ... +
    +
    + """ + end +end +``` + +## Adding new components + +Ideally, if you're adding a completely new component, make sure it works with all supported bitstyles versions by using the [versions showcase](#versions-showcase) to test it. + +If it's not practical to support it in other version, you can perform [a bitstyles version check in the component](#an-example-of-a-bitstyles-version-check-in-a-component). diff --git a/lib/bitstyles_phoenix/bitstyles.ex b/lib/bitstyles_phoenix/bitstyles.ex index 05474be..44b2087 100644 --- a/lib/bitstyles_phoenix/bitstyles.ex +++ b/lib/bitstyles_phoenix/bitstyles.ex @@ -1,8 +1,6 @@ defmodule BitstylesPhoenix.Bitstyles do @moduledoc false - # TODO: write a document about how to do bitstyles upgrades in this repo - @default_version "4.3.0" @cdn_url "https://cdn.jsdelivr.net/npm/bitstyles" diff --git a/scripts/generate_versions_showcase.ex b/scripts/generate_version_showcase.ex similarity index 99% rename from scripts/generate_versions_showcase.ex rename to scripts/generate_version_showcase.ex index a38c18d..310086c 100644 --- a/scripts/generate_versions_showcase.ex +++ b/scripts/generate_version_showcase.ex @@ -1,4 +1,4 @@ -defmodule Script.GenerateVersionsShowcase do +defmodule Script.GenerateVersionShowcase do import Phoenix.HTML, only: [safe_to_string: 1] import Phoenix.HTML.Tag, only: [content_tag: 3] From 859bd5a17a6b94d18cc2055a0f875d28b624a951 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 18:38:09 +0100 Subject: [PATCH 07/15] Fix credo problem --- lib/bitstyles_phoenix/helper/test_fixtures.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bitstyles_phoenix/helper/test_fixtures.ex b/lib/bitstyles_phoenix/helper/test_fixtures.ex index 630bfdb..c043e0d 100644 --- a/lib/bitstyles_phoenix/helper/test_fixtures.ex +++ b/lib/bitstyles_phoenix/helper/test_fixtures.ex @@ -4,7 +4,7 @@ defmodule BitstylesPhoenix.Helper.TestFixtures do # This module is in `/lib` and not in `/test` so that it can be used in the dev env # in the mix task Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase - def form() do + def form do Phoenix.Component.to_form(%{}, as: :user) end From 5e043767992f457f787b43b6504299a96d24c6f0 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 18:40:33 +0100 Subject: [PATCH 08/15] Don't include test fixtures in documentation --- lib/bitstyles_phoenix/helper/test_fixtures.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bitstyles_phoenix/helper/test_fixtures.ex b/lib/bitstyles_phoenix/helper/test_fixtures.ex index c043e0d..2afdf57 100644 --- a/lib/bitstyles_phoenix/helper/test_fixtures.ex +++ b/lib/bitstyles_phoenix/helper/test_fixtures.ex @@ -1,6 +1,7 @@ defmodule BitstylesPhoenix.Helper.TestFixtures do - @moduledoc "Static values helpful in writing doctests." + @moduledoc false + # Static values helpful in writing doctests. # This module is in `/lib` and not in `/test` so that it can be used in the dev env # in the mix task Mix.Tasks.BitstylesPhoenix.GenerateVersionsShowcase From 3d4bc4db3b930594ffa96ba608cfe4ec00d4e9cd Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 18:41:56 +0100 Subject: [PATCH 09/15] Don't link from hexdoc to a dev-only doc --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index d8c07ab..bc512f8 100644 --- a/README.md +++ b/README.md @@ -63,5 +63,3 @@ mix docs && fswatch -o lib | xargs -n1 -I {} mix docs ``` For running the demo app & integration tests check out the [demo README](demo/README.md). - -For guides on how to adapt bitstyles_phoenix to a higher bitstyles version, see [bitstyles version compatibility](docs/bitstyles_version_compatibility.md). From 547967925365273c462873ce4f7da7dc15d80459 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Mon, 18 Mar 2024 18:50:14 +0100 Subject: [PATCH 10/15] Fix plural vs singular --- .github/workflows/action.yml | 2 +- .gitignore | 2 +- docs/bitstyles_version_compatibility.md | 4 ++-- scripts/generate_version_showcase.ex | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index c467ba3..affa76b 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -16,7 +16,7 @@ jobs: - run: mix credo --strict - run: mix docs - name: Check if versions showcase mix script finishes - run: mix run scripts/generate_versions_showcase.ex + run: mix run scripts/generate_version_showcase.ex demo: runs-on: ubuntu-latest name: Demo diff --git a/.gitignore b/.gitignore index afb2c71..c1ff8c5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ erl_crash.dump # Ignore package tarball (built via "mix hex.build"). bitstyles_phoenix-*.tar -/versions_showcase +/version_showcase diff --git a/docs/bitstyles_version_compatibility.md b/docs/bitstyles_version_compatibility.md index be00213..19a54e2 100644 --- a/docs/bitstyles_version_compatibility.md +++ b/docs/bitstyles_version_compatibility.md @@ -25,10 +25,10 @@ The script accepts multiple `--only` arguments if you want to limit the showcase mix run scripts/generate_version_showcase.ex --only 4.3.0 --only 4.2.0 ``` -This script will create a `versions_showcase` directory with static web pages. Open the starting page with: +This script will create a `version_showcase` directory with static web pages. Open the starting page with: ```bash -open versions_showcase/index.html +open version_showcase/index.html ``` ## How to upgrade the default bitstyles version in bitstyles_phoenix? diff --git a/scripts/generate_version_showcase.ex b/scripts/generate_version_showcase.ex index 310086c..3bba95c 100644 --- a/scripts/generate_version_showcase.ex +++ b/scripts/generate_version_showcase.ex @@ -1,8 +1,8 @@ -defmodule Script.GenerateVersionShowcase do +defmodule Scripts.GenerateVersionShowcase do import Phoenix.HTML, only: [safe_to_string: 1] import Phoenix.HTML.Tag, only: [content_tag: 3] - @dir_name "versions_showcase" + @dir_name "version_showcase" @moduledoc "Generates static HTML pages in #{@dir_name} for manually testing bitstyles_phoenix with different bitstyles versions." @@ -321,4 +321,4 @@ defmodule Script.GenerateVersionShowcase do end end -Script.GenerateVersionsShowcase.run(System.argv()) +Scripts.GenerateVersionShowcase.run(System.argv()) From 0000e53d9efdd7922545a98adeb28304452a63fb Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Thu, 21 Mar 2024 14:28:06 +0100 Subject: [PATCH 11/15] Split doctest into two parts in story macro --- lib/bitstyles_phoenix/alpine3/dropdown.ex | 4 + lib/bitstyles_phoenix/alpine3/sidebar.ex | 2 + lib/bitstyles_phoenix/component/avatar.ex | 122 ++- lib/bitstyles_phoenix/component/badge.ex | 155 +-- .../component/breadcrumbs.ex | 6 + lib/bitstyles_phoenix/component/button.ex | 340 ++++--- lib/bitstyles_phoenix/component/card.ex | 8 + lib/bitstyles_phoenix/component/content.ex | 6 + .../component/description_list.ex | 4 + lib/bitstyles_phoenix/component/dropdown.ex | 12 + lib/bitstyles_phoenix/component/error.ex | 130 ++- lib/bitstyles_phoenix/component/flash.ex | 12 + lib/bitstyles_phoenix/component/form.ex | 936 ++++++++++-------- lib/bitstyles_phoenix/component/heading.ex | 10 + lib/bitstyles_phoenix/component/icon.ex | 62 +- lib/bitstyles_phoenix/component/modal.ex | 8 + lib/bitstyles_phoenix/component/sidebar.ex | 6 + lib/bitstyles_phoenix/component/tabs.ex | 6 + lib/bitstyles_phoenix/component/use_svg.ex | 6 + lib/bitstyles_phoenix/showcase.ex | 11 +- scripts/generate_version_showcase.ex | 12 +- 21 files changed, 1100 insertions(+), 758 deletions(-) diff --git a/lib/bitstyles_phoenix/alpine3/dropdown.ex b/lib/bitstyles_phoenix/alpine3/dropdown.ex index 64b73d5..5e8ac50 100644 --- a/lib/bitstyles_phoenix/alpine3/dropdown.ex +++ b/lib/bitstyles_phoenix/alpine3/dropdown.ex @@ -43,6 +43,8 @@ defmodule BitstylesPhoenix.Alpine3.Dropdown do ...> ...> ...> """ + ''', + ''' """
    - """ - ''') - - story("Default link", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button href="/" variant="ui"> - ...> Publish - ...> - ...> """ - """ - - Publish - - """ - ''') - - story("Default disabled link renders disabled button instead", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button href="/" variant="ui" disabled> - ...> Publish - ...> - ...> """ - """ - - """ - ''') - - story("Default submit button", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button type="submit"> - ...> Save - ...> - ...> """ - """ - - """ - ''') - - story("Default submit button with custom classes", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button type="submit" class="foo bar"> - ...> Save - ...> - ...> """ - """ - - """ - ''') - - story("UI button", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button type="submit" variant={:ui}> - ...> Save - ...> - ...> """ - """ - - """ - ''') - - story("Dangerous button", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_button type="submit" variant={:danger}> - ...> Save - ...> - ...> """ - """ - - """ - ''') + story( + "Default button", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button> + ...> Publish + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "Default link", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button href="/" variant="ui"> + ...> Publish + ...> + ...> """ + ''', + ''' + """ + + Publish + + """ + ''' + ) + + story( + "Default disabled link renders disabled button instead", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button href="/" variant="ui" disabled> + ...> Publish + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "Default submit button", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button type="submit"> + ...> Save + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "Default submit button with custom classes", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button type="submit" class="foo bar"> + ...> Save + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "UI button", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button type="submit" variant={:ui}> + ...> Save + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "Dangerous button", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_button type="submit" variant={:danger}> + ...> Save + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) story( "Button with an icon", @@ -133,6 +168,8 @@ defmodule BitstylesPhoenix.Component.Button do ...> Add ...> ...> """ + ''', + ''' """ - """ - ''') + story( + "Icon button with some options", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_icon_button icon={{"bin", file: "assets/icons.svg", size: "xl"}} label="Delete" class="foo" /> + ...> """ + ''', + ''' + """ + + """ + ''' + ) story( "Icon button reversed", @@ -364,6 +420,8 @@ defmodule BitstylesPhoenix.Component.Button do ...> render ~H""" ...> <.ui_icon_button icon="plus" label="Show" href="#" reversed /> ...> """ + ''', + ''' """

    Hello world

    ...> """ + ''', + ''' """

    @@ -35,6 +37,8 @@ defmodule BitstylesPhoenix.Component.Card do ...> render ~H""" ...> <.ui_card size="l">

    Hello world

    ...> """ + ''', + ''' """

    @@ -55,6 +59,8 @@ defmodule BitstylesPhoenix.Component.Card do ...>

    Hello world

    ...> ...> """ + ''', + ''' """
    @@ -80,6 +86,8 @@ defmodule BitstylesPhoenix.Component.Card do ...>

    Hello world

    ...> ...> """ + ''', + ''' """
    diff --git a/lib/bitstyles_phoenix/component/content.ex b/lib/bitstyles_phoenix/component/content.ex index db03d90..c869a7e 100644 --- a/lib/bitstyles_phoenix/component/content.ex +++ b/lib/bitstyles_phoenix/component/content.ex @@ -26,6 +26,8 @@ defmodule BitstylesPhoenix.Component.Content do ...> Content ...> ...> """ + ''', + ''' """
    Content @@ -44,6 +46,8 @@ defmodule BitstylesPhoenix.Component.Content do ...> Full Content ...> ...> """ + ''', + ''' """
    Full Content @@ -62,6 +66,8 @@ defmodule BitstylesPhoenix.Component.Content do ...> Content with extra ...> ...> """ + ''', + ''' """
    Content with extra diff --git a/lib/bitstyles_phoenix/component/description_list.ex b/lib/bitstyles_phoenix/component/description_list.ex index 38dbf10..62be529 100644 --- a/lib/bitstyles_phoenix/component/description_list.ex +++ b/lib/bitstyles_phoenix/component/description_list.ex @@ -15,6 +15,8 @@ defmodule BitstylesPhoenix.Component.DescriptionList do ...> <.ui_dl_item label="Inserted at">2007-01-02 ...> ...> """ + ''', + ''' """
    @@ -57,6 +59,8 @@ defmodule BitstylesPhoenix.Component.DescriptionList do ...> ...> ...> """ + ''', + ''' """
    diff --git a/lib/bitstyles_phoenix/component/dropdown.ex b/lib/bitstyles_phoenix/component/dropdown.ex index 53ad471..c960760 100644 --- a/lib/bitstyles_phoenix/component/dropdown.ex +++ b/lib/bitstyles_phoenix/component/dropdown.ex @@ -64,6 +64,8 @@ defmodule BitstylesPhoenix.Component.Dropdown do ...> ...> ...> """ + ''', + ''' """
    ...> """ + ''', + ''' """
    @@ -175,6 +179,8 @@ defmodule BitstylesPhoenix.Component.Dropdown do ...> ...> ...> """ + ''', + ''' """
    ...> """ + ''', + ''' """
    @@ -288,6 +296,8 @@ defmodule BitstylesPhoenix.Component.Dropdown do ...> ...> ...> """ + ''', + ''' """
    ...> """ + ''', + ''' """
    diff --git a/lib/bitstyles_phoenix/component/error.ex b/lib/bitstyles_phoenix/component/error.ex index 758ea51..e9969f8 100644 --- a/lib/bitstyles_phoenix/component/error.ex +++ b/lib/bitstyles_phoenix/component/error.ex @@ -23,38 +23,48 @@ defmodule BitstylesPhoenix.Component.Error do Uses the `translate_errors` MFA from the config to translate field errors (e.g. with `gettext`). """ - story("A single error", ''' - iex> assigns = %{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_errors form={@form} field={:name} /> - ...> """ - """ - - is too short - - """ - ''') - - story("Multiple errors", ''' - iex> assigns = %{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_errors form={@form} field={:email} /> - ...> """ - """ -
      -
    • - - is invalid - -
    • -
    • - - must end with @bitcrowd.net - -
    • -
    - """ - ''') + story( + "A single error", + ''' + iex> assigns = %{form: form_with_errors()} + ...> render ~H""" + ...> <.ui_errors form={@form} field={:name} /> + ...> """ + ''', + ''' + """ + + is too short + + """ + ''' + ) + + story( + "Multiple errors", + ''' + iex> assigns = %{form: form_with_errors()} + ...> render ~H""" + ...> <.ui_errors form={@form} field={:email} /> + ...> """ + ''', + ''' + """ +
      +
    • + + is invalid + +
    • +
    • + + must end with @bitcrowd.net + +
    • +
    + """ + ''' + ) def ui_errors(assigns) do assigns.form.errors @@ -106,29 +116,39 @@ defmodule BitstylesPhoenix.Component.Error do specified in [bitstyles colors](https://bitcrowd.github.io/bitstyles/?path=/docs/utilities-fg--warning). """ - story("An error tag", ''' - iex> assigns = %{} - ...> render ~H""" - ...> <.ui_error error={{"Foo error", []}} /> - ...> """ - """ - - Foo error - - """ - ''') - - story("An error tag extra options and classes", ''' - iex> assigns = %{error: {"Foo error", []}} - ...> render ~H""" - ...> <.ui_error error={@error} phx-feedback-for="foo" class="bar" /> - ...> """ - """ - - Foo error - - """ - ''') + story( + "An error tag", + ''' + iex> assigns = %{} + ...> render ~H""" + ...> <.ui_error error={{"Foo error", []}} /> + ...> """ + ''', + ''' + """ + + Foo error + + """ + ''' + ) + + story( + "An error tag extra options and classes", + ''' + iex> assigns = %{error: {"Foo error", []}} + ...> render ~H""" + ...> <.ui_error error={@error} phx-feedback-for="foo" class="bar" /> + ...> """ + ''', + ''' + """ + + Foo error + + """ + ''' + ) def ui_error(assigns) do extra = assigns_to_attributes(assigns, [:class, :error, :field, :form]) diff --git a/lib/bitstyles_phoenix/component/flash.ex b/lib/bitstyles_phoenix/component/flash.ex index 49b0823..eb03fd4 100644 --- a/lib/bitstyles_phoenix/component/flash.ex +++ b/lib/bitstyles_phoenix/component/flash.ex @@ -35,6 +35,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Something you may be interested to hear ...> ...> """) + ''', + ''' """
    @@ -55,6 +57,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Saved successfully ...> ...> """) + ''', + ''' """
    @@ -75,6 +79,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Saved with errors ...> ...> """) + ''', + ''' """
    @@ -95,6 +101,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Saving failed ...> ...> """) + ''', + ''' """
    @@ -115,6 +123,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Saving failed ...> ...> """) + ''', + ''' """
    @@ -135,6 +145,8 @@ defmodule BitstylesPhoenix.Component.Flash do ...> Saving failed ...> ...> """) + ''', + ''' """
    diff --git a/lib/bitstyles_phoenix/component/form.ex b/lib/bitstyles_phoenix/component/form.ex index d9fdf3a..7b542ae 100644 --- a/lib/bitstyles_phoenix/component/form.ex +++ b/lib/bitstyles_phoenix/component/form.ex @@ -66,221 +66,286 @@ defmodule BitstylesPhoenix.Component.Form do See the [bitstyles form docs](https://bitcrowd.github.io/bitstyles/?path=/docs/ui-data-forms--login-form) for examples of form layouts. """ - story("Text field with label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:name} /> - ...> """ - """ - - - """ - ''') - - story("Text required field with label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:name} required/> - ...> """ - """ - - - """ - ''') - - story("Text field with error", ''' - iex> assigns=%{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:name} /> - ...> """ - """ - - - - is too short - - """ - ''') - - story("Text field with multiple errors", ''' - iex> assigns=%{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:email} /> - ...> """ - """ - - -
      -
    • - - is invalid + """ + ''' + ) + + story( + "Text field with multiple errors", + ''' + iex> assigns=%{form: form_with_errors()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:email} /> + ...> """ + ''', + ''' + """ + + +
        +
      • + + is invalid + +
      • +
      • + + must end with @bitcrowd.net + +
      • +
      + """ + ''' + ) + + story( + "Text field with hidden label", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:name} hidden_label={true} /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Text field with label (without maxlength)", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:name} maxlength={false}/> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Text field with options", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input + ...> form={@form} + ...> field={:totp} + ...> label="Authentication code" + ...> label_opts={[class: "extra"]} + ...> placeholder="6-digit code" + ...> required={true} + ...> value="" + ...> inputmode="numeric" + ...> pattern="[0-9]*" + ...> autocomplete="one-time-code" + ...> maxlength={6} /> + ...> """ + ''', + ''' + """ +
    • -
    • - - must end with @bitcrowd.net + + + """ + ''' + ) + + story( + "Email field with label", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:email} type={:email} /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Search field with placholder", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input + ...> form={@form} + ...> field={:email_or_name} + ...> type={:search} + ...> placeholder="Search by email or name" + ...> autofocus={true} /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "File field for pdfs", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> + ...> <.ui_input form={form} field={:file} type={:file} accept="application/pdf" /> + ...> + ...> """ + ''', + ''' + """ +
      + + +
      + """ + ''' + ) + + story( + "Checkbox", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:accept} type={:checkbox} /> + ...> """ + ''', + ''' + """ + + """ + ''' + ) + + story( + "Checkbox required", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:accept} type={:checkbox} required/> + ...> """ + ''', + ''' + """ +
    • -
    - """ - ''') - - story("Text field with hidden label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:name} hidden_label={true} /> - ...> """ - """ - - - """ - ''') - - story("Text field with label (without maxlength)", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:name} maxlength={false}/> - ...> """ - """ - - - """ - ''') - - story("Text field with options", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input - ...> form={@form} - ...> field={:totp} - ...> label="Authentication code" - ...> label_opts={[class: "extra"]} - ...> placeholder="6-digit code" - ...> required={true} - ...> value="" - ...> inputmode="numeric" - ...> pattern="[0-9]*" - ...> autocomplete="one-time-code" - ...> maxlength={6} /> - ...> """ - """ - - - """ - ''') - - story("Email field with label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:email} type={:email} /> - ...> """ - """ - - - """ - ''') - - story("Search field with placholder", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input - ...> form={@form} - ...> field={:email_or_name} - ...> type={:search} - ...> placeholder="Search by email or name" - ...> autofocus={true} /> - ...> """ - """ - - - """ - ''') - - story("File field for pdfs", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> - ...> <.ui_input form={form} field={:file} type={:file} accept="application/pdf" /> - ...> - ...> """ - """ -
    - - -
    - """ - ''') - - story("Checkbox", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:accept} type={:checkbox} /> - ...> """ - """ - - """ - ''') - - story("Checkbox required", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:accept} type={:checkbox} required/> - ...> """ - """ - - """ - ''') - - story("Checkbox with label class", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_input form={@form} field={:accept} type={:checkbox} label_opts={[class: "extra"]}/> - ...> """ - """ - - """ - ''') + """ + ''' + ) + + story( + "Checkbox with label class", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_input form={@form} field={:accept} type={:checkbox} label_opts={[class: "extra"]}/> + ...> """ + ''', + ''' + """ + + """ + ''' + ) def ui_input(assigns) do extra = assigns_to_attributes(assigns, @wrapper_assigns_keys ++ [:type]) @@ -330,89 +395,114 @@ defmodule BitstylesPhoenix.Component.Form do See the [bitstyles textarea docs](https://bitcrowd.github.io/bitstyles/?path=/docs/base-forms--textarea-and-label) for examples of textareas and labels in use. """ - story("Textarea", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_textarea form={@form} field={:about_me} /> - ...> """ - """ - - - """ - ''') - - story("Textarea", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_textarea form={@form} field={:about_me} required/> - ...> """ - """ - - - """ - ''') - - story("Textarea with options", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_textarea - ...> form={@form} - ...> field={:metadata} - ...> label="Metadata" - ...> label_opts={[class: "extra"]} - ...> value="Value here" - ...> rows={10} - ...> /> - ...> """ - """ - - - """ - ''') - - story("Textarea with hidden label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_textarea form={@form} field={:address} hidden_label/> - ...> """ - """ - - - """ - ''') - - story("Textarea with error", ''' - iex> assigns=%{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_textarea form={@form} field={:name} /> - ...> """ - """ - - - - is too short - - """ - ''') + """ + ''' + ) def ui_textarea(assigns) do extra = assigns_to_attributes(assigns, @wrapper_assigns_keys) @@ -442,88 +532,108 @@ defmodule BitstylesPhoenix.Component.Form do See the [bitstyles select docs](https://bitcrowd.github.io/bitstyles/?path=/docs/base-forms--select-and-label) for examples of textareas and labels in use. """ - story("Select box", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_select form={@form} field={:week} options={1..2} /> - ...> """ - """ - - - """ - ''') - - story("Select box required", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_select form={@form} field={:week} options={1..2} required /> - ...> """ - """ - - - """ - ''') - - story("Select box without label", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_select form={@form} field={:week} options={1..2} hidden_label/> - ...> """ - """ - - - """ - ''') - - story("Select box with options", ''' - iex> assigns=%{form: form(), options: [{"Ducks", "ducks"}, {"Cats", "cats"}]} - ...> render ~H""" - ...> <.ui_select form={@form} field={:preference} options={@options} label="What do you like best?" label_opts={[class: "extra"]}/> - ...> """ - """ - - - """ - ''') + story( + "Select box", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_select form={@form} field={:week} options={1..2} /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Select box required", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_select form={@form} field={:week} options={1..2} required /> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Select box without label", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_select form={@form} field={:week} options={1..2} hidden_label/> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) + + story( + "Select box with options", + ''' + iex> assigns=%{form: form(), options: [{"Ducks", "ducks"}, {"Cats", "cats"}]} + ...> render ~H""" + ...> <.ui_select form={@form} field={:preference} options={@options} label="What do you like best?" label_opts={[class: "extra"]}/> + ...> """ + ''', + ''' + """ + + + """ + ''' + ) def ui_select(assigns) do extra = assigns_to_attributes(assigns, @wrapper_assigns_keys ++ [:options]) @@ -553,25 +663,30 @@ defmodule BitstylesPhoenix.Component.Form do - All options from above (see top level module doc). """ - story("Custom inputs", ''' - iex> assigns=%{form: form_with_errors()} - ...> render ~H""" - ...> <.ui_unwrapped_input form={@form} field={:name} label="Custom"> - ...> Custom content - ...> - ...> - ...> """ - """ - - Custom content - - - is too short - - """ - ''') + story( + "Custom inputs", + ''' + iex> assigns=%{form: form_with_errors()} + ...> render ~H""" + ...> <.ui_unwrapped_input form={@form} field={:name} label="Custom"> + ...> Custom content + ...> + ...> + ...> """ + ''', + ''' + """ + + Custom content + + + is too short + + """ + ''' + ) def ui_unwrapped_input(assigns) do label_text = Map.get_lazy(assigns, :label, fn -> default_label(assigns.field) end) @@ -604,20 +719,25 @@ defmodule BitstylesPhoenix.Component.Form do - All options from above (see top level module doc). """ - story("Custom wrapped inputs", ''' - iex> assigns=%{form: form()} - ...> render ~H""" - ...> <.ui_wrapped_input form={@form} field={:name} label="Current name"> - ...> - ...> - ...> """ - """ - - """ - ''') + story( + "Custom wrapped inputs", + ''' + iex> assigns=%{form: form()} + ...> render ~H""" + ...> <.ui_wrapped_input form={@form} field={:name} label="Current name"> + ...> + ...> + ...> """ + ''', + ''' + """ + + """ + ''' + ) def ui_wrapped_input(assigns) do assigns = diff --git a/lib/bitstyles_phoenix/component/heading.ex b/lib/bitstyles_phoenix/component/heading.ex index c97a856..2899685 100644 --- a/lib/bitstyles_phoenix/component/heading.ex +++ b/lib/bitstyles_phoenix/component/heading.ex @@ -35,6 +35,8 @@ defmodule BitstylesPhoenix.Component.Heading do ...> Title ...> ...> """ + ''', + ''' """
    @@ -69,6 +71,8 @@ defmodule BitstylesPhoenix.Component.Heading do ...> ...> ...> """ + ''', + ''' """
    @@ -161,6 +165,8 @@ defmodule BitstylesPhoenix.Component.Heading do ...> Section title ...> ...> """ + ''', + ''' """
    @@ -183,6 +189,8 @@ defmodule BitstylesPhoenix.Component.Heading do ...> Section title ...> ...> """ + ''', + ''' """
    @@ -217,6 +225,8 @@ defmodule BitstylesPhoenix.Component.Heading do ...> ...> ...> """ + ''', + ''' """
    diff --git a/lib/bitstyles_phoenix/component/icon.ex b/lib/bitstyles_phoenix/component/icon.ex index 55cce72..5b59f21 100644 --- a/lib/bitstyles_phoenix/component/icon.ex +++ b/lib/bitstyles_phoenix/component/icon.ex @@ -31,6 +31,8 @@ defmodule BitstylesPhoenix.Component.Icon do ...> render ~H""" ...> <.ui_icon name="inline-arrow"/> ...> """ + ''', + ''' """