# `PhoenixKitProjects.Web.Components.PopupHost`
[🔗](https://github.com/BeamLabEU/phoenix_kit_projects/blob/v0.14.0/lib/phoenix_kit_projects/web/components/popup_host.ex#L1)

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_ref`s. 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 50 (matches the
start-project modal precedent) and increments by 10 per stack depth.
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>

# `popup_host`

## Attributes

* `modal_stack` (`:list`) (required)
* `on_close` (`:string`) - Defaults to `"close_top_modal"`.
* `class` (`:string`) - Defaults to `nil`.
* `modal_box_class` (`:string`) - daisyUI `modal-box` sizing/class overrides. Default
  `"w-11/12 max-w-6xl"` takes 91% of the viewport width capped at
  `max-w-6xl` (72rem ≈ 1152px) — wider than daisyUI's default
  `max-w-md` so embedded admin LVs (project show, assignment form,
  etc.) have room for tables + cards + timelines. Pass a different
  Tailwind size class (`"max-w-4xl"`, `"max-w-7xl"`, etc.) if a
  host page wants a narrower or wider modal.

  Defaults to `"w-11/12 max-w-6xl"`.
## Slots

* `inner_block` (required)
* `frame` (required) - Accepts attributes:

  * `any` (`:any`)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
