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

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

A drawer provides access to supplementary content or actions without leaving
the current page. Unlike `Sheet`, the `Drawer` is designed for content-heavy
panels and uses its own dedicated `PhiaDrawer` JavaScript hook (rather than
sharing `PhiaDialog`) with CSS transform animations for the enter/exit motion.

## When to use Drawer vs Sheet

| Aspect           | Drawer                          | Sheet                           |
|------------------|---------------------------------|---------------------------------|
| JS Hook          | `PhiaDrawer` (dedicated)        | `PhiaDialog` (shared)           |
| Panel anatomy    | Simple (header/footer/close)    | Rich (title/description/close)  |
| Trigger          | `drawer_trigger/1` button       | Controlled by `:open` assign    |
| Animation        | CSS transform slide             | CSS transform slide             |
| Use case         | Side nav, detail views, step forms | Edit forms, filter panels   |

Use `Drawer` when you want a dedicated trigger button colocated with the
content and a simpler, content-driven panel. Use `Sheet` when state from
the server controls open/close or when you need the richer ARIA sub-components.

## Hook Registration

Copy the hook via `mix phia.add drawer`, then register it in `app.js`:

    # assets/js/app.js
    import PhiaDrawer from "./hooks/drawer"

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

## Sub-components

| Function           | Purpose                                          |
|--------------------|--------------------------------------------------|
| `drawer/1`         | Root container — wraps trigger and content       |
| `drawer_trigger/1` | Button that opens the drawer                     |
| `drawer_content/1` | Sliding panel + backdrop — the hook mount point  |
| `drawer_header/1`  | Title and description layout container           |
| `drawer_footer/1`  | Action row at the bottom                         |
| `drawer_close/1`   | × close button in the top-right corner           |

## Directions

| Value      | Slides from | Default panel dimensions                    |
|------------|-------------|---------------------------------------------|
| `"bottom"` | bottom edge | Full width, max 85vh, rounded top corners   |
| `"top"`    | top edge    | Full width, max 85vh, rounded bottom corners|
| `"left"`   | left edge   | 75% width, max `sm`, full height            |
| `"right"`  | right edge  | 75% width, max `sm`, full height            |

## Example — Settings Drawer (right)

The canonical drawer pattern: a button triggers a right-side panel.

    <.drawer id="settings-drawer">
      <.drawer_trigger drawer_id="settings-drawer">
        <.button>
          <.icon name="settings" class="mr-2" />
          Settings
        </.button>
      </.drawer_trigger>

      <.drawer_content id="settings-drawer-content" direction="right">
        <.drawer_header>
          <h2 id="settings-drawer-content-title" class="text-lg font-semibold">
            Settings
          </h2>
          <p class="text-sm text-muted-foreground">
            Manage your account preferences.
          </p>
        </.drawer_header>
        <.drawer_close />
        <div class="p-6 space-y-4">
          <.input name="display_name" label="Display name" value={@user.name} />
          <.select name="timezone" label="Timezone" options={@timezones} />
          <.switch name="email_notifications" label="Email notifications" checked={@user.email_notifs} />
        </div>
        <.drawer_footer>
          <button phx-click="cancel_settings">Cancel</button>
          <button phx-click="save_settings" class="...">Save</.button>
        </.drawer_footer>
      </.drawer_content>
    </.drawer>

## Example — Mobile Bottom Sheet

A bottom drawer is the mobile-native pattern for action sheets:

    <.drawer id="actions-drawer">
      <.drawer_trigger drawer_id="actions-drawer" class="md:hidden">
        <.button variant="outline">Actions</.button>
      </.drawer_trigger>

      <.drawer_content id="actions-drawer-content" direction="bottom">
        <.drawer_header>
          <h2 id="actions-drawer-content-title" class="text-sm font-medium">
            Item Actions
          </h2>
        </.drawer_header>
        <.drawer_close />
        <div class="p-4 pb-8 space-y-2">
          <button phx-click="edit_item" class="w-full text-left px-4 py-3 rounded-md hover:bg-muted">
            Edit
          </button>
          <button phx-click="duplicate_item" class="w-full text-left px-4 py-3 rounded-md hover:bg-muted">
            Duplicate
          </button>
          <button phx-click="delete_item" class="w-full text-left px-4 py-3 rounded-md text-destructive hover:bg-destructive/10">
            Delete
          </button>
        </div>
      </.drawer_content>
    </.drawer>

## Example — Step-by-Step Form (left drawer)

Multi-step forms work well in a left drawer that feels like an overlay wizard:

    <.drawer id="onboarding">
      <.drawer_trigger drawer_id="onboarding">
        <.button>Start Setup</.button>
      </.drawer_trigger>

      <.drawer_content id="onboarding-content" direction="left" open={@show_onboarding}>
        <.drawer_header>
          <h2 id="onboarding-content-title" class="text-lg font-semibold">
            Setup Wizard — Step {@step} of 3
          </h2>
        </.drawer_header>
        <.drawer_close />
        <div class="p-6">
          <%= render_step(@step) %>
        </div>
        <.drawer_footer>
          <button phx-click="prev_step" disabled={@step == 1}>Back</button>
          <button phx-click="next_step">
            {if @step == 3, do: "Finish", else: "Next"}
          </button>
        </.drawer_footer>
      </.drawer_content>
    </.drawer>

