PhoenixKitProjects.Web.Components.PopupHost (PhoenixKitProjects v0.19.0)

Copy Markdown View Source

Layered daisyUI <dialog> modal stack driven by a modal_stack assign. The function component renders the always-visible content (default slot) plus one <dialog> per stack frame, delegating each frame's body rendering to the :frame slot the host provides.

The host LV owns state — receiving :opened / :closed / :saved / :deleted PubSub events, pushing/popping the stack, generating frame_refs. See PhoenixKitProjects.Web.PopupHostLive for the opinionated wrapper that does this automatically. Use the component directly when you need full control (e.g. modal-stack alongside other host state).

Reuses the daisyUI modal pattern from project_show_live.ex:1633-1662<dialog open class="modal modal-open"> + ESC handler + modal-backdrop button.

Slots

  • :inner_block (default) — the always-visible content. Host typically embeds the root LV here via live_render(@socket, ...).
  • :frame (with :let={frame}) — per-stack-frame content. Receives the frame map (%{frame_ref, lv, session, id}) so the host can call live_render(@socket, frame.lv, id: frame.id, session: frame.session).

Attrs

  • :modal_stack — list of frame maps (ordered bottom→top).
  • :on_close — event name fired on ESC, backdrop-click, and explicit close buttons. Host's handle_event/3 must pop the top frame in response. Defaults to "close_top_modal".
  • :class — outer wrapper class. Defaults to nil (no wrapping).

Z-index layering

Each frame's <dialog> gets z-[N] where N starts at 60 and increments by 10 per stack depth. The dialogs open via the open attribute (not showModal()), so they are NOT in the browser top layer and must outrank the admin shell's fixed header (z-50) explicitly — same reasoning as the project drawer's z-[60]/z-[70]. Stack cap at 5 frames matches PopupHostLive's @max_stack_depth.

Example

<.popup_host modal_stack={@modal_stack} on_close="close_top_modal">
  {live_render(@socket, PhoenixKitProjects.Web.OverviewLive,
     id: "embed-root",
     session: %{
       "mode" => "emit",
       "pubsub_topic" => @host_topic,
       "wrapper_class" => "flex flex-col w-full px-4 py-6 gap-6"
     })}

  <:frame :let={frame}>
    {live_render(@socket, frame.lv, id: frame.id, session: frame.session)}
  </:frame>
</.popup_host>

Summary

Functions