lib/prenix_components/modal.ex

defmodule PrenixComponents.Modal do
  use Phoenix.Component
  import PrenixComponents.Helpers
  import PrenixComponents.Icon
  import PrenixComponents.Button

  @classes %{
    base: %{
      "modal" => "modal",
      "modal-header" => "modal-header",
      "modal-body" => "modal-body",
      "modal-footer" => "modal-footer",
      "modal-dialog" => "modal-dialog",
      "modal-content" => "modal-content",
      "modal-close" => "modal-close"
    },
    sm: %{
      "modal" => "modal--sm"
    },
    md: %{
      "modal" => "modal--md"
    },
    lg: %{
      "modal" => "modal--lg"
    },
    outside: %{
      "modal" => "modal--scroll-outside",
      "modal-content" => "modal-content--scroll-outside"
    },
    inside: %{
      "modal-body" => "modal-body--scroll-inside",
      "modal-dialog" => "modal-dialog--scroll-inside",
      "modal-content" => "modal-content--scroll-inside"
    }
  }

  @fullscreens [
    nil,
    "always",
    "2xl-below",
    "xl-below",
    "lg-below",
    "md-below",
    "sm-below"
  ]

  attr :id, :string, required: true
  attr :size, :string, default: "md", values: ~w(sm md lg)
  attr :scroll_behavior, :string, default: "outside", values: ~w(outside inside)
  attr :fullscreen, :string, default: nil, values: @fullscreens
  attr :class, :string, default: nil
  attr :dialog_class, :string, default: nil
  attr :content_class, :string, default: nil
  attr :close_button_class, :string, default: nil

  slot :header do
    attr :class, :string
  end

  slot :body do
    attr :class, :string
  end

  slot :footer do
    attr :class, :string
  end

  slot :inner_block

  def modal(assigns) do
    assigns = set_assigns(assigns)

    ~H"""
    <div class={@class} id={@id} tabindex="-1" aria-labelledby={"#{@id}-label"} aria-hidden="true">
      <div class={@dialog_class}>
        <div class={@content_class}>
          <.button
            size="sm"
            icon
            radius="full"
            variant="light"
            class={@close_button_class}
            data-bs-dismiss="modal"
          >
            <.icon name={@close_icon} />
          </.button>

          <%= for header <- @header do %>
            <div class={[@header_class, Map.get(header, :class)]}>
              <%= render_slot(header) %>
            </div>
          <% end %>

          <%= for body <- @body do %>
            <div class={[@body_class, Map.get(body, :class)]}>
              <%= render_slot(body) %>
            </div>
          <% end %>

          <%= render_slot(@inner_block) %>

          <%= for footer <- @footer do %>
            <div class={[@footer_class, Map.get(footer, :class)]}>
              <%= render_slot(footer) %>
            </div>
          <% end %>
        </div>
      </div>
    </div>
    """
  end

  def set_assigns(assigns) do
    classes =
      merge_classes([
        @classes[:base],
        @classes[String.to_atom(assigns.size)],
        @classes[String.to_atom(assigns.scroll_behavior)]
      ])

    assigns
    |> assign(
      :class,
      combine_class([
        classes["modal"],
        if(assigns.fullscreen, do: "modal--fullscreen-#{assigns.fullscreen}", else: nil),
        assigns.class
      ])
    )
    |> assign(
      :dialog_class,
      combine_class([
        classes["modal-dialog"],
        assigns.dialog_class
      ])
    )
    |> assign(
      :content_class,
      combine_class([
        classes["modal-content"],
        assigns.content_class
      ])
    )
    |> assign(
      :close_button_class,
      combine_class([
        classes["modal-close"],
        assigns.close_button_class
      ])
    )
    |> assign(:header_class, classes["modal-header"])
    |> assign(:body_class, classes["modal-body"])
    |> assign(:footer_class, classes["modal-footer"])
    |> assign(:close_icon, Application.get_env(:prenix_components, :close_icon, "mdi-close"))
  end
end