# Contrato · Toast

> Segue o modelo de `contratos/ruler.md`. Implementado em
> `app/src/componentes/Toast/Toast.tsx`, com CSS em
> `estilo/20-popover-torrada.css` (`.torrada`) — a **mesma** folha que o
> protótipo carrega. Sem contrato original: não é um dos 7 da Fase 1, nem
> tinha spec em `handoff/componentes.md` — construído a partir do CSS/JS
> reais (`torra()`, `T_IC`), achados revisando os prints do usuário
> (26/ago/2026).

> **Não é um dos 7 da Fase 1.** Mesma nota de escopo de `contratos/toggle.md`
> — vive só na vitrine.

**Use `@radix-ui/react-toast`. Não implemente timing, pausa, gesto nem
`aria-live`.** — Regra 5 do AGENTS.md. Terceira dependência Radix do
projeto.

## Anatomia

| Parte | API | Classe |
|---|---|---|
| área (monta uma vez, perto da raiz) | `<ToastArea>` | — |
| disparo | `useToast().show(message, variant?)` | — |
| viewport (fixo, centralizado embaixo) | interno | `.torrada-viewport` |
| toast individual | interno | `.torrada` |

```tsx
// perto da raiz, uma vez
<ToastArea>
  <App />
</ToastArea>

// em qualquer componente dentro
const { show } = useToast()
show('PDF de 2 páginas gerado com sucesso.', 'ok')
show('R. Hermano Marchetti, 210 saiu da lista.', 'info')
show('Não consegui salvar — tente de novo.', 'danger')
```

Três tipos. `ok` (ícone `circle-check`, cor `--ok`) e `info` (ícone
`info-circle`, cor base) são as MESMAS chaves de `T_IC` no protótipo.
`danger` (ícone `alert-f`, cor `--no` — o mesmo par que `.bdg-risco` e
`Button` variant="danger" já usam) é novo, adicionado em 22/set/2026 a
pedido do usuário ("vale a pena ter positivo, negativo"): o protótipo
nunca teve toast de erro, só confirmação e neutro. Os três se distinguem
pelo ícone (forma e cor) e pelo texto. A v0.54.0 tinha posto também uma
borda de 3px à esquerda; saiu em 24/set/2026 porque o toast aprovado no
protótipo não tem essa linha (Regra 2: produção aprovada é a fonte).

## Diferença estrutural deliberada do original

`torra(msg, tipo)` no protótipo é **singleton** — um `#torrada` fixo,
reaproveitado a cada chamada, no máximo um visível por vez (a próxima
chamada troca o texto do mesmo elemento). `@radix-ui/react-toast` é
**fila**: um `Viewport` fixo contém quantos `Root` estiverem abertos,
empilhados em fluxo normal. Isto é upgrade da primitiva, não invenção —
mensagens em sequência rápida agora empilham em vez de a segunda apagar a
primeira antes de alguém ler. Verificado no `test:vitrine`: duas chamadas
seguidas de `show()` produzem dois `.torrada` simultâneos.

## Achado construindo, não estava previsto

- **A posição fixa teve que migrar do toast pro viewport.** `.torrada` no
  CSS original tinha `position:fixed;left:50%;bottom:26px;transform:
  translate(-50%,14px)` — fazia sentido pro singleton, mas com Radix Toast
  cada `Root` é item de FLUXO NORMAL dentro do `Viewport`; só o `Viewport`
  pode ser fixo. Criada `.torrada-viewport`, nova, com a posição fixa +
  centralização + `flex-direction:column` pro empilhamento.
- **Sem animação de entrada/saída**, mesmo motivo já medido no Popover e no
  Dialog: `transition` de CSS liga a `Presence` interna do Radix, que
  atrasa o desmonte — sem animação de saída de verdade, cria janela de
  sobreposição. Terceira vez que a mesma causa aparece; não repetida com
  as mesmas consequências porque já era esperada.
- **`useToast()`/`<ToastArea>` são camada nova, não do CSS.** Radix Toast
  por si só é totalmente controlado (`open`/`onOpenChange` por item) — sem
  uma camada de conveniência, cada chamada de "mostrar um toast" exigiria
  o consumidor gerenciar estado à mão, perdendo a ergonomia de uma linha
  que `torra(msg, tipo)` tinha. `ToastArea` guarda a fila internamente
  (Context), reproduzindo a mesma ergonomia sobre a API controlada.

## Acessibilidade

- Região `aria-live` (`role="region" aria-label="Notifications (F8)"`) —
  da primitiva, anuncia a mensagem pro leitor de tela sem esforço aqui.
  Verificado no `test:vitrine`.
- Pausa a auto-dispensa quando o mouse ou o foco está em cima — da
  primitiva.
- Gesto de arrastar pra dispensar — da primitiva, sem CSS específico de
  swipe adicionado aqui (funciona com o comportamento default do Radix,
  sem estilo customizado de affordance — ver Pendências).
- O ícone é decorativo (`aria-hidden`, via `Icon` sem `label`) — a
  mensagem em si é o conteúdo anunciado.
- Cor não é o único sinal entre os três tipos: `ok`/`info`/`danger` usam
  ícones com formas diferentes (`circle-check`/`info-circle`/`alert-f`),
  não só cores diferentes — mesmo critério do `Badge`.

## Fora do contrato

- **Ação embutida no toast** (ex. "Desfazer") — `RadixToast.Action` existe
  na primitiva, não exposta na API de `useToast()` desta entrega. O CSS
  original (`.torrada`) também não tem essa variante.
- ~~Um terceiro tipo (risco/erro) — sem CSS real por trás, não
  inventado.~~ **Resolvido em 22/set/2026.** Ver `danger` acima —
  pedido explícito do usuário, com CSS real (`.torrada.danger`), não mais
  fora do contrato.

## Pendências deste componente

- **Sem CSS de affordance pro gesto de arrastar** (swipe-to-dismiss) — a
  primitiva funciona, mas não há indicação visual customizada de que dá
  pra arrastar. O CSS original também não tinha esse gesto (era só
  timeout), então não é regressão — é capacidade nova da primitiva ainda
  sem casca visual própria.
- **`RadixToast.Action` não exposto** — se surgir necessidade real de um
  toast com botão de ação, adicionar como parâmetro de `show()`.
- ~~Sem página de documentação própria, sem entrada na barra lateral.~~
  **Resolvido.** Página em `/componentes/toast` (`ToastDoc.tsx`,
  27/ago/2026), listada em "Componentes adicionais". Frase desatualizada,
  achada na auditoria de 21/set/2026.
