# xpl-side-nav

Slot-based sidebar for app navigation: scrollable main area and a fixed footer. Compose with [`xpl-side-nav-item`](./xpl-side-nav-item) for rows in the section and footer.

## Slots

| Slot | Description |
| ---- | ----------- |
| `section` | Primary navigation. Place `xpl-side-nav-item` elements (or wrappers) inside this slot; layout uses internal section markup. |
| `nav-footer` | Footer actions (e.g. Help, Logout). Same item component; typically smaller label styling via CSS. |

## Usage

Use the **`label`** prop for a unique accessible name on the `<nav>` when multiple landmarks exist on the page (see **Multi-Navigation Layouts** below).

```html
<xpl-side-nav label="Main navigation">
  <div slot="section">
    <xpl-side-nav-item label="Dashboard" icon="house-4" selected></xpl-side-nav-item>
    <xpl-side-nav-item has-children label="Settings" expanded>
      <xpl-side-nav-item label="Profile"></xpl-side-nav-item>
    </xpl-side-nav-item>
  </div>
  <div slot="nav-footer">
    <xpl-side-nav-item label="Help" icon="headset"></xpl-side-nav-item>
  </div>
</xpl-side-nav>
```

**Link rows:** use `xpl-side-nav-item` with `link` when the row needs to be a real `<a>` element (framework router components, right-click / open-in-new-tab, semantic link role). For typical in-app navigation, the default button mode is simpler. See [xpl-side-nav-item](./xpl-side-nav-item) **Link mode** for usage and styling guidance.

## Mobile behavior

Below the Tailwind `md` breakpoint (768px) the sidebar collapses to a ~56px top bar with a built-in toggle button (`sidebar-left-show` / `sidebar-left-hide`). Activating the toggle opens a drawer containing the `section` + `nav-footer` content over an [`xpl-backdrop`](../xpl-backdrop) scrim. Above `md` the toggle and backdrop are hidden and the sidebar renders inline — no markup changes are needed for either layout.

**Keyboard / focus:**
- **Escape** closes the drawer and returns focus to the toggle.
- A **backdrop click** closes the drawer.
- **Focus is trapped** inside the open drawer — Tab cycles only through the toggle and drawer rows.
- The toggle exposes `aria-expanded` and a dynamic `aria-label` ("Open/Close navigation menu").

**Row-level responsive scaling (applied automatically inside the drawer):**

| Property | Desktop (≥ 768px) | Mobile (< 768px) |
|---|---|---|
| Tap target (`xpl-side-nav-item` wrapper) | — (inner button sized by content) | `min-height: 48px; padding: 6px 8px` |
| Leading / trailing icon size | 16px | 24px |
| Top-level label typography | `title-4` (16px, medium) | `title-3` (20px, medium) |
| Child item label typography | `title-5` | `title-4` (16px, medium) |
| Footer item label typography | `title-5` | `title-4` (16px, medium) |
| Footer row min-height | `28px` | `--xpl-size-200` (32px) |
| Footer leading / trailing icon | 16px | 20px |

No markup or props are needed for these changes — they are driven entirely by CSS.

### Open state API

The `open` prop, `openChange` event, and `toggle()` / `setOpen()` methods control and observe the drawer. The component works **uncontrolled** (the built-in toggle flips `open`) or **controlled** (the consumer owns `open`).

**Uncontrolled** — the built-in toggle manages everything; listen to `openChange` if you need to react:

```html
<xpl-side-nav label="Main navigation" id="nav">
    <div slot="section">…</div>
    <div slot="nav-footer">…</div>
</xpl-side-nav>
<script>
    document.getElementById('nav').addEventListener('openChange', (e) => {
        console.log('drawer open:', e.detail);
    });
</script>
```

**Controlled** — own `open` and update it from `openChange` (and/or the imperative methods):

```html
<xpl-side-nav label="Main navigation" id="nav" open>…</xpl-side-nav>
<script>
    const nav = document.getElementById('nav');
    // Keep your state in sync with user-driven changes (toggle, Escape, backdrop):
    nav.addEventListener('openChange', (e) => {
        nav.open = e.detail;
    });
    // Programmatic control:
    await nav.toggle();
    await nav.setOpen(false);
</script>
```

> **v2 breaking change (beta):** the mobile toggle + drawer are now built in. Consumers who previously wrapped their own toggle around `xpl-side-nav` should remove it.

### Implementation details (mobile drawer)

