101 |
102 | \"""
103 | """
104 | )
105 |
106 | def ui_card(assigns) do
107 | class =
108 | classnames([
109 | "a-card",
110 | {"a-card-#{assigns[:size]}", assigns[:size] != nil},
111 | assigns[:class]
112 | ])
113 |
114 | extra =
115 | assigns
116 | |> assigns_to_attributes([:class, :size, :card_header])
117 |
118 | {card_header, card_header_extra} =
119 | assigns_from_single_slot(assigns, :card_header, optional: true)
120 |
121 | card_header_class =
122 | classnames([
123 | {"a-card-#{assigns[:size]}__header", !is_nil(assigns[:size])},
124 | {"a-card__header", is_nil(assigns[:size])},
125 | card_header[:class]
126 | ])
127 |
128 | assigns =
129 | assign(assigns,
130 | extra: extra,
131 | class: class,
132 | card_header_extra: card_header_extra,
133 | card_header_class: card_header_class
134 | )
135 |
136 | ~H"""
137 |
138 | <%= if assigns[:card_header] do %>
139 | <.ui_flash class={@card_header_class} {@card_header_extra}>
140 | <%= render_slot(@card_header) %>
141 |
142 | <% end %>
143 | <%= render_slot(@inner_block) %>
144 |
145 | """
146 | end
147 | end
148 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix/component/badge.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix.Component.Badge do
2 | use BitstylesPhoenix.Component
3 | alias BitstylesPhoenix.Bitstyles
4 |
5 | @moduledoc """
6 | The Badge component.
7 | """
8 |
9 | @doc ~s"""
10 | Render a badge to highlighted small texts, such as an item count or state indicator.
11 |
12 | ## Attributes
13 |
14 | - `variant` — Variant of the badge you want, from those available in the CSS classes e.g. `brand-1`, `danger`
15 | - `class` - Extra classes to pass to the badge. See `BitstylesPhoenix.Helper.classnames/1` for usage.
16 | - All other attributes are passed to the `span` tag.
17 |
18 | See [bitstyles badge docs](https://bitcrowd.github.io/bitstyles/?path=/docs/atoms-badge--badge) for examples, and for the default variants available.
19 | """
20 |
21 | story(
22 | "Default badge",
23 | """
24 | iex> assigns = %{}
25 | ...> render ~H\"""
26 | ...> <.ui_badge>
27 | ...> published
28 | ...>
29 | ...> \"""
30 | """,
31 | "6.0.0": """
32 | \"""
33 |
34 | published
35 |
36 | \"""
37 | """,
38 | "5.0.1": """
39 | \"""
40 |
41 | published
42 |
43 | \"""
44 | """,
45 | "4.3.0": """
46 | \"""
47 |
48 | published
49 |
50 | \"""
51 | """
52 | )
53 |
54 | story(
55 | "Badge variant brand-1",
56 | """
57 | iex> assigns = %{}
58 | ...> render ~H\"""
59 | ...> <.ui_badge variant="brand-1">
60 | ...> new
61 | ...>
62 | ...> \"""
63 | """,
64 | "6.0.0": """
65 | \"""
66 |
67 | new
68 |
69 | \"""
70 | """,
71 | "5.0.1": """
72 | \"""
73 |
74 | new
75 |
76 | \"""
77 | """
78 | )
79 |
80 | story(
81 | "Badge variant brand-2",
82 | """
83 | iex> assigns = %{}
84 | ...> render ~H\"""
85 | ...> <.ui_badge variant="brand-2">
86 | ...> recommended
87 | ...>
88 | ...> \"""
89 | """,
90 | """
91 | \"""
92 |
93 | recommended
94 |
95 | \"""
96 | """
97 | )
98 |
99 | story(
100 | "Badge variant danger",
101 | """
102 | iex> assigns = %{}
103 | ...> render ~H\"""
104 | ...> <.ui_badge variant="danger">
105 | ...> deleted
106 | ...>
107 | ...> \"""
108 | """,
109 | """
110 | \"""
111 |
112 | deleted
113 |
114 | \"""
115 | """
116 | )
117 |
118 | story(
119 | "Extra options and classes",
120 | """
121 | iex> assigns = %{}
122 | ...> render ~H\"""
123 | ...> <.ui_badge class="extra-class" data-foo="bar">
124 | ...> published
125 | ...>
126 | ...> \"""
127 | """,
128 | """
129 | \"""
130 |
131 | published
132 |
133 | \"""
134 | """
135 | )
136 |
137 | def ui_badge(assigns) do
138 | extra = assigns_to_attributes(assigns, [:class, :variant])
139 |
140 | {variant_class, extra} =
141 | if Bitstyles.Version.version() >= {6, 0, 0} do
142 | theme = assigns[:variant] || "grayscale"
143 | {nil, Keyword.put_new(extra, :"data-theme", theme)}
144 | else
145 | variant = assigns[:variant] || "text"
146 | {"a-badge--#{variant}", extra}
147 | end
148 |
149 | class =
150 | classnames([
151 | "a-badge u-h6 u-font-medium",
152 | variant_class,
153 | assigns[:class]
154 | ])
155 |
156 | assigns = assign(assigns, class: class, extra: extra)
157 | ~H"<%= render_slot(@inner_block) %>"
158 | end
159 | end
160 |
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/bitstyles_version_compatibility.md:
--------------------------------------------------------------------------------
1 | # Bitstyles version compatibility
2 |
3 | General rules:
4 |
5 | - 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.
6 | - 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.
7 |
8 | ## Doctests ("stories")
9 |
10 | The `story` macro is used to generate doctests, as well as the component preview in docs.
11 |
12 | The first argument is the story's description (`"An error tag"`), the second argument is an iex code snippet, and the third argument is the expected result when the code snippet gets executed. The third argument can be a single charlist, or a keyword lists that maps bitstyles version numbers to charlists. If the third argument is a single charlist, it's assumed that is the expected result for the default bitstyles version.
13 |
14 | ⚠️ Note that the 4 space indentation in the code snippet and the expected result is important.
15 |
16 | ### Multi-version doctest example
17 |
18 | ```elixir
19 | story(
20 | "An error tag",
21 | '''
22 | iex> assigns = %{}
23 | ...> render ~H"""
24 | ...> <.ui_error error={{"Foo error", []}} />
25 | ...> """
26 | ''',
27 | "4.3.0": '''
28 | """
29 |
30 | Foo error
31 |
32 | """
33 | ''',
34 | "3.0.0": '''
35 | """
36 |
37 | Foo error
38 |
39 | """
40 | '''
41 | )
42 | ```
43 |
44 | ## Versions showcase
45 |
46 | A showcase of components in all supported bitstyles versions can be generated by running:
47 |
48 | ```bash
49 | mix run scripts/generate_version_showcase.ex
50 | ```
51 |
52 | The script accepts multiple `--only` arguments if you want to limit the showcase to only a handful of versions:
53 |
54 | ```bash
55 | mix run scripts/generate_version_showcase.ex --only 4.3.0 --only 4.2.0
56 | ```
57 |
58 | This script will create a `version_showcase` directory with static web pages. Open the starting page with:
59 |
60 | ```bash
61 | open version_showcase/index.html
62 | ```
63 |
64 | ## How to upgrade the default bitstyles version in bitstyles_phoenix?
65 |
66 | 1. Choose the smallest possible version jump (do not skip versions).
67 | 2. Read about the changes in version in [the bitstyles Changelog](https://github.com/bitcrowd/bitstyles/blob/main/CHANGELOG.md).
68 | 3. Update the highest supported version mentioned in the [README](../README.md).
69 | 4. Update the `@default_version` in [`BitstylesPhoenix.Bitstyles`](../lib/bitstyles_phoenix/bitstyles.ex).
70 | 5. Add the new version to the version lists in [`generate_version_showcase`](../scripts/generate_version_showcase.ex).
71 | 6. Handle classes renamed by bitstyles by adding new clauses of the `classname/2` function in [`BitstylesPhoenix.Bitstyles`](../lib/bitstyles_phoenix/bitstyles.ex).
72 | 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).
73 | 8. Run `mix test`. Fix all doctests until `mix test` succeeds. Add new expected values for the new version, and keep the current expected value for the previous version (see [multi-version doctest example](#multi-version-doctest-example))
74 | 9. Run `mix docs` to preview components after making changes to them.
75 | 10. Use the [versions showcase](#versions-showcase) to test that you didn't break anything for older bitstyles versions.
76 |
77 | ### An example of a bitstyles version check in a component
78 |
79 | ```elixir
80 | def ui_tricky_component(assigns) do
81 | version = BitstylesPhoenix.Bitstyles.Version.version()
82 |
83 | if version >= {5,0,0} do
84 | ~H"""
85 |
...
86 | """
87 | else
88 | ~H"""
89 |
90 |
91 | ...
92 |
93 |
94 | """
95 | end
96 | end
97 | ```
98 |
99 | ## Adding new components
100 |
101 | 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.
102 |
103 | 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).
104 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix/component/icon.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix.Component.Icon do
2 | use BitstylesPhoenix.Component
3 | import BitstylesPhoenix.Component.UseSVG
4 |
5 | @moduledoc """
6 | An SVG icon system, that expects the icons to be present on the page, rendered as SVG ``s.
7 | """
8 |
9 | @doc ~S"""
10 | Renders an icon element.
11 |
12 | This uses `BitstylesPhoenix.Component.UseSVG` to render an icon either inlined in the page or
13 | referenced in an external SVG file. Icons are assumed to have an id prefixed with `icon-` followed
14 | by the name of the icon, which is used to reference the icon.
15 |
16 | ## Attributes
17 |
18 | - `name` *(required)* - The name of the icon. Assumes icons are prefixed with `icon-`.
19 | - `size` - Specify the icon size to use. Available sizes are specified in CSS, and default to `s`, `m`, `l`, `xl`. If you do not specify a size, the icon will fit into a `1em` square.
20 | - `file` - To be set if icons should be loaded from an external resource (see `BitstylesPhoenix.Component.UseSVG.ui_svg/1`).
21 | This can also be configured to a default `icon_file`, see `BitstylesPhoenix` for config options. With the configuration present, inline icons can still be rendered with `file={nil}`.
22 | - `class` - Extra classes to pass to the svg. See `BitstylesPhoenix.Helper.classnames/1` for usage.
23 |
24 | See the [bitstyles icon docs](https://bitcrowd.github.io/bitstyles/?path=/docs/atoms-icon) for examples of icon usage, and available icons in the [bitstyles icon set](https://bitcrowd.github.io/bitstyles/?path=/docs/ui-data-icons).
25 | """
26 |
27 | story(
28 | "An icon (from inline svg)",
29 | """
30 | iex> assigns = %{}
31 | ...> render ~H\"""
32 | ...> <.ui_icon name="inline-arrow"/>
33 | ...> \"""
34 | """,
35 | """
36 | \"""
37 |
41 | \"""
42 | """,
43 | extra_html: """
44 |
49 | """
50 | )
51 |
52 | story(
53 | "An icon with a size",
54 | """
55 | iex> assigns = %{}
56 | ...> render ~H\"""
57 | ...> <.ui_icon name="hamburger" file="/assets/icons.svg" size="xl"/>
58 | ...> \"""
59 | """,
60 | """
61 | \"""
62 |
66 | \"""
67 | """
68 | )
69 |
70 | story(
71 | "An icon with extra options",
72 | """
73 | iex> assigns = %{}
74 | ...> render ~H\"""
75 | ...> <.ui_icon name="bin" file="/assets/icons.svg" class="foo bar"/>
76 | ...> \"""
77 | """,
78 | """
79 | \"""
80 |
84 | \"""
85 | """
86 | )
87 |
88 | def ui_icon(assigns) do
89 | icon = "icon-#{assigns.name}"
90 |
91 | class =
92 | classnames([
93 | "a-icon",
94 | {"a-icon--#{assigns[:size]}", assigns[:size] != nil},
95 | assigns[:class]
96 | ])
97 |
98 | extra =
99 | assigns
100 | |> assigns_to_attributes([:class, :name, :size])
101 | |> put_defaults
102 |
103 | assigns = assign(assigns, extra: extra, class: class, icon: icon)
104 |
105 | ~H"""
106 | <.ui_svg use={@icon} class={@class} aria-hidden="true" focusable="false" {@extra} />
107 | """
108 | end
109 |
110 | @default_size 16
111 | defp put_defaults(opts) do
112 | opts
113 | |> Keyword.put_new(:width, @default_size)
114 | |> Keyword.put_new(:height, @default_size)
115 | |> put_icon_file(Application.get_env(:bitstyles_phoenix, :icon_file, :inline))
116 | end
117 |
118 | defp put_icon_file(opts, :inline), do: opts
119 |
120 | defp put_icon_file(opts, file) when is_binary(file) do
121 | Keyword.put_new(opts, :file, file)
122 | end
123 |
124 | defp put_icon_file(opts, {module, function, arguments}) do
125 | file = apply(module, function, arguments)
126 | put_icon_file(opts, file)
127 | end
128 |
129 | defp put_icon_file(opts, {module, function}) do
130 | file = apply(module, function)
131 | put_icon_file(opts, file)
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix/showcase.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix.Showcase do
2 | @moduledoc false
3 |
4 | import Phoenix.Component, only: [sigil_H: 2]
5 | alias Phoenix.HTML.Safe
6 |
7 | defmacro story(name, doctest_iex_code, doctest_expected_results, opts \\ []) do
8 | default_version =
9 | BitstylesPhoenix.Bitstyles.Version.default_version_string()
10 | |> String.to_atom()
11 |
12 | doctest_expected_results =
13 | if is_list(doctest_expected_results) && is_tuple(List.first(doctest_expected_results)) do
14 | doctest_expected_results
15 | else
16 | [{default_version, doctest_expected_results}]
17 | end
18 |
19 | doctest_expected_result_default_version =
20 | Keyword.get(doctest_expected_results, default_version)
21 |
22 | if doctest_expected_result_default_version == nil do
23 | raise """
24 | The third argument in the story/4 macro, the expected doctest result, must be a string.
25 | It could also be a keyword list of bitstyles versions to expected doctest result, with a required key of #{inspect(default_version)} (default version)
26 | """
27 | end
28 |
29 | code =
30 | doctest_expected_result_default_version
31 | |> to_string()
32 | |> String.split("\n")
33 | |> Enum.map_join("\n", &String.trim/1)
34 |
35 | storydoc = """
36 | ## #{name}
37 |
38 | #{sandbox(code, opts)}
39 |
40 | #{generate_doctests(doctest_iex_code, doctest_expected_results, default_version)}
41 | """
42 |
43 | extra_html = Keyword.get(opts, :extra_html)
44 |
45 | storydoc =
46 | if extra_html && Keyword.get(opts, :show_extra_html, true) do
47 | storydoc <>
48 | """
49 | *Requires additional content on the page:*
50 |
51 | ```
52 | #{extra_html}
53 | ```
54 | """
55 | else
56 | storydoc
57 | end
58 |
59 | if Keyword.get(opts, :module, false) do
60 | quote do
61 | @moduledoc @moduledoc <> unquote(storydoc)
62 | end
63 | else
64 | quote do
65 | @doc @doc <> unquote(storydoc)
66 | end
67 | end
68 | end
69 |
70 | @default_iframe_style """
71 | height:1px; \
72 | border:none; \
73 | overflow:hidden; \
74 | padding-left: 1em; \
75 | """
76 |
77 | defp sandbox(code, opts) do
78 | extra_html = Keyword.get(opts, :extra_html, "")
79 | transparent = Keyword.get(opts, :transparent, true)
80 | {result, _} = Code.eval_string(code)
81 | dist = BitstylesPhoenix.Bitstyles.cdn_url()
82 |
83 | style =
84 | if transparent do
85 | """
86 | html{ \
87 | background-color: transparent !important; \
88 | } \
89 | \
90 | @media (prefers-color-scheme: dark) { \
91 | body {color: #fff; } \
92 | } \
93 | """
94 | else
95 | ""
96 | end
97 |
98 | iframe_opts =
99 | [
100 | srcdoc:
101 | ~s(#{Enum.join([extra_html, result]) |> String.replace("\n", "")}),
102 | style: "",
103 | allowtransparency: if(transparent, do: "true", else: "false")
104 | ]
105 | |> Keyword.merge(style_opts(opts))
106 |
107 | assigns = %{iframe_opts: iframe_opts}
108 |
109 | if dist do
110 | ~H"""
111 |
112 | """
113 | else
114 | ~H""
115 | end
116 | |> Safe.to_iodata()
117 | |> IO.iodata_to_binary()
118 | end
119 |
120 | defp style_opts(opts) do
121 | width = Keyword.get(opts, :width, "auto")
122 |
123 | Keyword.get(opts, :height)
124 | |> case do
125 | nil ->
126 | [
127 | style: "#{@default_iframe_style}width: #{width}",
128 | # https://stackoverflow.com/questions/819416/adjust-width-and-height-of-iframe-to-fit-with-content-in-it
129 | onload: """
130 | javascript:(function(o) { \
131 | o.style.height=(o.contentWindow.document.body.scrollHeight + 25)+"px"; \
132 | }(this)); \
133 | """
134 | ]
135 |
136 | height ->
137 | [style: "#{@default_iframe_style}height: #{height}; width: #{width};"]
138 | end
139 | end
140 |
141 | defp generate_doctests(doctest_iex_code, doctest_expected_results, default_version) do
142 | # it's crucial that there is exactly one new line after the iex code,
143 | # if there's more, it would be an always-green doctest without an expected result
144 | doctest_iex_code = doctest_iex_code |> to_string() |> String.trim_trailing()
145 |
146 | # when running unit tests, we want to test examples for all specified versions
147 | # when generating docs, we want to only to show the snippets for the default version
148 | if Mix.env() === :test do
149 | doctest_expected_results
150 | |> Enum.map_join("\n\n", fn {version, expected_result} ->
151 | """
152 | iex> Process.put(:bitstyles_phoenix_bistyles_version, "#{version}")
153 | #{doctest_iex_code |> String.replace("iex>", "...>")}
154 | #{expected_result}
155 | """
156 | end)
157 | else
158 | """
159 | #{doctest_iex_code}
160 | #{Keyword.get(doctest_expected_results, default_version)}
161 |
162 | """
163 | end
164 | end
165 | end
166 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix do
2 | @moduledoc """
3 | Documentation for BitstylesPhoenix.
4 |
5 | ## Setup & Getting started
6 |
7 | Check out the main [README](README.md).
8 |
9 | ## Usage
10 |
11 | Use by either `use BitstylesPhoenix` or importing the components and helpers individually.
12 |
13 | ```elixir
14 | use BitstylesPhoenix
15 |
16 | # or
17 |
18 | import BitstylesPhoenix.Helper.Button
19 | ```
20 |
21 | Then use the components and helpers in your `*.heex` templates:
22 |
23 | ```elixir
24 | <.ui_badge>A nice badge
25 | # => A nice badge
26 | ```
27 |
28 | ```elixir
29 | ui_button("Save", type: "submit")
30 | # =>
31 | ```
32 |
33 | Some components require additional JS to work as intended. Either you implement the JS yourself
34 | and use the ui components directly, or the `Live` or `Alpine3` ones.
35 | ```
36 | use BitstylesPhoenix.Live # import all `BitstylesPhoenix.Live` components
37 | use BitstylesPhoenix.Alpine3 # import all `BitstylesPhoenix.Alpine3` components
38 | ```
39 |
40 | For the Alpine versions to work, [alpine.js](https://alpinejs.dev/) should be present on the page.
41 | The [JS commands](https://hexdocs.pm/phoenix_live_view/bindings.html#js-commands) version will only work within connected LiveViews.
42 |
43 | Checkout the components and helpers for examples and showcases.
44 |
45 | ## Configuration
46 |
47 | The library can be configured through `mix` configuration.
48 |
49 | ### Bitstyles version
50 |
51 | BitstylesPhoenix will generate classes #{BitstylesPhoenix.Bitstyles.Version.version_string()} of bitstyles.
52 | You can set older versions with a configuration:
53 |
54 | ```elixir
55 | config :bitstyles_phoenix,
56 | bitstyles_version: "1.5.0"
57 | ```
58 | Release candidate versions are currently not supported.
59 |
60 | ### Translating error messages
61 |
62 | 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)
63 | using `gettext`. This helper can be used to translate error messages rendered with `bitstyles_phoenix`.
64 |
65 | In order to do so you can configure the generated helper or write your own with the MFA configuration.
66 |
67 | ```elixir
68 | config :bitstyles_phoenix,
69 | translate_errors: {ExampleWeb.ErrorHelpers, :translate_error, []}
70 | ```
71 |
72 | ### Icon file
73 |
74 | In order for the bitstyles icons to properly render they can either be rendered inline in the document or served as an SVG file
75 | and configured with the phoenix static_path helpers. The configured icon file path can be either a string or a MF/MFA.
76 |
77 | Example:
78 | ```
79 | config :bitstyles_phoenix,
80 | icon_file: {MyappWeb.Endpoint, :static_path, ["/assets/images/icons.svg"]}
81 | ```
82 |
83 | In order for the above example to work, one has to serve the bitstyles icon file under that static path.
84 | This can be done either via a webpack file-loader, postcss or simply by copying the files into the `priv/static` dir of
85 | your phoenix app.
86 |
87 | Check out the [demo project](#{Mix.Project.config()[:source_url]}/tree/main/demo) folder in the sources
88 | for an example configuration.
89 |
90 | ### Trim `e2e-` classnames
91 |
92 | Sometimes you need to set classes for testing, that are not needed or used in production.
93 | By default, all classes generated by bitstyles_phoenix are appended using `BitstylesPhoenix.Helper.Classnames.classnames/1`.
94 | This helper trims classes prefixed with `e2e` by default. This behaviour can be configured:
95 |
96 | Disable trimming
97 | ```elixir
98 | config :bitstyles_phoenix,
99 | trim_e2e_classes: [enabled: false]
100 | ```
101 |
102 | Use another prefix for trimming
103 | ```elixir
104 | config :bitstyles_phoenix,
105 | trim_e2e_classes: [enabled: true, prefix: "test-"]
106 | ```
107 |
108 | ### Defining a required label
109 |
110 | You can control how required labels are rendered. By default you will get this appended to your labels if the input is required:
111 | ```html
112 | *
113 | ```
114 |
115 | You can override it by specifying your own component to render the required labels. The component will get the form and the field as assigns, as well as any optional parameters you pass in the MFA as `@opts`.
116 |
117 | ```elixir
118 | config :bitstyles_phoenix,
119 | required_label: {MyComponent, :my_required_label, []}
120 |
121 | defmodule MyComponent do
122 | use Phoenix.Component
123 |
124 | def my_required_label(assigns) do
125 | ~H"(required)"
126 | end
127 | end
128 | ```
129 |
130 | """
131 |
132 | defmacro __using__(_) do
133 | quote do
134 | import BitstylesPhoenix.Component.Avatar
135 | import BitstylesPhoenix.Component.Badge
136 | import BitstylesPhoenix.Component.Breadcrumbs
137 | import BitstylesPhoenix.Component.Button
138 | import BitstylesPhoenix.Component.Card
139 | import BitstylesPhoenix.Component.Content
140 | import BitstylesPhoenix.Component.DescriptionList
141 | import BitstylesPhoenix.Component.Dropdown
142 | import BitstylesPhoenix.Component.Error
143 | import BitstylesPhoenix.Component.Flash
144 | import BitstylesPhoenix.Component.Form
145 | import BitstylesPhoenix.Component.Heading
146 | import BitstylesPhoenix.Component.Icon
147 | import BitstylesPhoenix.Component.Modal
148 | import BitstylesPhoenix.Component.Sidebar
149 | import BitstylesPhoenix.Component.Tabs
150 | import BitstylesPhoenix.Component.UseSVG
151 |
152 | import BitstylesPhoenix.Helper.Classnames
153 | end
154 | end
155 | end
156 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix/component/tabs.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix.Component.Tabs do
2 | use BitstylesPhoenix.Component
3 |
4 | import BitstylesPhoenix.Component.Button
5 |
6 | @moduledoc """
7 | The Tabs component.
8 | """
9 |
10 | @doc ~s"""
11 | Render a tab navigation header, optionnally with selected tabs.
12 |
13 | ## Attributes
14 |
15 | - `variant` — Variant of the badge you want, from those available in the CSS classes e.g. `brand-1`, `danger`
16 | - `class` - Extra classes to pass to the badge. See `BitstylesPhoenix.Helper.classnames/1` for usage.
17 | - `active` - A value that represents the active tab. In order to be active, the tab has to have a `ref` attribute equal to this value.
18 | - All other attributes are passed to the `span` tag.
19 |
20 | ## Attributes - `tab` slot
21 | - `ref` - Sets the `active` attribute for the tab button if the parent `active` attribute matches it's value.
22 | - `show` - If the tab should be rendered. Defaults to `true`.
23 | - All other attributes are passed to `ui_tab_button/1`
24 |
25 | See [bitstyles badge docs](https://bitcrowd.github.io/bitstyles/?path=/docs/atoms-badge--badge) for examples, and for the default variants available.
26 | """
27 |
28 | story(
29 | "Tabs without active tab",
30 | """
31 | iex> assigns = %{}
32 | ...> render ~H\"""
33 | ...> <.ui_tabs>
34 | ...> <:tab>Foo
35 | ...> <:tab>Bar
36 | ...> <:tab>Baz
37 | ...> <:tab show={false}>Hidden
38 | ...>
39 | ...> \"""
40 | """,
41 | """
42 | \"""
43 |
187 | """
188 | end
189 | end
190 |
--------------------------------------------------------------------------------
/lib/bitstyles_phoenix/component/flash.ex:
--------------------------------------------------------------------------------
1 | defmodule BitstylesPhoenix.Component.Flash do
2 | use BitstylesPhoenix.Component
3 |
4 | import BitstylesPhoenix.Component.Content
5 | alias BitstylesPhoenix.Bitstyles
6 |
7 | @moduledoc """
8 | Component for building flash messages.
9 | """
10 |
11 | @doc ~s"""
12 | Render a flash message.
13 |
14 | Inform the user of a global or important event, such as may happen after a page reload.
15 | There are several variants, which use color, iconography, and html attributes to indicate severity.
16 |
17 | ## Attributes
18 |
19 | - `variant` - specifies which visual variant of flash you want, from those available in CSS.
20 | Defaults include: `brand-1`, `warning`, `info`, `danger`
21 | Additionally you can pass `full` as a variant that will be set on the content class.
22 | Variants can be passed in as binary, atoms or as a list of atoms/binaries.
23 | - `class` - Set CSS classes on the outer div.
24 | - `content_class` - Set CSS classes on the content div.
25 | - All other attributes are passed to the outer `div` tag.
26 |
27 | See [https://bitcrowd.github.io/bitstyles/?path=/docs/ui-flashes--flash-brand-1](https://bitcrowd.github.io/bitstyles/?path=/docs/ui-flashes--flash-brand-1)
28 | """
29 |
30 | story(
31 | "Flash brand 1",
32 | """
33 | iex> assigns = %{}
34 | iex> render(~H\"""
35 | ...> <.ui_flash variant="brand-1">
36 | ...> Something you may be interested to hear
37 | ...>
38 | ...> \""")
39 | """,
40 | [
41 | "6.0.0": """
42 | \"""
43 |
118 | \"""
119 | """,
120 | module: true,
121 | width: "100%"
122 | )
123 |
124 | @doc ~s"""
125 | Render a description list.
126 |
127 | ## Attributes
128 |
129 | - `class` - Extra classes to pass to the `dl` tag.
130 | See `BitstylesPhoenix.Helper.classnames/1` for usage.
131 | - All other attributes are passed to the `dl` tag.
132 |
133 | See [bitstyles description list docs](https://bitcrowd.github.io/bitstyles/?path=/docs/ui-data-description-list--description-list) for examples.
134 | """
135 |
136 | def ui_dl(assigns) do
137 | extra = assigns_to_attributes(assigns, [:class])
138 | assigns = assign(assigns, extra: extra)
139 |
140 | ~H"""
141 |
142 | <%= render_slot(@inner_block) %>
143 |
144 | """
145 | end
146 |
147 | @doc ~s"""
148 | Render a description list item.
149 |
150 | ## Attributes
151 |
152 | - `label` - If set renders two tags `ui_dt/1` with the contents of the attribute and `ui_dd/1` with the inner contents of the component.
153 | If you need to set more custom content on the `ui_dt/1` you can omit this attribute, and provide `ui_dt/1` and `ui_dd/1` yourself
154 | in the inner conten.
155 | - `value` - If `label` is set, a `value` can be specified instead of using the inner content of the component.
156 | - `class` - Extra classes to pass to the `div` tag. See `BitstylesPhoenix.Helper.classnames/1` for usage.
157 | - All other attributes are passed to the `div` tag.
158 | """
159 | def ui_dl_item(assigns) do
160 | extra = assigns_to_attributes(assigns, [:class, :label, :value])
161 |
162 | # u-gap-l and u-gap-xl in 5.0.0 are equivalent to respectively u-gap-m and u-gap-l in 4.3.0
163 | gap_class =
164 | if Bitstyles.Version.version() >= {5, 0, 0} do
165 | "u-gap-l"
166 | else
167 | "u-gap-m"
168 | end
169 |
170 | assigns =
171 | assigns
172 | |> assign(extra: extra)
173 | |> assign(gap_class: gap_class)
174 |
175 | ~H"""
176 |
`s they are now the direct children of the `div` and have a `role="tab"` set. As a side effect the gap between the tabs is now a little smaller.
9 | - Fix compiler warnings for elixir 17+ and upgrade dependencies.
10 |
11 | ## v2.5.0 - 2024-09-25
12 |
13 | - Switched bitstyles version comparisons from string comparisons to tuple comparisons, which will ensure correct output if bitstyles version ever contains a number above 9.
14 | - Modified the process of downgrading classnames to the target bitstyles version by chaining renames. This is necessary preparation work to support bitstyles `v6.0.0`.
15 | - Added support for bitstyles `v6.0.0`. You can continue using bitstyles_phoenix with a lower bitstyles version, or migrate your codebase to bitstyles `v6.0.0`.
16 |
17 | ### How to migrate to bitstyles `v6.0.0`
18 |
19 | Follow the [bitstyles changelog](https://github.com/bitcrowd/bitstyles/blob/main/CHANGELOG.md#600---2023-06-08) for version 6.0.0.
20 |
21 | There are no changes to bitstyles_phoenix component APIs.
22 |
23 | ## v2.4.0 - 2024-07-18
24 |
25 | - Added support for phoenix_html `v4`. You can also continue using phoenix_html `v3`. To replace `Phoenix.HTML.Form` input helpers removed in phoenix_html `v4`, new components `ui_raw_input` and `ui_raw_label` were added.
26 | - Added support for bitstyles `v5.0.0`. You can continue using bitstyles_phoenix with a lower bitstyles version, or migrate your codebase to bitstyles `v5.0.0`.
27 |
28 | ### How to migrate to bitstyles `v5.0.0`
29 |
30 | Follow the [bitstyles changelog](https://github.com/bitcrowd/bitstyles/blob/main/CHANGELOG.md#500---2023-01-03) for versions 5.0.0 and 5.0.0-alpha-1. Even if you're using the `BitstylesPhoenix.Helper.Classnames.classnames/1` helper to apply bitstyles classes in your own codebase, you will still need to migrate some of them yourself. For example, the class `u-gap-m` could not have been migrated via the helper because it exists in both bitstyles versions with different meanings (`u-gap-m` and `u-gap-l` in bitstyles 4.3.0 are equivalent to `u-gap-l` and `u-gap-xl` in bitstyles `v5.0.0`).
31 |
32 | The `variant` attribute of the `Button` component is deprecated in bitstyles `v5.0.0`. Use the attributes `shape` and `color` instead.
33 |
34 | ## v2.3.1 - 2023-10-23
35 |
36 | - Bump LiveView.
37 |
38 | ## v2.3.0 - 2023-05-15
39 |
40 | - Add `Modal` component.
41 |
42 | ## v2.2.0 - 2023-03-20
43 |
44 | - Updated to LiveView 0.18.18.
45 | - Add `Card` and `Avatar` components.
46 |
47 | ## v2.1.1 - 2022-12-02
48 |
49 | - Fixed version backwards compatibility.
50 |
51 | ## v2.1.0 - 2022-12-02
52 |
53 | ### Changed
54 |
55 | - Add `heading_class` option to `ui_section_title` to set classes on the heading
56 | - `ui_dl_items` now aligns the items to the baseline (following the Bitstyles examples)
57 | - Updated to bitstyles `v4.3.0`.
58 |
59 | ### Added
60 |
61 | - Inputs now render a required label *. This can be configured via `required_label` config. If you do not want this new behaviour, define an empty component as required label.
62 |
63 | ### Breaking
64 |
65 | - When `ui_button` is disabled it always renders a button now instead of a link with a disabled property. In most cases this should be fine, but it could break e2e tests that check for links or similar things.
66 |
67 | ## v2.0.0 - 2022-11-12
68 |
69 | Since there is quite some changes in liveview 0.18.0 mainly about link helpers this breaks with the existing API for the `ui_button` and
70 | `ui_icon_button` components quite a lot. Since the underlying phoenix helper for generating links is now available as component as well,
71 | the support for the `ui_button` as helper is dropped completely in favor of components.
72 |
73 | ### Breaking
74 |
75 | - Updated to LiveView 0.18.X
76 | - Removed BitstylesPhoenix.Helpers.Button
77 | - Removed `ui_button/2` helper
78 | -> Use the `ui_button` component
79 | - Removed `ui_icon_button/3` helper
80 | -> Use the `ui_icon_button` component
81 | - `ui_button` component now acts as a wrapper for Phoenix.Component.link
82 | - Removed `link_fn` on `ui_button` component, since it's main use-case is now deprecated.
83 |
84 | ### How to update:
85 |
86 | - Upgrade to LiveView > 0.18
87 | `<%= ui_button("some label", to: some_path) %>` => `<.ui_button href={some_path}>some label`
88 | `<.ui_button to={some_path} %>some label` => `<.ui_button href={some_path}>some label`
89 | `<.ui_button to={some_path} link_fn={&live_redirect/2}%>some label` => `<.ui_button navigate={some_path}>some label`
90 | `<.ui_button to={some_path} link_fn={&live_patch/2}%>some label` => `<.ui_button patch={some_path}>some label`
91 | `<%= ui_icon_button("some label", to: some_path) %>` => `<.ui_icon_button href={some_path}>some label`
92 | ...
93 |
94 | ## v1.0.0 - 2022-01-04
95 |
96 | This version breaks with the existing API quite a lot 🔥, since we changed the library to take advantage of the recent developments in Phoenix and LiveView.
97 |
98 | ### Breaking
99 |
100 | All `ui_*` component helpers are now instead [HEEx function components](https://hexdocs.pm/phoenix/views.html#html-components). They will expect the options and arguments
101 | now through component attributes. The only exception is `ui_button`, which still delegate to the link_helper given via `link_fn`, but is also available as component.
102 | In order to migrate to the new components update to Phoenix 1.6.0 and LiveView 1.17.0 and change all templates from
103 | `*.html.eex` to `*.html.heex` to be able to use the new component syntax. After that you can change your previous `ui_*` helpers to use the new syntax:
104 |
105 | `<%= ui_badge("foo", variant: "warning") %>` => `<.ui_badge variant: "warning">foo`
106 |
107 | If you have contexts, where you do not want to use `heex` templates yet, you can call the functions via `Phoenix.LiveView.Helpers.component/2`.
108 |
109 | Below is a list of changes that happened besides the componentization:
110 |
111 | - Renamed `ui_error_tag` to `ui_error`
112 | - `ui_input` dropped `datetime` input type (was not working anyways)
113 | - `ui_input` dropped `radio` input type (use `ui_unwrapped_input` with `radio_button` instead)
114 | - `ui_input` dropped `textarea` input type (use `ui_textarea` instead)
115 | - Removed `ui_time/2` without replacement for now
116 | - Removed `xclassnames/1`. Use `classnames/1` from the same module instead.
117 | - `classnames/1` now returns `false` instead of empty string when there is no class set.
118 | - Removed `BitstylesPhoenix.Components` module. Instead of `use BitstylesPhoenix.Components` do `use BitstylesPhoenix`.
119 | - Removed all `e2e_classname` options. Use `class` instead, which will trim the e2e classes by default (like before).
120 | - Changed `trim_e2e_classes` config. In order to migrate change the following
121 | ```
122 | config :bitstyles_phoenix, :trim_e2e_classes, false
123 | ```
124 | =>
125 | ```
126 | config :bitstyles_phoenix, :trim_e2e_classes, [enabled: false]
127 | ```
128 |
129 | ### Added
130 |
131 | - All components now accept extra attributes that are passed on to the outermost parent attribute.
132 | - Config option to configure `classnames/1` prefixes to remove other prefixes than `e2e-` instead (e.g. `test-`).
133 | - Backwards compatibility option for different versions of `bitstyles` (see `bitstyles_version` config option)
134 | - The `BitstylesPhoenix.Icon` components `file` attribute can now get a default value via `icon_file` application config.
135 | - `ui_dropdown` and `ui_js_dropdown` (Live/Alpine3) components
136 | - `BitstylesPhoenix.Live` and `BitstylesPhoenix.Alpine3` for quick imports of the live and alpine3 components
137 | - Integration tests and example configuration with the `demo` app
138 | - Added `ui_icon_button/2` & `ui_icon_button/3` as helpers.
139 | - Added various new components: `Button`, `Tabs`, `Breadcrumbs`, `Sidebar`, `Dropdown`, `Content` ...
140 | - Added `icon` option to `ui_button`.
141 | - Added support for `:datetime` type in `ui_input`, rendered as Browser-native datetime input element
142 |
143 | ### Changed
144 |
145 | - Added dependency to `phoenix_live_view` >= 1.17.0 (for using `sigil_H/1` and new component syntax)
146 | - Doctest now use `floki` to prettify the output HTML, so docs will be a nicer read.
147 | - `classnames/1` is now imported by default with `use BitstylesPhoenix`
148 | - Updated to bitstyles `v3.0.0`
149 | - `ui_errors` now uses larger padding
150 |
151 | ## v0.8.0
152 |
153 | ### Changed
154 |
155 | - Updated `phoenix_html` dependency to `~> 3`. See [its changelog](https://github.com/phoenixframework/phoenix_html/blob/master/CHANGELOG.md) for backwards incompatible changes.
156 |
157 | ## v0.7.0
158 |
159 | - Added `link_fn` option in `ui_button` to support LiveView's `live_patch`/`live_redirect`
160 |
161 | ## v0.6.0
162 |
163 | ### Changed
164 |
165 | - Updated dependencies
166 |
167 | ### Breaking
168 |
169 | - Update to bitstyles 1.5.0 (Update flash component padding)
170 |
171 | ## v0.5.1
172 |
173 | ### Fixed
174 |
175 | - Fix `use BitstylesPhoenix.Components` (see https://github.com/bitcrowd/bitstyles_phoenix/pull/34)
176 |
177 | ## v0.5.0
178 |
179 | ### Added
180 |
181 | - Better documentation for form helpers
182 |
183 | ### Changes
184 |
185 | - Multiple errors on ones field are now rendered as a list
186 |
187 | ### Breaking/Bugfix
188 |
189 | - Do not forward options from form helpers to each `label` anymore, but instead pass options through `label_opts`.
190 | - Drop `hidden` and `reset` input types for `ui_input`.
191 |
192 | ### Deprecations
193 |
194 | - deprecated `ui_error/2` in favour of the new `ui_errors/2`
195 |
196 | ## v0.4.0
197 |
198 | ### Added
199 |
200 | - `BitstylesPhoenix.UseSVG.ui_svg/2` to display inline svg references with `