# `PhoenixKitWeb.Components.LayoutWrapper`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.15.1/lib/phoenix_kit_web/components/layout_wrapper.ex#L1)

Dynamic layout wrapper component for Phoenix v1.7- and v1.8+ compatibility.

This component automatically detects the Phoenix version and layout configuration
to provide seamless integration with parent applications while maintaining
backward compatibility.

## Usage

Replace direct layout calls with the wrapper:

    <%!-- OLD (Phoenix v1.7-) --%>
    <%!-- Templates relied on router-level layout config --%>

    <%!-- NEW (Phoenix v1.8+) --%>
    <PhoenixKitWeb.Components.LayoutWrapper.app_layout flash={@flash}>
      <%!-- content --%>
    </PhoenixKitWeb.Components.LayoutWrapper.app_layout>

## Configuration

Configure parent layout in config.exs:

    config :phoenix_kit,
      layout: {MyAppWeb.Layouts, :app}

# `admin_sidebar_compact_bootstrap`

Styles + pre-paint stamp for the admin sidebar's compact (icon-only) mode.

Render once, immediately BEFORE the sidebar markup. Everything about compact
mode lives client-side, and deliberately so:

  * the sidebar is a **function component**, not a LiveView — there is no
    `handle_event/3` owner for a `phx-click`, and giving one to every admin
    page (or bolting a global `attach_hook` onto the admin `on_mount` chain)
    would be a lot of machinery for a display preference;
  * it is a per-browser density choice, exactly like the theme, so it belongs
    in `localStorage` next to it rather than in a settings row;
  * a client-side toggle costs no round trip, and nothing for morphdom to
    fight over — the DOM is identical either way, only `<html>` changes.

Which makes the first paint the whole problem, and the reason this is a
synchronous inline `<script>` rather than a hook in `phoenix_kit.js`:

  * it must run **before the sidebar is parsed**, or a viewer who chose
    compact gets a frame of the full-width menu on every load. An inline
    script placed above the markup does exactly that;
  * it must not depend on the host having re-run `mix phoenix_kit.update` to
    refresh its vendored `phoenix_kit.js`. Self-contained markup ships with
    the feature.

Same reasoning, and the same shape, as
`PhoenixKitWeb.Components.ThemeBootstrap` — including the one-instance guard,
since a host layout and the kit's own admin shell can both be on the page.

The CSS hides the label rather than removing it (`clip-path`, not
`display: none`), so every link keeps its accessible name and the menu still
reads correctly to a screen reader while collapsed.

# `app_layout`

Renders content with the appropriate layout based on configuration and Phoenix version.

Automatically handles:
- Phoenix v1.8+ function component layouts
- Phoenix v1.7- legacy layout configuration
- Fallback to PhoenixKit layouts when no parent configured
- Parent layout compatibility with PhoenixKit assigns

## Attributes

- `flash` - Flash messages (required)
- `phoenix_kit_current_scope` - Current authentication scope (optional)
- `phoenix_kit_current_user` - Current user (optional, for backwards compatibility)

## Inner Block

- `inner_block` - Content to render within the layout

## Attributes

* `flash` (`:map`) - Defaults to `%{}`.
* `socket` (`:any`) - Defaults to `nil`.
* `phoenix_kit_current_scope` (`:any`) - Defaults to `nil`.
* `phoenix_kit_current_user` (`:any`) - Defaults to `nil`.
* `page_title` (`:string`) - Defaults to `nil`.
* `page_subtitle` (`:string`) - Defaults to `nil`.
* `page_section` (`:string`) - Optional breadcrumb segment rendered between "Admin Panel" and `page_title` (e.g. "Users" on a user detail page). Desktop only — collapses along with the rest of the breadcrumb prefix on mobile. Defaults to `nil`.
* `page_section_path` (`:string`) - Prefixed path (via `PhoenixKit.Utils.Routes.path/1`) the `page_section` crumb links to. Renders as plain text when omitted. Defaults to `nil`.
* `page_crumbs` (`:list`) - Extra breadcrumb crumbs rendered between `page_section` and `page_title`, for pages nested deeper than one level (e.g. catalogue / category drill trails): `[%{label: "Plumbing", path: "/…"}]`. `path` is a `push_navigate` target; `patch` is a `push_patch` target for same-LiveView drill trails. Both are optional — omitted renders plain text. The last crumb stays visible below `sm` (the trail truncates from the left); earlier crumbs collapse with the section. Defaults to `[]`.
* `page_action` (`:map`) - Optional compact action button rendered right after the breadcrumb title: `%{icon: "hero-plus", label: "New template", navigate: path}`. Lets a page keep its primary create action without spending an in-content header row. `label` becomes the tooltip/aria-label; `icon` defaults to hero-plus. Navigation only — for a `phx-click` action (or anything needing `phx-target`), use the `:action` slot instead. ⚠️ Plugin LiveViews rendered through the admin layout can only use this map: the layout threads it as an assign, and a slot cannot travel that way. Defaults to `nil`.
* `current_path` (`:string`) - Defaults to `nil`.
* `inner_content` (`:string`) - Defaults to `nil`.
* `project_title` (`:string`) - Defaults to `nil`.
* `show_admin_panel_label` (`:boolean`) - Overrides the `show_admin_panel_label` setting for this render. `nil` (the default) reads the setting. Mirrors how `project_title` overrides `Settings.get_project_title/0`, and keeps the header renderable without a database. Defaults to `nil`.
* `current_locale` (`:string`) - Defaults to `nil`.
* `from_layout` (`:boolean`) - Defaults to `false`.
* `pk_pending_invitations` (`:list`) - Defaults to `[]`.
* `module_assigns` (`:map`) - Module-supplied host-consumable assigns. Each key in this map is merged into the assigns set passed to the parent layout (`Layouts.app`), so a host's custom layout can read e.g. `assigns[:phoenix_kit_publishing_translations]` from publishing, or any other module-defined key. Plain `conn.assigns` don't reach a function-component layout — only declared attrs do — so this single map attribute is how modules thread arbitrary host-consumable data through the boundary without core having to declare each one explicitly. Defaults to `%{}`.
## Slots

