Components Modals

Modals

Modals sit on top of an applications main window and require a live view to handle their on/off state. We recommend using a new route to map to the on/off state.
Sizes
Router
          scope "/", PetalProWeb do
  pipe_through [:browser]

  live "/", ExampleLive, :index
  live "/modal", ExampleLive, :modal
end

        
Live view/component
          defmodule ExampleLive do
  use PetalProWeb, :live_view

  # ...

  @impl true
  def handle_params(params, _uri, socket) do
    {:noreply, socket}
  end

  # The modal component emits this event when `PetalComponents.Modal.hide_modal()` is called.
  # This happens when the user clicks the dark background or the 'X'.
  @impl true
  def handle_event("close_modal", _, socket) do

    # Go back to the :index live action
    {:noreply, push_patch(socket, to: "/")}
  end

  # ...

end

        
HEEX template
          <.a type="live_patch" to="/modal" label="Opens the modal" />

<%= if @live_action == :modal do %>
  <.modal max_width="sm|md|lg|xl|2xl|full" title="Modal">
    <.p>Content</.p>

    <div class="flex justify-end">
      <.button label="close" phx-click={PetalComponents.Modal.hide_modal()} />
    </div>
  </.modal>
<% end %>

        

If you’re nesting a modal inside a live component, the close event will go to the parent live view, instead of the component.

To fix this, you can change the target to be the live component:

<.modal title="Modal" close_modal_target={@myself}>
  <.p>Content</.p>
</.modal>

Underneath the hood, this gets forwarded to the JS.push function. Here’s what it looks like:

# in petal_components/modal.ex
JS.push(js, "close_modal", target: close_modal_target)
Properties
          attr :id, :string, default: "modal", doc: "modal id"
attr :hide, :boolean, default: false, doc: "modal is hidden"
attr :title, :string, default: nil, doc: "modal title"

attr :close_modal_target, :string,
  default: nil,
  doc:
    "close_modal_target allows you to target a specific live component for the close event to go to. eg: close_modal_target={@myself}"

attr :close_on_click_away, :boolean,
  default: true,
  doc: "whether the modal should close when a user clicks away"

attr :close_on_escape, :boolean,
  default: true,
  doc: "whether the modal should close when a user hits escape"

attr :hide_close_button, :boolean,
  default: false,
  doc: "whether or not the modal should have a close button in the header"

attr :on_cancel, JS,
  default: JS.exec("data-cancel-default"),
  doc:
    "a JS function to execute when the modal is closed. Defaults to pushing close_modal event"

attr :max_width, :string,
  default: "md",
  values: ["sm", "md", "lg", "xl", "2xl", "full"],
  doc: "modal max width"

attr :rest, :global
slot :inner_block, required: false