## Server-Controlled Open State

Pass `open={@show_drawer}` to `drawer_content/1` to control visibility from
the server (e.g. after a navigation event or background task completion):

    <.drawer_content id="notif-content" direction="right" open={@show_notifications}>
      ...
    </.drawer_content>

    # Push from LiveView
    {:noreply, assign(socket, :show_notifications, true)}

## Hook Behaviour

The `PhiaDrawer` hook (mounted on `drawer_content/1`) handles:
- CSS `transform` animation (slide in/out) on open/close
- Focus trap — Tab / Shift+Tab cycle within the open panel
- `Escape` key closes the panel and returns focus to the trigger
- Backdrop click closes the panel
- Focus return — restores focus to `drawer_trigger/1` (or last focused element)
  when the drawer closes

## Accessibility

- `drawer_content/1` has `role="dialog"` and `aria-modal="true"`
- `aria-labelledby="{id}-title"` is auto-set — give your title heading
  the id `"{drawer-content-id}-title"` for the link to resolve
- `drawer_close/1` has `aria-label="Close drawer"` for screen reader users
- Keyboard: focus automatically moves to the first focusable element on open

# `drawer`

Root container for the Drawer. Wraps both `drawer_trigger/1` and
`drawer_content/1`. Provides the `relative` positioning context and a
shared ID namespace.

The root is purely a layout wrapper — no hook attaches here. The hook
mounts on `drawer_content/1`.

## Attributes

* `id` (`:string`) (required) - Unique drawer ID used as a namespace for child IDs.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the root `<div>`.
## Slots

* `inner_block` (required) - `drawer_trigger/1` and `drawer_content/1`.

# `drawer_close`

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

The `PhiaDrawer` JS hook listens for clicks on `data-drawer-close` and:
1. Reverses the CSS transform animation (slides the panel out)
2. Adds the `hidden` class to the root container after the animation
3. Returns focus to the `drawer_trigger/1` that opened the drawer

The `opacity-70` default with `hover:opacity-100` keeps the button
present but unobtrusive, becoming fully visible on hover.

## Attributes

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

# `drawer_content`

The drawer surface — renders the semi-transparent backdrop and the sliding
panel. This is the `PhiaDrawer` hook mount point.

The outer element is `fixed inset-0` and starts hidden when `open={false}`.
It contains two children:
- `data-drawer-backdrop` — the full-screen dark overlay (click to close)
- `data-drawer-panel` — the sliding panel itself

The hook applies CSS `transform: translate*` to the panel to create the
slide-in animation, then removes the `hidden` class from the container.

## The `aria-labelledby` convention

The `aria-labelledby` is auto-set to `"{id}-title"`. Give your title heading
this exact ID so the reference resolves:

    <.drawer_content id="settings-content" direction="right">
      <.drawer_header>
        <h2 id="settings-content-title">Settings</h2>
      </.drawer_header>
    </.drawer_content>

## Attributes

* `id` (`:string`) (required) - Content element ID — the `PhiaDrawer` hook mount point. By convention: "{drawer_id}-content".
* `open` (`:boolean`) - Whether the drawer is open on initial render. When `false` (default), the
  outer container has the `hidden` class. Pass `open={@show_drawer}` to
  control visibility from the server after the initial render.

  Defaults to `false`.
* `direction` (`:string`) - Direction the drawer panel slides in from. Defaults to `"bottom"`. Must be one of `"bottom"`, `"top"`, `"left"`, or `"right"`.
* `class` (`:string`) - Additional CSS classes for the sliding panel. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the outer container `<div>`.
## Slots

* `inner_block` (required) - Drawer panel content: header, body, footer, close.

# `drawer_footer`

Action row at the bottom of the drawer.

Renders buttons in a flex row, right-aligned on desktop and stacked
vertically (reversed order) on mobile for thumb accessibility. Use
`pt-0` to align naturally below the last content section.

    <.drawer_footer>
      <button phx-click="cancel">Cancel</button>
      <button phx-click="save" class="...">Save</.button>
    </.drawer_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 + action buttons.

# `drawer_header`

Layout container for the drawer title and optional description.

Provides `p-6` padding and vertical `space-y-1.5` between title and
description. Always place at the top of the drawer content, before the
scrollable body.

## Attributes

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

* `inner_block` (required) - Drawer heading and optional description paragraph.

# `drawer_trigger`

Wrapper that opens the drawer when any element inside it is clicked.

Renders a `<div>` (not a `<button>`) so you can place any trigger content
inside — including a `<.button>` — without creating invalid nested-button
HTML. The `PhiaDrawer` hook intercepts clicks on `[data-drawer-trigger]`
anywhere in the document via event delegation, so the wrapper element type
does not need to be focusable itself.

## Example

    <.drawer_trigger drawer_id="settings-drawer">
      <.button variant="outline">
        <.icon name="settings" class="mr-2" />
        Open Settings
      </.button>
    </.drawer_trigger>

## Attributes

* `drawer_id` (`:string`) (required) - ID that matches the target `drawer_content/1`'s `id` attribute.
  The hook looks for `[data-drawer-trigger]` whose value equals the
  `drawer_content/1` element's `id`.

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the wrapper `<div>`.
## Slots

* `inner_block` (required) - Trigger content — any element, typically a `<.button>`.

---

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