Current section

48 Versions

Jump to

Compare versions

29 files changed
+1550 additions
-1761 deletions
  @@ -1,8 +1,34 @@
1 1 # Changelog
2 2
3 - ## 0.3.2
3 + ## 0.4.0
4 4
5 - Replaced a couple more underscores.
5 + ### Improvements
6 +
7 + - The open state of menus and dialogs can now be maintained when used inside forms - see [Menus and dialogs](doc-extra/menus-and-dialogs.md).
8 + - Improved validation message logic.
9 + - Updated components:
10 + - `theme_menu_options`: added attr `update_theme_event`: the event name to be called for updating the theme.
11 + - `radio_group`: added to slot `radio_button` the attr `label` to set a custom label.
12 + - Updated `@primer/css` to `21.0.7`.
13 +
14 + ### Breaking changes
15 + - Removed functions related to using session for theme state - see `PrimerLive.Theme` for alternatives. Removed:
16 + - `ThemeSessionController`
17 + - `ThemeEvent`
18 + - Theme hook
19 + - IDs of checkboxes and radio buttons have been updated to only include valid characters.
20 +
21 + ### Deprecated
22 +
23 + - For all menu components, including 'dialog' and 'drawer': passing prompt options to the `toggle` slot is replaced by passing `prompt_options` to the main component.
24 + - In the `drawer` component, replace the subcomponent `drawer_content` with the slot `body`.
25 + - This allows the focus wrap ID to be derived from the drawer's 'id' attribute, similar to how it is done for 'dialog'.
26 + - When using the previous syntax, a warning message will be shown in the shell.
27 +
28 + ### Other changes
29 +
30 + - The HTML structure and some of the CSS classes of `action_menu`, `dropdown_menu` and `select_menu` have changed. Instead of `<details>` and `<summary>` elements, the open state is now controlled with `<input type="checkbox">` and `<label>`.
31 + - HTML attributes are sorted alphabetically.
6 32
7 33 ## 0.3.1
  @@ -1,11 +1,28 @@
