defmodule Paleta.Components.Modal do
use Phoenix.Component
# import Phoenix.LiveView.Helpers
alias Phoenix.LiveView.JS
alias Paleta.Components.Icon
@doc """
Renders a modal.
## Examples
<.modal id="confirm-modal">
This is a modal.
JS commands may be passed to the `:on_cancel` to configure
the closing/cancel event, for example:
<.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
This is another modal.
"""
attr(:id, :string, default: "modal")
attr(:title, :string, required: true)
attr(:show, :boolean, default: false)
attr(:on_cancel, JS, default: %JS{})
attr(:width, :atom, default: :small, values: [:small, :medium, :large])
attr(:return_to, :string, default: nil)
slot(:inner_block, required: true)
def modal(assigns) do
assigns =
assigns
|> assign(:on_cancel, on_cancel(assigns))
|> assign_width_class()
~H"""
<%= @title %>
<.focus_wrap
id={"#{@id}-container"}
phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
phx-key="escape"
phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
class="px-4 py-4 sm:px-5"
>
<%= render_slot(@inner_block) %>
"""
end
## JS Commands
def show(js \\ %JS{}, selector) do
JS.show(js,
to: selector,
transition:
{"transition-all transform ease-out duration-300",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
"opacity-100 translate-y-0 sm:scale-100"}
)
end
defp on_cancel(%{return_to: nil, on_cancel: on_cancel}), do: on_cancel
defp on_cancel(%{return_to: return_to}) do
JS.patch(return_to)
end
def hide(js \\ %JS{}, selector) do
JS.hide(js,
to: selector,
time: 200,
transition:
{"transition-all transform ease-in duration-200",
"opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
def show_modal(js \\ %JS{}, id) when is_binary(id) do
js
|> JS.show(to: "##{id}")
|> JS.show(
to: "##{id}-bg",
transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"}
)
|> show("##{id}-container")
|> JS.add_class("overflow-hidden", to: "body")
|> JS.focus_first(to: "##{id}-content")
end
def hide_modal(js \\ %JS{}, id) do
js
|> JS.hide(
to: "##{id}-bg",
transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"}
)
|> hide("##{id}-container")
|> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"})
|> JS.remove_class("overflow-hidden", to: "body")
|> JS.pop_focus()
end
attr(:label, :string, default: "Cancel")
attr(:modal_id, :string, default: "modal")
def close_modal_button(assigns) do
~H"""
<%= @label %>
"""
end
attr(:label, :string, default: "Save")
attr(:in_progress_label, :string, default: "Saving...")
def save_modal_button(assigns) do
~H"""
"""
end
attr(:label, :string, default: "Delete")
attr(:in_progress_label, :string, default: "Deleting...")
def delete_modal_button(assigns) do
~H"""
"""
end
slot(:inner_block, required: true)
def modal_actions(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
end
attr(:action, :atom, default: :edit)
def default_modal_actions(assigns) do
~H"""
<.close_modal_button />
<.save_modal_button :if={@action != :delete} />
<.delete_modal_button :if={@action == :delete} />
"""
end
def assign_width_class(%{width: width} = assigns) do
class =
case width do
:small -> "w-full max-w-lg"
:medium -> "w-2/3"
:large -> "w-3/4"
end
assigns
|> assign(:width_class, class)
end
end