# `PhiaUi.Components.Sheet`
[🔗](https://github.com/charlenopires/PhiaUI/blob/v0.1.17/lib/phia_ui/components/overlay/sheet.ex#L1)

Sheet component — a sliding side panel that enters from any edge of the screen.

A sheet is a modal panel that slides in from a screen edge, covering a portion
of the page. It is ideal for secondary workflows that don't need a full page —
edit forms, settings panels, filter builders, detail views, and navigation
drawers in mobile layouts.

Unlike `Drawer`, `Sheet` has a richer semantic sub-component set including
`sheet_title/1` and `sheet_description/1` for proper ARIA labelling, and a
size system (`sm`/`md`/`lg`/`xl`/`full`) to control the panel's dimension.

Reuses the `PhiaDialog` JavaScript hook — no additional hook registration
beyond what `Dialog` already requires.

## Hook Registration

Copy the hook via `mix phia.add sheet` (or `mix phia.add dialog`), then
register in `app.js`:

    # assets/js/app.js
    import PhiaDialog from "./hooks/dialog"

    let liveSocket = new LiveSocket("/live", Socket, {
      hooks: { PhiaDialog }
    })

## Sub-components

| Function              | Purpose                                                  |
|-----------------------|----------------------------------------------------------|
| `sheet/1`             | Root container — hook mount point                        |
| `sheet_header/1`      | Title + description layout container                     |
| `sheet_title/1`       | `<h2>` heading — set `:id` for ARIA linkage              |
| `sheet_description/1` | `<p>` supporting text — set `:id` for ARIA               |
| `sheet_footer/1`      | Action row at the bottom (save, cancel)                  |
| `sheet_close/1`       | × close button in the top-right corner                   |

## Sides

| Value      | Slides from | Panel sizing                              |
|------------|-------------|-------------------------------------------|
| `"right"`  | right edge  | Full height, configurable width (default) |
| `"left"`   | left edge   | Full height, configurable width           |
| `"top"`    | top edge    | Full width, configurable height           |
| `"bottom"` | bottom edge | Full width, configurable height           |

## Sizes

| Value    | Width (right/left) | Height (top/bottom) | Best For                    |
|----------|--------------------|---------------------|-----------------------------|
| `"sm"`   | `w-64` (256px)     | `h-64`              | Simple notifications, tips  |
| `"md"`   | `w-96` (384px)     | `h-96` (default)    | Short forms, quick edits    |
| `"lg"`   | `max-w-lg`         | `max-h-64`          | Medium forms, settings      |
| `"xl"`   | `max-w-xl`         | `max-h-96`          | Complex forms, rich content |
| `"full"` | `max-w-full`       | `max-h-full`        | Full-width panels, mobile   |

## Example — Edit Record Form (right side)

The most common use case: a form to edit a record without leaving the page.

    <.sheet id="edit-user-sheet" open={@show_edit} side="right" size="lg">
      <.sheet_header>
        <.sheet_title id="edit-user-sheet-title">
          Edit User
        </.sheet_title>
        <.sheet_description id="edit-user-sheet-description">
          Update the user's profile information below.
        </.sheet_description>
      </.sheet_header>
      <div class="p-6">
        <.form for={@changeset} phx-submit="save_user" phx-change="validate_user">
          <.input name="name" label="Full Name" value={@changeset.data.name} />
          <.input name="email" label="Email" value={@changeset.data.email} />
          <.input name="role" label="Role" value={@changeset.data.role} />
        </.form>
      </div>
      <.sheet_footer>
        <.sheet_close />
        <button phx-click="cancel_edit" class="...">Cancel</button>
        <button phx-click="save_user" class="...">Save changes</button>
      </.sheet_footer>
      <.sheet_close />
    </.sheet>

## Example — Filter Panel (left side)

A filter sidebar that slides in from the left:

    <.sheet id="filter-sheet" open={@show_filters} side="left" size="md">
      <.sheet_header>
        <.sheet_title id="filter-sheet-title">Filters</.sheet_title>
      </.sheet_header>
      <div class="p-6 space-y-4">
        <.select name="status" label="Status" options={@status_options} />
        <.select name="category" label="Category" options={@category_options} />
        <.input type="date" name="from" label="From date" />
        <.input type="date" name="to" label="To date" />
      </div>
      <.sheet_footer>
        <button phx-click="clear_filters">Clear all</button>
        <button phx-click="apply_filters">Apply</button>
      </.sheet_footer>
      <.sheet_close />
    </.sheet>

## Example — Mobile Navigation Drawer (bottom side)

A mobile-friendly navigation menu that slides up from the bottom:

    <.sheet id="mobile-nav" open={@show_mobile_nav} side="bottom" size="full">
      <.sheet_header>
        <.sheet_title id="mobile-nav-title">Navigation</.sheet_title>
      </.sheet_header>
      <nav class="p-4 grid grid-cols-2 gap-2">
        <.link navigate="~p"/dashboard"" phx-click="close_mobile_nav" class="...">
          Dashboard
        </.link>
        <.link navigate={~p"/reports"} phx-click="close_mobile_nav" class="...">
          Reports
        </.link>
      </nav>
      <.sheet_close />
    </.sheet>

## Controlling Visibility

Control the sheet via the `:open` assign. Toggle from event handlers:

    # Open the sheet
    def handle_event("edit_user", %{"id" => id}, socket) do
      user = Accounts.get_user!(id)
      {:noreply, socket |> assign(:editing_user, user) |> assign(:show_edit, true)}
    end

    # Close the sheet (cancel or save)
    def handle_event("cancel_edit", _params, socket) do
      {:noreply, assign(socket, :show_edit, false)}
    end

## ARIA

Always set `aria-labelledby` pointing to `sheet_title/1`'s `:id`. This is
automatically handled if you use the convention `id="{sheet-id}-title"`.
The `sheet/1` container already includes `aria-labelledby="{id}-title"`.

## Accessibility

- `role="dialog"` and `aria-modal="true"` on the sliding panel
- `aria-labelledby` on the panel references the title for screen reader context
- Focus trap: Tab/Shift+Tab stay within the open sheet
- Escape key closes the sheet and returns focus to the triggering element
- Backdrop click closes the sheet when `on_close` is wired up
- The × close button has `aria-label="Close sheet"` and is always
  in the Tab order for keyboard users

# `sheet`

Root Sheet container. Binds the `PhiaDialog` JS hook for focus trap and
keyboard handling (Escape to close, Tab to cycle focus within the panel).

The `open` boolean controls visibility via the `hidden` CSS class on the
outer container. The `side` attribute controls which edge the panel slides
from. The `size` attribute controls the panel's cross-axis dimension.

The `on_close` attr wires a `Phoenix.LiveView.JS` command to the backdrop
click — typically used to update the visibility assign:

    <.sheet id="settings" open={@show_settings}
      on_close={JS.push("close_settings")}>

Or using `phx-click` directly:

    <.sheet id="settings" open={@show_settings}
      on_close={JS.push("close_settings") |> JS.hide(to: "#settings")}>

## Attributes

* `id` (`:string`) (required) - Unique sheet ID — the `PhiaDialog` hook mount point.
* `open` (`:boolean`) - Whether the sheet is currently visible. Toggle via LiveView assigns. Defaults to `false`.
* `side` (`:string`) - Screen edge the panel slides in from. Defaults to `"right"`. Must be one of `"top"`, `"right"`, `"bottom"`, or `"left"`.
* `size` (`:string`) - Width for left/right sides, or height for top/bottom sides. Defaults to `"md"`. Must be one of `"sm"`, `"md"`, `"lg"`, `"xl"`, or `"full"`.
* `on_close` (`Phoenix.LiveView.JS`) - `Phoenix.LiveView.JS` command executed when the backdrop is clicked. Use to
  update your visibility assign:

      on_close={JS.push("close_sheet")}

  Defaults to `nil`.
* `safe_area` (`:boolean`) - When `true` and `side` is `"bottom"`, adds `pb-[env(safe-area-inset-bottom)]`
  to the sheet panel for proper spacing on devices with a home indicator or notch
  (e.g. iPhone X+).

  Defaults to `false`.
* `class` (`:string`) - Additional CSS classes for the sliding panel. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the root element.
## Slots

* `inner_block` (required) - Sheet content: `sheet_header/1`, body div, `sheet_footer/1`, `sheet_close/1`.

# `sheet_close`

The × close button rendered in the top-right corner of the sheet panel.

The `PhiaDialog` JS hook listens for clicks on `data-sheet-close` and:
1. Adds the `hidden` class to the root sheet element
2. Returns focus to the element that was focused before the sheet opened

This button is always visible (unlike the toast close button). Keyboard
users can Tab to it or press `Escape` to close.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes.

# `sheet_description`

Supporting text below the sheet title.

Use to provide context about what the sheet is for or what the user should
do. Set `:id` if you want to also wire `aria-describedby` on the sheet
container for richer screen reader support.

    <.sheet id="settings" aria-describedby="settings-description">
      <.sheet_description id="settings-description">
        Changes take effect immediately and sync across all devices.
      </.sheet_description>
    </.sheet>

## Attributes

* `id` (`:string`) - Element ID for `aria-describedby` linkage from the sheet container. Defaults to `nil`.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes.
## Slots

* `inner_block` (required) - Description text.

# `sheet_footer`

Action row at the bottom of the sheet.

Renders a `flex` row of buttons, right-aligned on desktop and stacked
vertically (reversed) on mobile. Place the primary action button last in
the template for natural desktop order.

    <.sheet_footer>
      <button phx-click="cancel">Cancel</button>
      <button phx-click="save" class="...">Save changes</button>
    </.sheet_footer>

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes.
## Slots

* `inner_block` (required) - Footer content — typically cancel + confirm buttons.

# `sheet_header`

Layout container for the sheet title and optional description.

Provides consistent padding and vertical spacing. Always place this at the
top of the sheet content, before the scrollable body area.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes.
## Slots

* `inner_block` (required) - `sheet_title/1` and optionally `sheet_description/1`.

# `sheet_title`

Sheet heading rendered as `<h2>`.

Set `:id` to `"{sheet-id}-title"` — the parent `sheet/1` includes
`aria-labelledby="{id}-title"` by default. This ensures screen readers
announce the sheet's purpose when it opens.

    <.sheet id="edit-profile" ...>
      <.sheet_title id="edit-profile-title">Edit Profile</.sheet_title>
    </.sheet>

## Attributes

* `id` (`:string`) - Element ID for ARIA linkage. Set to `"{sheet-id}-title"` so the sheet's
  built-in `aria-labelledby` resolves correctly.

  Defaults to `nil`.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes.
## Slots

* `inner_block` (required) - Title text.

---

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