defmodule PrimerLive.Helpers.DeclarationHelpers do @moduledoc false defmacro id do quote do attr(:id, :string, default: nil, doc: "Component DOM id.") end end defmacro class do quote do attr(:class, :any, default: nil, doc: "Additional classname.") end end defmacro slot_id do quote do attr(:id, :any, doc: "Component DOM id.") end end defmacro slot_class do quote do attr(:class, :any, doc: "Additional classname.") end end defmacro slot_aria do quote do attr(:"aria-disabled", :string, doc: "ARIA disabled attribute.") attr(:"aria-expanded", :string, doc: "ARIA disabled attribute.") attr(:"aria-hidden", :string, doc: "ARIA hiddeh attribute.") attr(:"aria-label", :string, doc: "ARIA label attribute.") attr(:"aria-labelledby", :string, doc: "ARIA labelledby attribute.") attr(:"aria-required", :string, doc: "ARIA disabled attribute.") attr(:"aria-role", :string, doc: "ARIA role attribute.") end end defmacro slot_style do quote do attr(:style, :string, doc: """ Additional CSS styles. """ ) end end defmacro slot_tabindex do quote do attr(:tabindex, :string, doc: "Tab index. Default \"0\".") end end defmacro slot_phx_click_and_target do quote do attr(:"phx-click", :string) attr(:"phx-target", :any) end end defmacro slot_phx_value_item(item) do quote do attr(:"phx-value-item", :any, doc: """ The payload parameter for the selected value to be sent with an event. Examples: """ <> case unquote(item) do :menu_item -> """ ``` <:item :for={{label, role_id} <- @options} phx-click="set_role" phx-value-item={role_id}> ... def handle_event( "set_role", %{"item" => role_id, "value" => ""}, socket ) do ... ``` """ :tab_item -> """ ``` <:item :for={%{label: label, id: tab_id} <- @tabs} is_selected={id == @selected_tab} phx-click="set_tab" phx-value-item={tab_id} > ... def handle_event( "set_tab", %{"item" => tab_id}, socket) do ... ``` """ :row -> """ ``` <:row phx-click="select_row" phx-value-item={row_id} > ... def handle_event( "select_row", %{"item" => row_id}, socket) do ... ``` """ :header_item -> """ ``` <:item phx-click="set_preference" phx-value-item={preference_id} > ... def handle_event( "set_preference", %{"item" => preference_id}, socket) do ... ``` """ _ -> "" end ) end end defmacro input_id do quote do attr(:input_id, :string, default: nil, doc: "When using form/field. Custom input ID to be used in case the generated IDs cause \"Multiple IDs detected\" errors." ) end end defmacro common_input_attrs do quote do attr(:common_input_attrs, :map, default: nil, doc: "Used to pass calculated attributes from an input component to the form control." ) end end defmacro form do quote do attr(:form, :any, default: nil, doc: "Either a [Phoenix.HTML.Form](https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html) or an atom." ) end end defmacro field do quote do attr(:field, :any, default: nil, doc: "Field name (atom or string).") end end defmacro name do quote do attr(:name, :string, doc: "Input name attribute (when not using `form` and `field`).") end end defmacro validation_message do quote do attr(:validation_message, :any, default: nil, doc: """ Function to write a custom validation message (in case of error or success). The function receives a `PrimerLive.FieldState` struct and returns a validation message or nil. A validation message is shown: - If form is a `Phoenix.HTML.Form`, containing a `changeset` - The `validation_message` function does not return nil Function signatures: - `fun () -> string | nil` - `fun (field_state) -> string | nil` Conditional error message: ``` fn field_state -> if !field_state.valid?, do: "Please enter your first name" end ``` Conditional success message, only shown when `changeset.action` is `:validate`: ``` fn field_state -> if field_state.valid? && field_state.changeset.action == :validate, do: "Is available" end ``` """ ) end end defmacro validation_message_id do quote do attr(:validation_message_id, :string, default: nil, doc: """ Message ID that is usually passed from the form element component to `input_validation_message`. If not used, the ID will be generated. """ ) end end defmacro form_control(the_input_name) do quote do attr(:form_control, :map, default: nil, doc: """ Form control attributes. Places {the_input_name} inside a `form_control/1` component with given attributes, alongside `form` and `field` to generate a form control label. If only a automatically generated label is required, use convenience attr `is_form_control` instead. """ |> String.replace("{the_input_name}", unquote(the_input_name)) ) end end defmacro is_form_control(the_input_name) do quote do attr(:is_form_control, :boolean, default: false, doc: """ Places {the_input_name} inside a `form_control/1` component. Attributes `form` and `field` are automatically passed to `form_control` to generate a form control label. If the field is required, its label will show a required marker. To configure the form control and label, use attr `form_control`. """ |> String.replace("{the_input_name}", unquote(the_input_name)) ) end end defmacro caption(the_element) do quote do attr(:caption, :any, default: nil, doc: """ Hint message below {the_element}. Pass a string, or a function that returns a string. The function receives a `PrimerLive.FieldState` struct and returns a message or nil (which omits the caption element). Function signatures: - `fun () -> string | nil` - `fun (field_state) -> string | nil` To conditionally show a caption, use `field_state`. To show the caption when field state is valid: ``` fn field_state -> if !field_state.valid?, do: nil, else: "Caption" end ``` For additional markup, use the `H` sigil: ``` fn -> ~H''' Caption with link ''' end ``` """ |> String.replace("{the_element}", unquote(the_element)) ) end end defmacro is_aligned_end(the_element) do quote do attr(:is_aligned_end, :boolean, default: false, doc: """ Aligns {the_element} at the end (at the right in left-to-right languages and at the left in right-to-left languages). """ |> String.replace("{the_element}", unquote(the_element)) ) end end defmacro offset_x(the_element) do quote do attr(:offset_x, :integer, values: [nil, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], default: nil, doc: """ The absolute offset for the {the_element} on the horizontal axis. By default this is the start offset (at the left in left-to-right languages and at the right in right-to-left languages), unless `is_aligned_end` is used, in which case the value defines the end offset. Integer values are translated to px values using the Primer Design System's base-8 scale, as documented in [CSS Utilities: Margin](https://primer.style/foundations/css-utilities/margin): - `0`: `0` - `1`: `4px` - `2`: `8px` - `3`: `16px` - `4`: `24px` - `5`: `32px` - `6`: `40px` - `7`: `48px` - `8`: `64px` - `9`: `80px` - `10`: `96px` - `11`: `112px` - `12`: `128px` """ |> String.replace("{the_element}", unquote(the_element)) ) end end # link attr defmacro href do quote do attr(:href, :any, doc: """ Link attribute. The link is created with `Phoenix.Component.link/1`, passing all other attributes to the link. """ ) end end defmacro slot_href do quote do attr(:href, :any, doc: """ Link attribute. The link is created with `Phoenix.Component.link/1`, passing all other slot attributes to the link. """ ) end end defmacro patch do quote do attr(:patch, :string, doc: """ Link attribute - see `href`. """ ) end end defmacro navigate do quote do attr(:navigate, :string, doc: """ Link attribute - see `href`. """ ) end end # rest attr defmacro rest(opts) do quote do attr(:rest, :global, unquote(opts)) end end defmacro rest do quote do attr(:rest, :global) end end defmacro slot_rest do quote do attr(:rest, :any) end end # form_control attrs defmacro form_control_label do quote do attr(:label, :string, default: nil, doc: "Custom label. Note that a label is automatically generated when using `field`." ) end end defmacro form_control_legend_label do quote do attr(:label, :string, default: nil, doc: "Fieldset legend label." ) end end defmacro form_control_is_hide_label do quote do attr(:is_hide_label, :boolean, default: false, doc: "Omits the automatically generated label (when using `field`)." ) end end defmacro form_control_is_disabled do quote do attr(:is_disabled, :boolean, default: false, doc: "Adjusts the styling to indicate disabled state." ) end end defmacro form_control_required_marker do quote do attr(:required_marker, :string, default: "*", doc: "Required field marking. This will be shown for all required fields (when using `Phoenix.HTML.Form`). Pass an empty string to remove the indicator." ) end end defmacro form_control_for do quote do attr(:for, :string, default: nil, doc: "Internally used by `text_input/1` and `textarea/1` when using `is_form_control` or `form_control`. Label attribute to associate the label with the input. `for` should be the same as the input's `id`." ) end end defmacro form_control_is_input_group do quote do attr(:is_input_group, :boolean, default: false, doc: "Creates styling for `checkbox_group/1` and `radio_group/1`: a larger label font size, and layout for inputs, captions and validation." ) end end defmacro form_control_classes(component_name) do quote do attr(:classes, :map, default: %{ control: nil, group: nil, header: nil, label: nil, input_group_container: nil, caption: nil, fieldset: nil, legend: nil }, doc: """ Additional classnames for {component_name} elements. Any provided value will be appended to the default classname. Default map: ``` %{ control: "", # {component_name_title} wrapper group: "", # Deprecation support: identical to "control" header: "", # Header element containing the group label label: "", # {component_name_title} label input_group_container: "", # Input group container (for checkbox_group and radio_group) caption: "", # {component_name_title} caption fieldset: "", # Fieldset wrapper (for checkbox_group and radio_group) legend: "", # Legend (for checkbox_group and radio_group) } ``` """ |> String.replace( "{component_name_title}", unquote(component_name) |> Macro.camelize() ) |> String.replace("{component_name}", unquote(component_name |> String.downcase())) ) end end defmacro form_control_slot_inner_block(component_name) do quote do slot(:inner_block, required: true, doc: """ {component_name} content. """ |> String.replace("{component_name}", unquote(component_name)) |> Macro.camelize() ) end end # checkbox attrs defmacro checkbox_checked(the_component) do quote do attr(:checked, :boolean, doc: """ The state of {the_component} (when not using `form` and `field`). """ |> String.replace("{the_component}", unquote(the_component)) ) end end defmacro checkbox_checked_value(the_component) do quote do attr(:checked_value, :string, default: nil, doc: """ The value to be sent when {the_component} is checked. If `checked_value` equals `value`, {the_component} is marked checked. Defaults to "true". """ |> String.replace("{the_component}", unquote(the_component)) ) end end defmacro checkbox_hidden_input do quote do attr(:hidden_input, :string, default: "true", doc: """ Controls if the component will generate a hidden input to submit the unchecked checkbox value or not. Defaults to "true". """ ) end end defmacro checkbox_value(component_name) do quote do attr(:value, :string, doc: """ {component_name} value attribute (overrides field value when using `form` and `field`). """ |> String.replace("{component_name}", unquote(component_name)) |> Macro.camelize() ) end end defmacro checkbox_is_multiple do quote do attr(:is_multiple, :boolean, default: false, doc: """ When creating a list of checkboxes. Appends `[]` to the input name so that a list of values is passed to the form events. Alternatively, use `checkbox_in_group/1` to set this automatically. """ ) end end defmacro checkbox_is_emphasised_label do quote do attr(:is_emphasised_label, :boolean, default: false, doc: "Adds emphasis to the label.") end end defmacro checkbox_is_omit_label do quote do attr(:is_omit_label, :boolean, default: false, doc: "Omits the label.") end end defmacro checkbox_classes(component_name) do quote do attr(:classes, :map, default: %{ container: nil, label_container: nil, label: nil, input: nil, caption: nil, hint: nil, disclosure: nil }, doc: """ Additional classnames for {component_name} elements. Any provided value will be appended to the default classname. Default map: ``` %{ container: "", # {component_name_title} container label_container: "", # Input label container label: "", # Input label input: "", # {component_name_title} input element caption: "", # Hint message element hint: "", # Deprecation support: identical to "caption" disclosure: "" # Disclosure container (inline) } ``` """ |> String.replace( "{component_name_title}", unquote(component_name) |> Macro.camelize() ) |> String.replace("{component_name}", unquote(component_name |> String.downcase())) ) end end defmacro checkbox_slot_label(component_name) do quote do alias PrimerLive.Helpers.DeclarationHelpers slot :label, doc: """ Custom {component_name} label. Overides the derived label when using a `form` and `field`. """ |> String.replace("{component_name}", unquote(component_name)) do DeclarationHelpers.slot_class() DeclarationHelpers.slot_style() DeclarationHelpers.slot_rest() end end end defmacro checkbox_slot_caption(the_component) do quote do alias PrimerLive.Helpers.DeclarationHelpers slot :caption, doc: """ Adds text below {the_component} label. Enabled when a label is displayed. """ |> String.replace("{the_component}", unquote(the_component)) do DeclarationHelpers.slot_class() DeclarationHelpers.slot_style() DeclarationHelpers.slot_rest() end end end defmacro checkbox_slot_hint do quote do alias PrimerLive.Helpers.DeclarationHelpers slot :hint, doc: """ Deprecated: use `caption`. As of 0.5.0. """ do DeclarationHelpers.slot_class() DeclarationHelpers.slot_style() DeclarationHelpers.slot_rest() end end end defmacro checkbox_slot_disclosure(the_component) do quote do alias PrimerLive.Helpers.DeclarationHelpers slot :disclosure, doc: """ Extra label content to be revealed when {the_component} is checked. Enabled when a label is displayed. Note that the label element can only contain inline child elements. """ |> String.replace("{the_component}", unquote(the_component)) do DeclarationHelpers.slot_class() DeclarationHelpers.slot_style() DeclarationHelpers.slot_rest() end end end defmacro form_control_attrs do quote do alias PrimerLive.Helpers.DeclarationHelpers DeclarationHelpers.caption("the form control label") DeclarationHelpers.checkbox_is_multiple() DeclarationHelpers.class() DeclarationHelpers.common_input_attrs() DeclarationHelpers.field() DeclarationHelpers.form_control_classes("form control") DeclarationHelpers.form_control_for() DeclarationHelpers.form_control_is_disabled() DeclarationHelpers.form_control_is_hide_label() DeclarationHelpers.form_control_is_input_group() DeclarationHelpers.form_control_label() DeclarationHelpers.form_control_required_marker() DeclarationHelpers.form_control_slot_inner_block("The form control") DeclarationHelpers.form() DeclarationHelpers.input_id() DeclarationHelpers.rest() DeclarationHelpers.validation_message_id() DeclarationHelpers.validation_message() attr :is_wrap_in_fieldset, :boolean, default: false attr(:is_full_width, :boolean, default: false, doc: "Full width control.") end end end