# `PhoenixKitWeb.Components.LayoutWrapper`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.32.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. A crumb may also carry a `:switcher` (see `page_title_switcher`): a ▾ beside it lists the other things on that crumb's level. The last crumb stays visible below `sm` (the trail truncates from the left); earlier crumbs collapse with the section. Defaults to `[]`.
* `page_title_switcher` (`:map`) - A switcher on the page title itself: a ▾ beside it opening a searchable list of the other things on this level — GitHub's repository switcher. Same shape as a crumb's `:switcher` (`%{title:, items: [%{label:, navigate: | patch:, current:}], search_placeholder:}`); see `PhoenixKitWeb.Components.Core.CrumbSwitcher`. Defaults to `nil`.
* `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, by design: it renders a real link, so middle-click, open-in-new-tab and copy-link keep working. For anything interactive — a `phx-click`, a modal, a `JS` command — use `page_toolbar: {Module, :fun}` on the socket (see the `:toolbar` slot), which reaches every page including plugin LiveViews rendered through the admin layout. Do not add click handling to this map. 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`.
* `dev_environment` (`:boolean`) - Overrides `PhoenixKit.WebsiteAccess.environment().looks_like_dev?` for this render (nil, the default, reads it). Drives the small "[dev]" tag next to the project title — automatic, not a setting, so a dev/staging box never has to be told apart by an admin toggle. 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` - **Superseded by `:toolbar` / `page_toolbar`** — prefer those for new code:
  they reach every page, including plugin LiveViews routed through the admin
  layout, which this slot never can. Kept for existing direct callers.

  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*
