Current section
48 Versions
Jump to
Current section
48 Versions
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…