defmodule Phoenix.UI.Components.Modal do @moduledoc """ Provides modal component. """ import Phoenix.UI.Components.{Backdrop, Paper} use Phoenix.UI, :component @default_max_width :sm @default_open false @doc """ Renders modal component. ## Examples ``` <.modal id="basic_modal"> <:header> <.typography variant="h2" margin={false}> Use Google's location service? <:content> <.typography margin={false}> Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running. <:actions> <.button variant="text" color="slate" extend_class="mr-4">Disagree <.button variant="text">Agree ``` """ @spec modal(Socket.assigns()) :: Rendered.t() def modal(raw_assigns) do assigns = raw_assigns |> assign_new(:max_width, fn -> @default_max_width end) |> assign_new(:open, fn -> @default_open end) |> assign_new("phx-click-away", fn -> hide_modal("##{raw_assigns[:id]}") end) |> assign_new("phx-key", fn -> "escape" end) |> assign_new("phx-window-keydown", fn -> hide_modal("##{raw_assigns[:id]}") end) |> build_modal_attrs() ~H""" <.backdrop id={"#{@id}_modal_backdrop"} open={@open} phx-click={hide_modal("##{@id}")} /> <.paper variant="elevated" {@modal_attrs}> <%= render_slot(@inner_block) %> """ end ### JS Interactions ########################## @doc """ Hide modal matching selector. ## Examples iex> hide_modal(selector) %JS{} iex> hide_modal(js, selector) %JS{} """ @spec hide_modal(String.t()) :: struct() def hide_modal(selector), do: hide_modal(%JS{}, selector) @spec hide_modal(struct(), String.t()) :: struct() def hide_modal(%JS{} = js, selector) do js |> JS.remove_attribute("open", to: selector) |> hide_backdrop("#{selector}_modal_backdrop") end @doc """ Show modal matching selector. ## Examples iex> show_modal(selector) %JS{} iex> show_modal(js, selector) %JS{} """ @spec show_modal(String.t()) :: struct() def show_modal(selector), do: show_modal(%JS{}, selector) @spec show_modal(struct(), String.t()) :: struct() def show_modal(%JS{} = js, selector) do js |> JS.set_attribute({"open", "true"}, to: selector) |> show_backdrop("#{selector}_modal_backdrop") end ### Modal Attrs ########################## defp build_modal_attrs(assigns) do extend_class = build_class(~w( overflow-auto z-50 w-full max-h-[75%] invisible opacity-0 open:visible open:opacity-100 fixed top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2 transition-all ease-in-out duration-300 #{classes(:max_width, assigns)} #{Map.get(assigns, :extend_class)} )) attrs = assigns |> assigns_to_attributes([:extend_class, :max_width]) |> Keyword.put(:extend_class, extend_class) assign(assigns, :modal_attrs, attrs) end ### CSS Classes ########################## # Max Width defp classes(:max_width, %{max_width: size}), do: "max-w-#{size}" defp classes(_rule_group, _assigns), do: nil end