# Drawer

> Panel slide từ 1 cạnh màn hình — dùng cho detail view, form edit inline, filter panel. Khác Modal ở chỗ contextual (giữ flow chính bên dưới).

**A11y critical:** focus trap · escape · scroll lock · backdrop click close.

## API

| Prop | Type | Default | Description |
|---|---|---|---|
| `open` | `boolean` | required | Controlled visibility |
| `onClose` | `() => void` | required | Callback đóng drawer |
| `title` | `ReactNode` | — | Header title — render header bar + close button khi có |
| `children` | `ReactNode` | — | Body content (scrollable) |
| `footer` | `ReactNode` | — | Footer actions (vd buttons Save/Cancel) |
| `side` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'right'` | Cạnh slide từ |
| `size` | `string` | (auto theo side) | Override `w-*` (left/right) hoặc `h-*` (top/bottom) |
| `className` | `string` | — | Override panel |

## Variants

- **Side**: `right` (default — detail view), `left` (filter panel), `top` (notification center), `bottom` (mobile action sheet)
- **Size** mặc định: left/right `max-w-md` (28rem), top/bottom `max-h-[80vh]`

## Composition

3 slots: `title` (header) · `children` (body) · `footer`. Tất cả optional — drawer minimum chỉ cần `open` + `onClose`.

## States

- **Closed**: component return `null`, body scroll restored
- **Open**: overlay + panel mounted, body scroll lock, first focusable element auto-focus
- **Backdrop click**: close (event bubble từ overlay, panel `stopPropagation`)
- **Escape**: close

## Usage

```tsx
import { Drawer, Button, FormGroup, Input } from '@cowork/ui';

const [open, setOpen] = useState(false);

<Drawer
  open={open}
  onClose={() => setOpen(false)}
  title="Sửa thông tin khách"
  side="right"
  footer={
    <>
      <Button variant="ghost" onClick={() => setOpen(false)}>Huỷ</Button>
      <Button onClick={save}>Lưu</Button>
    </>
  }
>
  <FormGroup label="Họ tên" htmlFor="name">
    <Input id="name" value={name} onChange={setName} />
  </FormGroup>
  <FormGroup label="SĐT" htmlFor="phone">
    <Input id="phone" value={phone} onChange={setPhone} />
  </FormGroup>
</Drawer>
```

## Real-world example

- An Nhien admin: click row đơn hàng → drawer phải mở chi tiết đơn (sản phẩm, địa chỉ ship, lịch sử trạng thái) — giữ list đơn bên trái.
- KHI CRM: filter drawer trái với các nhóm filter (lifecycle / nguồn / khoảng thời gian) — apply không cần reload.
- A-Kryphan editor: drawer bottom trên mobile cho "Insert block" menu (image, embed, divider).

## Accessibility

- `role="dialog"` + `aria-modal="true"`
- `aria-labelledby="drawer-title"` khi có title
- **Focus trap**: query first focusable + focus on open. Tab nav trong drawer (chưa implement full trap với sentinel — Tab có thể escape ra background, caller có thể thêm `inert` cho main content)
- **Escape**: close handler
- **Scroll lock**: `document.body.style.overflow = 'hidden'`
- **Backdrop**: click outside panel close (overlay div nhận click, panel `stopPropagation`)

## Responsive behavior

- Left/Right: `w-full max-w-md` → trên mobile chiếm full width, desktop max 28rem
- Top/Bottom: `max-h-[80vh]` → chừa 20vh cho user thấy main content
- Override qua `size` prop nếu cần (vd `size="w-full max-w-2xl"` cho drawer wide)

## Related

- [[Modal]] — center dialog, blocking flow chính (drawer giữ flow chính visible)
- [[CmdPalette]] — modal đặc biệt cho command search
- [[Sidebar]] — persistent left panel (drawer = temporary)

## Source

`react/src/organisms/Drawer.tsx`
