# Contrato · Popover

> Segue o modelo de `contratos/ruler.md`. Implementado em
> `app/src/componentes/Popover/Popover.tsx`, com CSS em
> `estilo/20-popover-torrada.css` (`.pop`) — a **mesma** folha que o
> protótipo carrega. Especificação original em `handoff/componentes.md` §4.
> Fecha, junto com `Opção`, os 7 componentes da Fase 1.

**Use `@radix-ui/react-popover`. Não implemente posicionamento, foco nem
teclado** — Regra 5 do AGENTS.md. A primitiva cuida de tudo isso; este
componente só estiliza.

## Anatomia

| Parte | Componente | Classe |
|---|---|---|
| disparador | `PopoverTrigger` (`RadixPopover.Trigger`) | — |
| superfície | `PopoverContent` | `.pop` |
| rótulo | `PopoverLabel` | `.pop>.rot` |
| divisor | `PopoverSeparator` | `.pop .div` |
| nota | `PopoverNote` | `.pop .nota` |
| fechar | `PopoverClose` (`RadixPopover.Close`) | — |

```tsx
<Popover>
  <PopoverTrigger asChild><Botao peso="contorno" tamanho="sm">MCMV e econômico</Botao></PopoverTrigger>
  <PopoverContent aria-label="Segmento deste estudo">
    <PopoverLabel>Segmento deste estudo</PopoverLabel>
    <Option selected>MCMV e econômico</Option>
    <Option selected={false}>Médio padrão</Option>
    <PopoverSeparator />
    <PopoverNote>Vale só para este estudo.</PopoverNote>
  </PopoverContent>
</Popover>
```

## Comportamento (da primitiva, verificado no navegador — Playwright, story
"Comportamento" em `Popover.stories.tsx`, roda no `test:vitrine`)

- abre no clique e no teclado (Tab até o disparador, Enter)
- flutua — o conteúdo é portado (`RadixPopover.Portal`), nunca ocupa espaço
  no fluxo. Medido: a posição do disparador não muda um pixel ao abrir.
- fixa quando aberta; só fecha em X (`PopoverClose`), Esc ou clique fora
- Esc fecha **e** devolve o foco ao disparador, **sem reabrir**
- clicar numa `Opção` dentro NÃO fecha o popover sozinho (comportamento
  correto da primitiva, e o que `handoff/componentes.md` §4 pede)

## Achado construindo, não estava no documento original

- **A "ponte de 200ms" da Regra 5 não se aplica aqui.** Essa armadilha vale
  pra camada hover-triggered (como uma Tooltip) — se o cursor precisa
  atravessar um vão sem a camada fechar no meio do caminho. Este Popover é
  abre-no-clique e só fecha em X/Esc/clique-fora, nunca por causa do mouse
  saindo. A ponte volta a valer quando este sistema tiver uma Tooltip real.
- **`transform` teve que sair do CSS.** Radix posiciona o conteúdo com
  `transform` inline (Floating UI, pra colisão com borda de tela) — qualquer
  `transform` neste arquivo colidiria com o posicionamento. A animação de
  entrada de fade+scale+slide que `.pop`/`.pop.acima` faziam no CSS antigo
  não é possível sem embrulhar `.pop>.rot` num filho extra, o que quebraria
  o seletor de filho direto que este documento descreve.
- **A transição de `opacity` também teve que sair**, por um motivo diferente
  e mais sutil: qualquer CSS `transition` liga a `Presence` interna do Radix,
  que **espera a transição acabar antes de desmontar** o conteúdo do DOM
  (pra permitir animação de saída). Sem uma animação de saída implementada,
  isso só criava uma janela onde reabrir rápido deixava DOIS `.pop`
  coexistindo — **achado real**, pego pelo próprio `play()` da story
  "Comportamento": `getByRole` encontrou dois botões com o mesmo nome porque
  o popover anterior ainda não tinha desmontado. Resolvido tirando a
  transição inteira: Radix monta/desmonta, sem CSS de entrada/saída.
- **`role="dialog"` (da primitiva) sem nome acessível é reprovado pelo axe**
  (`aria-dialog-name`) — achado pelo `test:vitrine`. `PopoverLabel` é
  opcional na anatomia, então não dá pra assumir que ele sempre existe e
  ligar `aria-labelledby` automaticamente (um id órfão seria a mesma falha,
  só que "referencia elemento inexistente" em vez de "sem nome"). Resolvido
  exigindo `aria-label` OU `aria-labelledby` em `PopoverContent`, com
  quebra em desenvolvimento se nenhum dos dois vier.
- **`shadcn init` nunca rodou neste projeto** (`components.json` não existe).
  Instalada só a dependência `@radix-ui/react-popover` direto — não a
  scaffold do CLI, que geraria componentes já estilizados com o Tailwind
  default do shadcn, incompatível com o sistema de tokens deste repositório.

## Acessibilidade

- Foco: trap dentro do conteúdo enquanto aberto (Radix `FocusScope`), Esc
  devolve ao disparador.
- `role="dialog"` com nome acessível obrigatório (ver acima).
- Teclado: Tab não escapa do conteúdo enquanto aberto; Esc fecha.

## Fora do contrato

- **Posicionamento, foco e teclado** são da primitiva — não reimplementados,
  não testados unitariamente aqui além de composição (ver Pendências).
- **Tooltip** é outro componente (hover-triggered), fora de escopo.

## Pendências deste componente

- **Testes de interação em jsdom não cobrem o comportamento real.** Radix
  Popover usa `@radix-ui/react-popper`, que depende de medição de layout
  real — jsdom não resolve isso de verdade. A verificação de comportamento
  (abre/fecha/foco/teclado/sem-salto-de-layout) mora inteiramente no
  `play()` da story "Comportamento", que roda no `test:vitrine` (Chromium
  real via Playwright) — mesmo raciocínio que `AGENTS.md` já registra pra
  contraste não ser testado em jsdom.
- **`shadcn init` continua não rodado.** Se um dia o time quiser os
  componentes-fonte do shadcn como ponto de partida de estrutura (permitido
  por `HANDOFF.md` §4.5, "pode usar shadcn Blocks"), essa decisão fica pra
  quando surgir necessidade real — não antecipada aqui.
- ~~Sem página de documentação própria.~~ **Resolvido.** Popover é um dos 7
  da Fase 1 — sempre teve página própria em `/componentes/popover`
  (`PopoverDoc.tsx`, 27/ago/2026). A frase estava errada desde que foi
  escrita (Opção e Número também já tinham página; só Bloco tinha o mesmo
  erro, corrigido em separado) — achado na auditoria de 21/set/2026.