1 1 # PrimerLive
2 2
3 - PrimerLive is a collection of function components that implements <a href="https://primer.style/" target="_blank">GitHub's Primer Design System</a>. It is intended for usage in <a href="https://github.com/phoenixframework/phoenix_live_view" target="_blank">Phoenix LiveView pages</a> and regular (non-LiveView) views in Phoenix applications.
4 -
5 - PrimerLive components can be used in Phoenix LiveView pages and regular (non-LiveView) views in Phoenix applications.
3 + <p>
4 + An implementation of GitHub's <a href="https://primer.style/design/" target="blank">Primer Design System</a> for Phoenix LiveView.
5 + </p>
6 6
7 + <p>
7 8 The Primer Design System provides a strong base for creating data driven applications such as rich CRUD applications with interactive forms.
9 + </p>
10 +
11 + <p>
12 + All PrimerLive components accept the <code>class</code> attribute for customisations (and often the <code>classes</code> struct to address inner elements). <a href="https://primer.style/design/foundations/css-utilities/getting-started" target="blank">Primer's utility classes</a> allow a wide range of customisations without having to write custom styles.
13 + </p>
14 +
15 + <p>
16 + PrimerLive components can be used in <a href="https://github.com/phoenixframework/phoenix_live_view" target="_blank">Phoenix LiveView pages</a> and regular (non-LiveView) views in Phoenix applications.
17 + </p>
18 +
19 + ## Documentation
8 20
9 21 - [Documentation at Hexdocs](https://hexdocs.pm/primer_live/)
10 - - [Storybook and examples](https://primer-live.org/)
11 - - [Source code](https://github.com/ArthurClemens/primer_live)
22 + - [Installation](doc-extra/installation.md)
23 + - [Usage](doc-extra/usage.md)
24 +
25 + ## Resources
26 +
27 + - [PrimerLive Storybook and Demo](https://primer-live.org)
28 + - [Source code](https://github.com/ArthurClemens/primer_live)
\ No newline at end of file
  @@ -1,46 +1,41 @@
1 - {<<"app">>,<<"primer_live">>}.
2 - {<<"build_tools">>,[<<"mix">>]}.
3 - {<<"description">>,
4 - <<"A collection of function components that implements GitHub's Primer Design System.">>}.
5 - {<<"files">>,
6 - [<<"lib">>,<<"lib/field_state.ex">>,<<"lib/component.ex">>,<<"lib/theme">>,
7 - <<"lib/theme/theme_session_controller.ex">>,<<"lib/theme/theme.ex">>,
8 - <<"lib/theme/theme_event.ex">>,<<"lib/primer_live.ex">>,<<"lib/helpers">>,
9 - <<"lib/helpers/form_helpers.ex">>,<<"lib/helpers/attribute_helpers.ex">>,
10 - <<"lib/helpers/octicons_helper.ex">>,<<"lib/helpers/schema_helpers.ex">>,
11 - <<"lib/helpers/test_helpers.ex">>,<<"lib/helpers/component_helpers.ex">>,
12 - <<"lib/ui_icons.ex">>,<<"lib/octicons.ex">>,<<".formatter.exs">>,
13 - <<"mix.exs">>,<<"README.md">>,<<"LICENSE.md">>,
14 - <<"priv/octicon_builder/build.exs">>,<<"priv/static">>,
15 - <<"priv/static/primer-live.cjs.js.map">>,<<"priv/static/primer-live.css">>,
16 - <<"priv/static/session.d.ts">>,<<"priv/static/primer-live.esm.js">>,
17 - <<"priv/static/primer-live.cjs.js">>,<<"priv/static/primer-live.min.css">>,
18 - <<"priv/static/primer-live.min.js">>,<<"priv/static/primer-live.js">>,
19 - <<"priv/static/index.d.ts">>,<<"priv/static/primer-live.esm.js.map">>,
20 - <<"CHANGELOG.md">>,<<"package.json">>]}.
21 - {<<"licenses">>,[<<"MIT">>]}.
22 1 {<<"links">>,
23 2 [{<<"GitHub">>,<<"https://github.com/ArthurClemens/primer_live">>}]}.
24 3 {<<"name">>,<<"primer_live">>}.
4 + {<<"version">>,<<"0.4.0">>}.
5 + {<<"description">>,
6 + <<"A collection of function components that implements GitHub's Primer Design System.">>}.
7 + {<<"app">>,<<"primer_live">>}.
8 + {<<"licenses">>,[<<"MIT">>]}.
9 + {<<"files">>,
10 + [<<"lib">>,<<"lib/field_state.ex">>,<<"lib/component.ex">>,<<"lib/theme">>,
11 + <<"lib/theme/theme.ex">>,<<"lib/primer_live.ex">>,<<"lib/helpers">>,
12 + <<"lib/helpers/form_helpers.ex">>,<<"lib/helpers/attribute_helpers.ex">>,
13 + <<"lib/helpers/octicons_helper.ex">>,<<"lib/helpers/schema_helpers.ex">>,
14 + <<"lib/helpers/component_helpers.ex">>,
15 + <<"lib/helpers/declaration_helpers.ex">>,
16 + <<"lib/helpers/prompt_declaration_helpers.ex">>,<<"lib/ui_icons.ex">>,
17 + <<"lib/octicons.ex">>,<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,
18 + <<"LICENSE.md">>,<<"priv/octicon_builder/build.exs">>,<<"priv/static">>,
19 + <<"priv/static/primer-live.cjs.js.map">>,<<"priv/static/primer-live.css">>,
20 + <<"priv/static/primer-live.esm.js">>,<<"priv/static/primer-live.cjs.js">>,
21 + <<"priv/static/prompt.d.ts">>,<<"priv/static/primer-live.min.css">>,
22 + <<"priv/static/primer-live.min.js">>,<<"priv/static/primer-live.js">>,
23 + <<"priv/static/index.d.ts">>,<<"priv/static/primer-live.esm.js.map">>,
24 + <<"CHANGELOG.md">>,<<"package.json">>]}.
25 25 {<<"requirements">>,
26 - [[{<<"app">>,<<"ecto_sql">>},
27 - {<<"name">>,<<"ecto_sql">>},
26 + [[{<<"name">>,<<"phoenix_html">>},
27 + {<<"app">>,<<"phoenix_html">>},
28 28 {<<"optional">>,false},
29 - {<<"repository">>,<<"hexpm">>},
30 - {<<"requirement">>,<<"~> 3.9">>}],
31 - [{<<"app">>,<<"phoenix_html">>},
32 - {<<"name">>,<<"phoenix_html">>},
29 + {<<"requirement">>,<<"~> 3.3">>},
30 + {<<"repository">>,<<"hexpm">>}],
31 + [{<<"name">>,<<"phoenix_live_view">>},
32 + {<<"app">>,<<"phoenix_live_view">>},
33 33 {<<"optional">>,false},
34 - {<<"repository">>,<<"hexpm">>},
35 - {<<"requirement">>,<<"~> 3.3">>}],
36 - [{<<"app">>,<<"phoenix_live_view">>},
37 - {<<"name">>,<<"phoenix_live_view">>},
34 + {<<"requirement">>,<<"~> 0.19">>},
35 + {<<"repository">>,<<"hexpm">>}],
36 + [{<<"name">>,<<"jason">>},
37 + {<<"app">>,<<"jason">>},
38 38 {<<"optional">>,false},
39 - {<<"repository">>,<<"hexpm">>},
40 - {<<"requirement">>,<<">= 0.18.3">>}],
41 - [{<<"app">>,<<"jason">>},
42 - {<<"name">>,<<"jason">>},
43 - {<<"optional">>,false},
44 - {<<"repository">>,<<"hexpm">>},
45 - {<<"requirement">>,<<"~> 1.4">>}]]}.
46 - {<<"version">>,<<"0.3.2">>}.
39 + {<<"requirement">>,<<"~> 1.4">>},
40 + {<<"repository">>,<<"hexpm">>}]]}.
41 + {<<"build_tools">>,[<<"mix">>]}.
unknownlib/component.ex
File is too large to be displayed (100 KB limit).
  @@ -1,6 +1,7 @@
1 1 defmodule PrimerLive.Helpers.AttributeHelpers do
2 2 @moduledoc false
3 3
4 + use Phoenix.Component
4 5 alias PrimerLive.Helpers.FormHelpers
5 6
6 7 @doc ~S"""
  @@ -100,6 +101,9 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
100 101 append_attributes(attr_list, input_attributes)
101 102 end
102 103
104 + def append_attributes(attributes, input_attributes) when is_nil(attributes),
105 + do: append_attributes([], input_attributes)
106 +
103 107 def append_attributes(attributes, input_attributes) do
104 108 input_attributes
105 109 |> Enum.reject(&(&1 == false || is_nil(&1)))
  @@ -107,10 +111,18 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
107 111 |> Enum.reduce(attributes, fn kw, acc ->
108 112 acc ++ kw
109 113 end)
114 + |> Enum.sort()
110 115 end
111 116
112 117 def append_attributes(input_attributes), do: append_attributes([], input_attributes)
113 118
119 + @doc ~S"""
120 + Passes arguments to Phoenix.LiveView.Helpers.assigns_to_attributes and sorts the result.
121 + """
122 + def assigns_to_attributes_sorted(assigns, exclude \\ []) do
123 + assigns_to_attributes(assigns, exclude) |> Enum.sort()
124 + end
125 +
114 126 @doc ~S"""
115 127 Converts user input to an integer, with optionally a default value
116 128
  @@ -298,9 +310,28 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
298 310 end
299 311
300 312 @doc """
301 - Generates a random string.
313 + From: https://gist.github.com/ahmadshah/8d978bbc550128cca12dd917a09ddfb7?permalink_comment_id=4178225#gistcomment-4178225
314 +
315 + A helper functions to generate a random (printable) string of given `len`.
316 + You could change the hard-coded ascii codes 32 and 126 to only
317 + uppercase letters, digits, etc...
302 318 """
303 - def random_string(), do: Ecto.UUID.generate()
319 + def random_string(len \\ 12) when is_integer(len) and len > 0 do
320 + (for _ <- 1..len, do: rand_uniform(32, 126))
321 + |> List.to_string()
322 + end
323 +
324 + # Returns a random integer uniformly distributed in the range
325 + # `n <= X <= m`.
326 + #
327 + # If the random variable `X` is uniformly distributed in the range
328 + # `1 <= X <= m - n + 1`, then r.v `Y = X + n - 1` is uniformly
329 + # distributed in the range `n <= Y <= m`.
330 + # (Because we just shift X to the right).
331 + defp rand_uniform(n, m) do
332 + :rand.uniform(m - n + 1) # random X
333 + |> Kernel.+(n - 1) # shift X to the right to get Y
334 + end
304 335
305 336 @doc """
306 337 Verifies if a slot should be handled as a link.
  @@ -496,17 +527,69 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
496 527 def input_id(_input_id, _id, _input_type, "", _value_for_derived_label), do: nil
497 528 def input_id(_input_id, _id, _input_type, input_name, _value_for_derived_label), do: input_name
498 529
530 + @spec cleanup_id(nil | binary) :: nil | binary
531 + def cleanup_id(id) when is_nil(id), do: nil
532 +
533 + def cleanup_id(id) do
534 + id
535 + |> String.replace(~r/(\]\[)|(\[\])|\[|\]/, "_")
536 + |> String.replace(~r/^(\_)*/, "")
537 + |> String.replace(~r/(\_)*$/, "")
538 + end
539 +
499 540 @doc ~S"""
500 541 Extracts common attributes for form inputs. Shared for consistency.
501 542 """
502 543 def common_input_attrs(assigns, input_type) do
544 + common_shared_attrs = common_shared_attrs(assigns)
545 +
546 + # ID and label
547 + id_attrs = common_id_attrs(assigns, input_type, common_shared_attrs)
548 +
549 + # Form group
550 + form_group_attrs = common_form_group_attrs(assigns, id_attrs.input_id, common_shared_attrs)
551 +
552 + # Field state
553 + field_state_attrs =
554 + common_field_state_attrs(
555 + assigns,
556 + id_attrs,
557 + common_shared_attrs
558 + )
559 +
560 + [
561 + common_shared_attrs,
562 + id_attrs,
563 + form_group_attrs,
564 + field_state_attrs
565 + ]
566 + |> Enum.reduce(&Map.merge/2)
567 + end
568 +
569 + defp common_shared_attrs(assigns) do
503 570 rest = assigns[:rest]
504 571 name = assigns[:name] || rest[:name]
505 - id = rest[:id]
506 572 form = assigns[:form]
507 573 field = assigns[:field]
508 574 field_or_name = field || name || ""
575 +
576 + %{
577 + rest: rest,
578 + name: name,
579 + form: form,
580 + field: field,
581 + field_or_name: field_or_name
582 + }
583 + end
584 +
585 + defp common_id_attrs(
586 + assigns,
587 + input_type,
588 + %{form: form, field: field, rest: rest, name: name}
589 + ) do
590 + id = cleanup_id(assigns[:id] || rest[:id])
509 591 is_multiple = !!assigns[:is_multiple]
592 + checked_value = assigns[:checked_value]
510 593
511 594 input_name =
512 595 if !is_nil(form) || !is_nil(field),
  @@ -515,12 +598,13 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
515 598
516 599 phx_feedback_for_id = input_name
517 600
518 - # Get value from checkbox or radio button
519 -
520 - checked_value = assigns[:checked_value]
521 601 value = assigns[:value] || rest[:value]
522 602 value_for_derived_label = checked_value || value
523 603
604 + input_id =
605 + input_id(assigns[:input_id], id, input_type, input_name, value_for_derived_label)
606 + |> cleanup_id()
607 +
524 608 derived_label =
525 609 case input_type do
526 610 :checkbox -> Phoenix.HTML.Form.humanize(value_for_derived_label || field)
  @@ -528,10 +612,20 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
528 612 _ -> nil
529 613 end
530 614
531 - input_id = input_id(assigns[:input_id], id, input_type, input_name, value_for_derived_label)
532 -
533 - # Form group
615 + %{
616 + derived_label: derived_label,
617 + input_id: input_id,
618 + id: id,
619 + input_name: input_name,
620 + phx_feedback_for_id: phx_feedback_for_id,
621 + value: value
622 + }
623 + end
534 624
625 + defp common_form_group_attrs(assigns, input_id, %{
626 + form: form,
627 + field_or_name: field_or_name
628 + }) do
535 629 form_group = assigns[:form_group]
536 630 is_form_group = assigns[:is_form_group]
537 631 has_form_group = is_form_group || form_group
  @@ -543,7 +637,16 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
543 637 for: input_id
544 638 })
545 639
546 - # Field state
640 + %{
641 + form_group_attrs: form_group_attrs,
642 + has_form_group: has_form_group
643 + }
644 + end
645 +
646 + defp common_field_state_attrs(assigns, %{input_name: input_name, input_id: input_id}, %{
647 + form: form,
648 + field_or_name: field_or_name
649 + }) do
547 650 validation_message = assigns[:validation_message]
548 651 field_state = FormHelpers.field_state(form, field_or_name, validation_message)
549 652
  @@ -553,24 +656,32 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
553 656 ignore_errors?: ignore_errors?
554 657 } = field_state
555 658
556 - has_changeset = !is_nil(field_state.changeset)
659 + has_changeset? = !is_nil(field_state.changeset)
557 660 show_message? = !!message && !ignore_errors? && assigns[:type] !== "hidden"
558 - validation_message_id = if !is_nil(field_state.message), do: "#{input_id}-validation"
661 +
662 + validation_message_id =
663 + if !is_nil(field_state.message),
664 + do:
665 + assigns[:validation_message_id] ||
666 + "#{input_id}-validation"
667 +
668 + validation_marker_class =
669 + if has_changeset? do
670 + cond do
671 + valid? == false -> "pl-invalid"
672 + valid? && show_message? -> "pl-valid"
673 + valid? && !show_message? -> "pl-neutral"
674 + end
675 + end
559 676
560 677 ## Phoenix uses phx_feedback_for to hide form field errors that are untouched.
561 678 ## However, this attribute can't be set on the element itself (the JS DOM library stalls).
562 679 ## Element "validation_marker" is used as stopgap: a separate element placed just before the input element.
563 680 validation_marker_attrs =
564 - case has_changeset do
681 + case has_changeset? && show_message? do
565 682 true ->
566 683 [
567 - phx_feedback_for: input_id,
568 - class:
569 - if valid? do
570 - "pl-valid"
571 - else
572 - "pl-invalid"
573 - end
684 + "phx-feedback-for": input_name
574 685 ]
575 686
576 687 false ->
  @@ -578,26 +689,289 @@ defmodule PrimerLive.Helpers.AttributeHelpers do
578 689 end
579 690
580 691 %{
581 - # Common
582 - rest: rest,
583 - form: form,
584 - field: field,
585 - # ID and label
586 - input_name: input_name,
587 - input_id: input_id,
588 - phx_feedback_for_id: phx_feedback_for_id,
589 - value: value,
590 - derived_label: derived_label,
591 - validation_message_id: validation_message_id,
592 - # Form group
593 - has_form_group: has_form_group,
594 - form_group_attrs: form_group_attrs,
595 - # Field state
596 692 message: message,
597 693 valid?: valid?,
598 694 ignore_errors?: ignore_errors?,
599 695 show_message?: show_message?,
600 - validation_marker_attrs: validation_marker_attrs
696 + validation_message_id: validation_message_id,
697 + validation_marker_attrs: validation_marker_attrs,
698 + validation_marker_class: validation_marker_class
699 + }
700 + end
701 +
702 + @doc ~S"""
703 + Extracts menu/dialog prompt attributes.
704 +
705 + ## Tests
706 +
707 + Default values (with attr id to prevent getting random values in tests):
708 + iex> PrimerLive.Helpers.AttributeHelpers.prompt_attrs(
709 + ...> %{
710 + ...> rest: %{
711 + ...> id: "some-id"
712 + ...> },
713 + ...> prompt_options: nil,
714 + ...> is_fast: false,
715 + ...> is_dark_backdrop: false,
716 + ...> is_medium_backdrop: false,
717 + ...> is_light_backdrop: false,
718 + ...> is_backdrop: false
719 + ...> },
720 + ...> %{
721 + ...> form: nil,
722 + ...> field: nil,
723 + ...> toggle_slot: nil,
724 + ...> toggle_class: "btn",
725 + ...> menu_class: "",
726 + ...> is_menu: true
727 + ...> })
728 + %{
729 + backdrop_attrs: [],
730 + checkbox_attrs: [{:"aria-hidden", "true"}, {:hidden_input, false}, {:id, "some-id-toggle"}, {:onchange, "window.Prompt && Prompt.change(this)"}],
731 + focus_wrap_id: "focus-wrap-some-id",
732 + menu_attrs: [class: "", "data-prompt": "", id: "some-id", "phx-hook": "Prompt"],
733 + toggle_attrs: [{:"aria-haspopup", "true"}, {:class, "btn"}, {:for, "some-id-toggle"}],
734 + touch_layer_attrs: ["data-touch": ""]
735 + }
736 +
737 + phx_click_touch:
738 + iex> PrimerLive.Helpers.AttributeHelpers.prompt_attrs(
739 + ...> %{
740 + ...> rest: %{
741 + ...> id: "some-id"
742 + ...> },
743 + ...> prompt_options: nil,
744 + ...> is_fast: false,
745 + ...> is_dark_backdrop: false,
746 + ...> is_medium_backdrop: false,
747 + ...> is_light_backdrop: false,
748 + ...> is_backdrop: false,
749 + ...> phx_click_touch: "handle_close"
750 + ...> },
751 + ...> %{
752 + ...> form: nil,
753 + ...> field: nil,
754 + ...> toggle_slot: nil,
755 + ...> toggle_class: "btn",
756 + ...> menu_class: "",
757 + ...> is_menu: true
758 + ...> })
759 + %{
760 + backdrop_attrs: [],
761 + checkbox_attrs: [{:"aria-hidden", "true"}, {:hidden_input, false}, {:id, "some-id-toggle"}, {:onchange, "window.Prompt && Prompt.change(this)"}],
762 + focus_wrap_id: "focus-wrap-some-id",
763 + menu_attrs: [class: "", "data-prompt": "", id: "some-id", "phx-hook": "Prompt"],
764 + toggle_attrs: [{:"aria-haspopup", "true"}, {:class, "btn"}, {:for, "some-id-toggle"}],
765 + touch_layer_attrs: ["data-touch": "", "phx-click": "handle_close"]
766 + }
767 +
768 + phx_click_touch with is_modal:
769 + iex> PrimerLive.Helpers.AttributeHelpers.prompt_attrs(
770 + ...> %{
771 + ...> rest: %{
772 + ...> id: "some-id"
773 + ...> },
774 + ...> prompt_options: nil,
775 + ...> is_fast: false,
776 + ...> is_dark_backdrop: false,
777 + ...> is_medium_backdrop: false,
778 + ...> is_light_backdrop: false,
779 + ...> is_backdrop: false,
780 + ...> phx_click_touch: "handle_close",
781 + ...> is_modal: true,
782 + ...> },
783 + ...> %{
784 + ...> form: nil,
785 + ...> field: nil,
786 + ...> toggle_slot: nil,
787 + ...> toggle_class: "btn",
788 + ...> menu_class: "",
789 + ...> is_menu: true
790 + ...> })
791 + %{
792 + backdrop_attrs: [],
793 + checkbox_attrs: [{:"aria-hidden", "true"}, {:hidden_input, false}, {:id, "some-id-toggle"}, {:onchange, "window.Prompt && Prompt.change(this)"}],
794 + focus_wrap_id: "focus-wrap-some-id",
795 + menu_attrs: [class: "", "data-ismodal": "", "data-prompt": "", id: "some-id", "phx-hook": "Prompt"],
796 + toggle_attrs: [{:"aria-haspopup", "true"}, {:class, "btn"}, {:for, "some-id-toggle"}],
797 + touch_layer_attrs: ["data-touch": ""]
798 + }
799 +
800 + Backdrop settings:
801 + iex> PrimerLive.Helpers.AttributeHelpers.prompt_attrs(
802 + ...> %{
803 + ...> rest: %{
804 + ...> id: "some-id"
805 + ...> },
806 + ...> prompt_options: nil,
807 + ...> is_fast: true,
808 + ...> is_dark_backdrop: false,
809 + ...> is_medium_backdrop: true,
810 + ...> is_light_backdrop: false,
811 + ...> is_backdrop: true
812 + ...> },
813 + ...> %{
814 + ...> form: nil,
815 + ...> field: nil,
816 + ...> toggle_slot: nil,
817 + ...> toggle_class: "btn",
818 + ...> menu_class: "",
819 + ...> is_menu: true
820 + ...> })
821 + %{
822 + backdrop_attrs: ["data-backdrop": "", "data-ismedium": ""],
823 + checkbox_attrs: [{:"aria-hidden", "true"}, {:hidden_input, false}, {:id, "some-id-toggle"}, {:onchange, "window.Prompt && Prompt.change(this)"}],
824 + focus_wrap_id: "focus-wrap-some-id",
825 + menu_attrs: [{:class, ""}, {:"data-isfast", ""}, {:"data-prompt", ""}, {:id, "some-id"}, {:"phx-hook", "Prompt"}],
826 + toggle_attrs: [{:"aria-haspopup", "true"}, {:class, "btn"}, {:for, "some-id-toggle"}],
827 + touch_layer_attrs: ["data-touch": ""]
828 + }
829 +
830 + With form:
831 + iex> PrimerLive.Helpers.AttributeHelpers.prompt_attrs(
832 + ...> %{
833 + ...> rest: %{},
834 + ...> prompt_options: nil,
835 + ...> is_fast: true,
836 + ...> is_dark_backdrop: true,
837 + ...> is_medium_backdrop: false,
838 + ...> is_light_backdrop: false,
839 + ...> is_backdrop: true
840 + ...> },
841 + ...> %{
842 + ...> form: %Phoenix.HTML.Form{
843 + ...> source: %Ecto.Changeset{
844 + ...> action: :update,
845 + ...> changes: [],
846 + ...> errors: [],
847 + ...> data: nil,
848 + ...> valid?: true
849 + ...> },
850 + ...> },
851 + ...> field: :job,
852 + ...> toggle_slot: nil,
853 + ...> toggle_class: "btn",
854 + ...> menu_class: "my-menu",
855 + ...> is_menu: true
856 + ...> })
857 + %{
858 + backdrop_attrs: ["data-backdrop": "", "data-isdark": ""],
859 + checkbox_attrs: [{:"aria-hidden", "true"}, {:hidden_input, true}, {:id, "job"}, {:onchange, "window.Prompt && Prompt.change(this)"}],
860 + focus_wrap_id: "focus-wrap-job",
861 + menu_attrs: [{:class, "my-menu"}, {:"data-isfast", ""}, {:"data-prompt", ""}, {:id, "menu-job"}, {:"phx-hook", "Prompt"}],
862 + toggle_attrs: [{:"aria-haspopup", "true"}, {:class, "btn"}, {:for, "job"}],
863 + touch_layer_attrs: ["data-touch": ""]
864 + }
865 + """
866 + def prompt_attrs(assigns, %{
867 + form: form,
868 + field: field,
869 + toggle_slot: toggle_slot,
870 + toggle_class: toggle_class,
871 + menu_class: menu_class,
872 + is_menu: is_menu
873 + }) do
874 + id = assigns[:id] || assigns.rest[:id]
875 +
876 + prompt_options = assigns[:prompt_options] || toggle_slot[:options]
877 +
878 + if !is_nil(toggle_slot[:options]) && Application.get_env(:primer_live, :env) !== :test do
879 + IO.puts(
880 + "Deprecated toggle options: pass prompt_options to the main component. Since 0.4.0."
881 + )
882 + end
883 +
884 + input_name = if field, do: Phoenix.HTML.Form.input_name(form, field), else: nil
885 + generated_id = id || input_name || random_string()
886 + is_modal = assigns[:is_modal] === true
887 +
888 + toggle_id =
889 + if id,
890 + do: "#{id}-toggle",
891 + else: generated_id
892 +
893 + toggle_rest = assigns_to_attributes_sorted(toggle_slot || [], [:class, :for, :options])
894 +
895 + toggle_attrs =
896 + append_attributes(toggle_rest, [
897 + [
898 + class: toggle_class,
899 + "aria-haspopup": "true",
900 + for: toggle_id
901 + ]
902 + ])
903 +
904 + checkbox_attrs =
905 + append_attributes([
906 + [
907 + id: toggle_id,
908 + "aria-hidden": "true",
909 + # Only use the default extra hidden input when using the menu inside a form
910 + hidden_input: !is_nil(field),
911 + onchange:
912 + [
913 + "window.Prompt && Prompt.change(this",
914 + if prompt_options do
915 + ", #{prompt_options}"
916 + end,
917 + ")"
918 + ]
919 + |> Enum.join("")
920 + ]
921 + ])
922 +
923 + menu_id = id || "menu-" <> toggle_id
924 +
925 + menu_attrs =
926 + append_attributes(assigns.rest |> Map.drop([:id, :phx_click_touch]), [
927 + [
928 + class: menu_class,
929 + "data-prompt": "",
930 + id: menu_id,
931 + "phx-hook": "Prompt"
932 + ],
933 + assigns[:is_fast] && ["data-isfast": ""],
934 + # Dialog and drawer specific:
935 + is_modal && ["data-ismodal": ""],
936 + assigns[:is_escapable] && ["data-isescapable": ""],
937 + assigns[:focus_first] && ["data-focusfirst": assigns[:focus_first]]
938 + ])
939 +
940 + backdrop_attrs =
941 + append_attributes([
942 + cond do
943 + assigns[:is_dark_backdrop] ->
944 + ["data-backdrop": "", "data-isdark": ""]
945 +
946 + assigns[:is_medium_backdrop] ->
947 + ["data-backdrop": "", "data-ismedium": ""]
948 +
949 + assigns[:is_light_backdrop] ->
950 + ["data-backdrop": "", "data-islight": ""]
951 +
952 + assigns[:is_backdrop] ->
953 + if is_menu, do: ["data-backdrop": "", "data-islight": ""], else: ["data-backdrop": ""]
954 +
955 + true ->
956 + []
957 + end
958 + ])
959 +
960 + touch_layer_attrs =
961 + append_attributes([
962 + ["data-touch": ""],
963 + !is_nil(assigns[:phx_click_touch]) && !is_modal && ["phx-click": assigns[:phx_click_touch]]
964 + ])
965 +
966 + focus_wrap_id = "focus-wrap-#{id || generated_id}"
967 +
968 + %{
969 + toggle_attrs: toggle_attrs,
970 + checkbox_attrs: checkbox_attrs,
971 + menu_attrs: menu_attrs,
972 + backdrop_attrs: backdrop_attrs,
973 + touch_layer_attrs: touch_layer_attrs,
974 + focus_wrap_id: focus_wrap_id
601 975 }
602 976 end
603 977 end
Loading more files…