lib/moon/design/form/dropdown.ex

defmodule Moon.Design.Form.Dropdown do
  @moduledoc "Fully styled select component for the forms"

  use Moon.StatelessComponent

  alias Surface.Components.Form
  alias Moon.Design.Dropdown
  alias Moon.Design.Form.Checkbox
  alias Moon.Design.Form.Radio

  import Moon.Helpers.Form

  @doc "Name of the field, usually should be taken from context"
  prop(field, :atom, from_context: {Form.Field, :field})
  @doc "Form info, usually should be taken from context"
  prop(form, :form, from_context: {Form, :form})

  @doc "... format: [%{key: shown_label, value: option_value, disabled: bool}], diisabled is optional"
  prop(options, :list, required: true)

  @doc "Selected option(s) value - do not use it inside the form, just for away-from-form components"
  prop(value, :any)
  @doc "Well, disabled"
  prop(disabled, :boolean)

  @doc "Common moon size property"
  prop(size, :string, values!: ~w(sm md lg), default: "md")
  @doc "Additional classes for the <select> tag"
  prop(class, :css_class, from_context: :class)
  @doc "Some prompt to be shown on empty value"
  prop(prompt, :string)

  @doc "Id to be given to the select tag"
  prop(id, :string)
  @doc "Data-testid attribute value"
  prop(testid, :string)

  @doc "Some additional styling will be set to indicate field is iinvalid"
  prop(error, :boolean, from_context: :error)

  @doc "If field does support multiselect, `multiple` attribute for select tag in HTML terms"
  prop(is_multiple, :boolean)

  @doc "Should dropdown be open"
  prop(is_open, :boolean)

  @doc "Option for custom stylings - use it to show icons or anything else"
  slot(default)

  @doc "Trigger element for the dropdown, default is Dropdown.Select"
  slot(trigger)

  @doc "Slot used for rendering single option. option[:key] will be used if not given"
  slot(option)

  def render(assigns) do
    ~F"""
    <Dropdown id={dropdown_id(assigns)} {=@is_open} {=@class} {=@testid}>
      <:trigger :let={is_open: is_open}>
        <#slot {@trigger, is_open: is_open}>
          <Dropdown.Select
            {=@prompt}
            {=@size}
            {=is_open}
            {=@error}
            {=@disabled}
            value={select_value(assigns) && select_value(assigns)[:key]}
            badge={select_badge(assigns)}
          >
            <#slot {@option, option: select_value(assigns)}>
              {(select_value(assigns) && select_value(assigns)[:key]) || @prompt}
            </#slot>
          </Dropdown.Select>
        </#slot>
      </:trigger>
      <#slot {@default}>
        <Dropdown.Options on_change={option_click()}>
          <Dropdown.Option :for={option <- @options} {=@size}>
            <Checkbox
              checked_value={option[:value]}
              :if={@is_multiple}
              disabled={option[:disabled]}
              {=@size}
              hidden_input={false}
              is_multiple
            >
              <#slot {@option, option: option}>{option[:key]}</#slot>
            </Checkbox>
            <Radio.Button value={option[:value]} :if={!@is_multiple} disabled={option[:disabled]} {=@size}>
              <Radio.Indicator />
              <#slot {@option, option: option}>{option[:key]}</#slot>
            </Radio.Button>
          </Dropdown.Option>
        </Dropdown.Options>
      </#slot>
    </Dropdown>
    """
  end

  defp option_click(), do: Phoenix.LiveView.JS.dispatch("moon-empty-event")
end