- **Host class hooks** — when the drawer is open the host carries `xpl-side-nav--open` alongside `xpl-side-nav`. Useful for app-level CSS that needs to react (e.g. lock body scroll).
- **Drawer transition** — `transform` + `opacity` + `visibility` over **300 ms** with `ease-out`. Closed state is `translateX(-100%)` + `opacity: 0` + `visibility: hidden`; the `visibility` flip is delayed 300 ms on close so the slide is visible.
- **Backdrop transition** — `opacity` + `visibility` over **500 ms** with `ease-in-out`. Open state uses `opacity-50` (matches the native `xpl-backdrop` value, not full opaque). Visibility delayed 500 ms on close. `pointer-events: none` applies immediately on close so page content is tappable while the scrim fades out.
- **Drawer geometry** — width `80vw` with a `min-width: 300px` floor (Figma spec; no Foundation token yet — see `TECH_DEBT.md`). The remaining `20vw` shows the backdrop scrim.
- **Toggle styling overrides** — the built-in `xpl-button` toggle uses `padding-inline: var(--xpl-space-12)` on its inner `<button>` and renders its icon at `var(--xpl-size-200)` (32 px) so the icon's left edge aligns with the drawer's nav-item icon column.

### Multi-Navigation Layouts (Accessibility)

When multiple navigation landmarks exist on a page (e.g., side nav, top nav, breadcrumbs), use the `label` prop to provide a unique accessible name for each `<nav>` element. This helps screen reader users distinguish between them.

```html
<!-- Main side navigation -->
<xpl-side-nav label="Main navigation">
  ...
</xpl-side-nav>

<!-- Top navigation (different component) -->
<xpl-top-nav label="User menu">
  ...
</xpl-top-nav>

<!-- Breadcrumbs (different component) -->
<xpl-breadcrumbs label="Breadcrumb">
  ...
</xpl-breadcrumbs>
```

**WCAG Note:** When a page has multiple `<nav>` landmarks, each should have a unique accessible name via `aria-label`.

## Related components

- [xpl-side-nav-item](./xpl-side-nav-item) — Navigation rows for section and footer: leaf button, expandable parent, or link (real `<a>`) mode.

<!-- Auto Generated Below -->


## Overview

Slot-based sidebar for app navigation: a scrollable main `section` and a fixed
`nav-footer`. Compose with `xpl-side-nav-item` for rows.

**Mobile (v2):** below the Tailwind `md` breakpoint, the sidebar collapses to a
~56px top bar with a toggle button (`sidebar-left-show` / `sidebar-left-hide`).
Activating the toggle opens a drawer containing the `section` + `nav-footer`
content behind an `xpl-backdrop` scrim. The drawer traps focus while open;
Escape or a backdrop click closes it and returns focus to the toggle. Above
`md` the toggle and backdrop are hidden via CSS and the sidebar renders inline.

The toggle is always rendered (no gating prop) and hidden above the breakpoint
in CSS. Open state is exposed via the `open` prop (controlled or uncontrolled),
the `openChange` event, and the `toggle()` / `setOpen()` methods.

**v2 breaking change (beta):** consumers who wrapped their own mobile toggle
around `xpl-side-nav` should remove it — the toggle and drawer are now built in.

## Properties

| Property | Attribute | Description                                                                                                                                                                                                                                                                      | Type      | Default     |
| -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- |
| `label`  | `label`   | Accessible label for the navigation landmark. Maps to `aria-label` on the `<nav>` element. Recommended when multiple navigation landmarks exist on a page (e.g., side nav + top nav + breadcrumbs) to help screen readers distinguish them.                                      | `string`  | `undefined` |
| `open`   | `open`    | Whether the mobile drawer is open. Reflected to the `open` attribute so CSS and host state stay in sync. Controlled (consumer owns `open`) or uncontrolled (built-in toggle flips it). Has no visible effect above the `md` breakpoint, where the sidebar always renders inline. | `boolean` | `false`     |


## Events

| Event        | Description                                                                                                                                                        | Type                   |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- |
| `openChange` | Emitted when the mobile drawer open state changes: toggle activation, Escape, or a backdrop click. Detail is the new open state (`true` = open, `false` = closed). | `CustomEvent<boolean>` |


## Methods

### `setOpen(open: boolean) => Promise<void>`

Programmatically set the mobile drawer open state. Emits `openChange` when it changes.

#### Parameters

| Name   | Type      | Description |
| ------ | --------- | ----------- |
| `open` | `boolean` |             |

#### Returns

Type: `Promise<void>`



### `toggle() => Promise<void>`

Programmatically toggle the mobile drawer open/closed. Emits `openChange`.

#### Returns

Type: `Promise<void>`




## Slots

| Slot           | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| `"nav-footer"` | Footer items (e.g. Help, Logout), separated by a top border. |
| `"section"`    | Primary navigation items (`xpl-side-nav-item`).              |


## Dependencies

### Depends on

- [xpl-button](../xpl-button)
- [xpl-backdrop](../xpl-backdrop)

### Graph
```mermaid
graph TD;
  xpl-side-nav --> xpl-button
  xpl-side-nav --> xpl-backdrop
  xpl-button --> xpl-icon
  style xpl-side-nav fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
