import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks'
import * as DialogShellStories from './DialogShell.stories'

<Meta of={DialogShellStories} />

# DialogShell

The dialog, minus what it says: the portal, the backdrop, a 502px card,
a 48px header with the title and a close button, a body, and a 48px
footer for the buttons. A dialog is just what goes in the three slots.

<Canvas of={DialogShellStories.Default} />

## When

- Anything with a title, a body, and buttons: a form, a picker, a roster.
- `footer` absent removes the footer row entirely — a list that simply
  ends keeps the card's own bottom edge.
- `headerContent` replaces the title text — a back button beside the
  title, a count chip after it. The close button stays.

## When not

- A short list of verbs from a trigger → **Menu**.
- "Are you sure?" → **ConfirmDialog** — it is this shell with the question
  already wired, and it is the *only* thing that uses `alert`. There is no
  `alert` story here, on purpose: any example of it would be a confirmation
  dialog rebuilt by hand, which is the one thing this bullet exists to stop.
  Its canvases and its nine tests are on that page.
- Telling someone what happened → **Toast**.
- Searching for something and acting on it → **CommandPalette**.

## How

```tsx
import { DialogShell, Button } from '@estiva-app/ui'

{open && (
  <DialogShell
    title="New document"
    onClose={() => setOpen(false)}
    footer={
      <>
        <Button onClick={() => setOpen(false)}>Cancel</Button>
        <Button variant="primary" onClick={submit}>Create</Button>
      </>
    }
  >
    …fields…
  </DialogShell>
)}
```

- Render it only while open — mounting is opening.
- `bodyClassName` shapes the body — its layout and any padding override
  (`flex flex-col gap-6`, `p-0 py-2`).
- **`bodyMaxHeight` makes the body scroll in the package's bar**: give it
  the cap (`max-h-[400px]`, `max-h-[70vh]`) and the body becomes a
  `ScrollArea`, so a long roster keeps its text column when it overflows.
  Without it the body grows to its content, as it always did. A
  `bodyClassName` carrying `overflow-y-auto` and no cap never scrolled
  anything — the box grew — and is what this replaces. Rows in a capped
  flex body still need `shrink-0`, or overflow crushes them.
- The card names itself to assistive tech (`role="dialog"`, labelled by
  its header — or by `title` when `headerContent` replaces that text).
- **Focus is trapped and returned.** Tab cannot leave the card, and closing
  puts focus back on whatever opened the dialog. This page said the opposite
  until stage 3; it is Base UI's now, and pinned by `DialogShell.test.tsx`.
  The rest of the page is marked `inert` while it is open, so a screen
  reader cannot wander out either. `autoFocus` on the first field is still
  worth setting where typing is the point — the trap decides where focus
  *cannot* go, not where it starts.
- `alert` for a question that has to be answered: a press on the backdrop
  stops closing it. Escape and the ✕ still do.
  `ConfirmDialog` sets it; a form or a roster should not.

## What it owns

It does these by itself. Write none of them around it.

| It does | So you never write | Refused when written by hand |
|---|---|---|
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
| Closes on a press outside, except with `alert` | a click listener on the page | ✓ |
| Takes its keys by itself: Escape closes it | a key listener on the page | ✓ |
| Holds focus inside while open, and gives it back to whatever opened it | focus code, or a Tab handler | ✓ |
| Stops the page behind it scrolling | `overflow: hidden` on the page | ✓ |
| Says what it is to assistive technology: a dialog | `role="dialog"` | ✓ |

## Keys

| Input | What happens |
|---|---|
| Escape | closes |
| backdrop click | closes — **except with `alert`, where nothing happens** |
| close button | closes (it is an IconButton with its label) |
| Tab / Shift+Tab | cycles the controls inside the card, and cannot leave it |
| on close | focus returns to whatever opened the dialog |

## Props

<Controls of={DialogShellStories.Default} />
