# Modal

> Dialog center có overlay — blocking UI chính để user focus vào 1 action. Dùng cho CRUD form, confirm, alert critical.

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

## API

| Prop | Type | Default | Description |
|---|---|---|---|
| `open` | `boolean` | required | Controlled visibility |
| `onClose` | `() => void` | required | Callback đóng modal |
| `title` | `ReactNode` | — | Header title — render header bar + close button khi có |
| `children` | `ReactNode` | — | Body content |
| `footer` | `ReactNode` | — | Footer actions (vd Cancel/Save buttons) |
| `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Width cap: sm=384px, md=448px, lg=672px, xl=896px |
| `className` | `string` | — | Override dialog |

## Variants

Size map:
- **sm** `max-w-sm` (24rem) — confirm dialog, alert ngắn
- **md** `max-w-md` (28rem, default) — form ngắn 2-4 field
- **lg** `max-w-2xl` (42rem) — form dài, table preview
- **xl** `max-w-4xl` (56rem) — wizard, rich content

## Composition

3 slots optional: `title` · `children` · `footer`. Minimum chỉ cần `open` + `onClose` + body via `children`.

## States

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

## Usage

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

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

<Modal
  open={open}
  onClose={() => setOpen(false)}
  title="Tạo đơn hàng mới"
  size="md"
  footer={
    <>
      <Button variant="ghost" onClick={() => setOpen(false)}>Huỷ</Button>
      <Button onClick={create}>Tạo đơn</Button>
    </>
  }
>
  <FormGroup label="Khách hàng" htmlFor="customer">
    <Input id="customer" value={customer} onChange={setCustomer} />
  </FormGroup>
  <FormGroup label="SĐT" htmlFor="phone">
    <Input id="phone" value={phone} onChange={setPhone} />
  </FormGroup>
</Modal>

// Confirm dialog size sm
<Modal
  open={confirmOpen}
  onClose={() => setConfirmOpen(false)}
  title="Xác nhận xoá"
  size="sm"
  footer={
    <>
      <Button variant="ghost" onClick={() => setConfirmOpen(false)}>Không</Button>
      <Button variant="danger" onClick={confirmDelete}>Xoá</Button>
    </>
  }
>
  Bạn chắc chắn muốn xoá đơn #{orderId}? Hành động này không thể hoàn tác.
</Modal>
```

## Real-world example

- An Nhien admin: Modal "Tạo đơn nhanh" size md — form 4 field, save call Pancake POS API.
- KHI CRM: Modal confirm "Chuyển member sang Churn?" size sm.
- A-Kryphan editor: Modal preview bài viết trước khi publish — size xl.

## Accessibility

- `role="dialog"` + `aria-modal="true"`
- `aria-labelledby="modal-title"` khi có title
- **Focus auto**: first focusable element auto-focus on open
- **Focus trap**: chưa implement full sentinel-based trap — Tab có thể escape về background. Caller cần care nếu modal critical (vd auth modal)
- **Escape**: close handler
- **Scroll lock**: `document.body.style.overflow = 'hidden'`
- **Backdrop click**: overlay div nhận click → close, dialog `stopPropagation`
- Close button có `aria-label="Close"`

## Responsive behavior

- Container `flex items-center justify-center p-4` — căn giữa với padding 16px
- Dialog `w-full` + `max-w-*` theo size — full width mobile, max width desktop
- Mobile small với form dài: body cần scroll internal nếu vượt viewport (chưa built-in — caller wrap body content trong scrollable div nếu cần)

## Related

- [[Drawer]] — panel slide từ cạnh, giữ flow chính visible (Modal chặn flow)
- [[CmdPalette]] — modal đặc biệt cho command search
- [[Toast]] — feedback nhanh không blocking (sau khi Modal save success → Toast)

## Source

`react/src/organisms/Modal.tsx`