* `action` - The same compact action button, for pages whose primary action is not a
  navigation — a `phx-click`, a `JS` command, anything needing `phx-target`.
  The map attribute cannot express those and cannot address a LiveComponent.

  Takes render priority over the `page_action` attribute. Content is wrapped
  in the same chip shell, and the contract is **one compact control**: a
  multi-action toolbar belongs in the page body, not the breadcrumb bar.

  ⚠️ Only reaches views calling `app_layout/1` directly. Plugin LiveViews
  render through `layouts/admin.html.heex`, which threads `page_action` as an
  assign — slots do not travel through assigns — so those keep the map.

      <:action>
        <button phx-click="new_device" phx-target={@myself} title="Add device">
          <.icon name="hero-plus" class="w-4 h-4" />
        </button>
      </:action>

* `toolbar` - Controls that belong to the page's identity, rendered in the breadcrumb
  bar right after the title — a status picker, the page's ⋮ menu. Rendered
  as given (no chip shell); distinct from `page_action` / `:action`, which
  stay the one compact *create* chip.

  Two ways in. A view calling `app_layout/1` directly passes this slot. A
  plugin LiveView rendered through `layouts/admin.html.heex` cannot pass a
  slot, so it assigns `page_toolbar: {Module, :fun}` on its socket: the
  layout calls `render_page_toolbar/1` with the LiveView's own assigns
  (change-tracked) and puts the result in this slot — `phx-change` /
  `phx-click` inside it reach the LiveView as usual, because the layout
  renders inside it. Embedded mounts have no breadcrumb bar, so a page that
  is also embeddable renders the same component in its body there.

      # in a plugin LiveView's mount:
      assign(socket, page_toolbar: {__MODULE__, :header_toolbar})
      # `def header_toolbar(assigns)` renders, with ~H, e.g. a
      # `<form id="status" phx-change="change_status">` select and the
      # page's `<.table_row_menu>`; both events land in handle_event/3.

* `inner_block`

# `auth_layout`

Rendering path for auth pages (login, register, reset, confirm, magic link,
QR handoff, and the invite-only referral screen).

Auth pages **do not** render inside the host's `Layouts.app`. That layout is
where a `mix phx.new` app keeps its logo, framework version and off-site
links, so wrapping sign-in in it put Phoenix Framework branding on the login
page of every kit install — reported by more than one host. Admin already
works this way (`render_admin_with_parent/1` never calls the host's `:app`);
auth was the outlier.

The host's **root** layout still applies, because `PhoenixKitWeb.Integration`
never calls `put_root_layout`. That is where the document shell, assets and
CSRF come from, and it is unaffected.

A host that genuinely wants its own chrome on sign-in opts back in:

    config :phoenix_kit, auth_uses_host_layout: true

⚠️ Two things a host may notice when it does not: anything wired into the
**app** layout rather than root — a cookie-consent banner, analytics, a theme
toggle — stops appearing on auth pages only. Root-level wiring is unaffected.
Stock `phx.new` puts assets and CSRF in root, so conventional hosts see only
the branding disappear, which is the point.

## Attributes

* `flash` (`:map`) (required)
* `phoenix_kit_current_scope` (`:any`) - Defaults to `nil`.
* `page_title` (`:string`) - Defaults to `nil`.
* `current_path` (`:string`) - Defaults to `nil`.
* `pk_pending_invitations` (`:list`) - Defaults to `[]`.
## Slots

* `inner_block` (required)

# `get_language_flag`

# `render_page_toolbar`

```elixir
@spec render_page_toolbar(map()) :: Phoenix.LiveView.Rendered.t() | nil
```

Renders a page's `page_toolbar` — `{Module, :fun}` — with the LiveView's own
assigns. Called by `layouts/admin.html.heex`, which is the only place that
holds those assigns; the result goes into the `:toolbar` slot. Renders
nothing when the page set no toolbar — and nothing, with a logged
warning, when the pair names no `fun/1`: a page missing its toolbar beats
the whole admin chrome raising on every render (the same policy
`nav_tabs` applies to a dead tab).

---

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