# Dropdown

> Componentes / Formulário e ação · Estado: **Em andamento** — Implementado e utilizável, mas ainda sem a validação completa de acessibilidade.

Menu de ações que abre de um botão — o padrão Menu Button do WAI-ARIA: um ponto de Tab para o menu inteiro, setas para andar, digitação para buscar e Esc para fechar.

- Função: Formulário e ação
- Também chamado de: menu, menu de ações, mais opções, kebab, menu suspenso
- Mora em: `app/components/molecules/Dropdown.tsx`
- Página: [https://phorte-design-system.vercel.app/styleguide/peca/Dropdown](https://phorte-design-system.vercel.app/styleguide/peca/Dropdown)
- Ficha no Storybook: [components-base-dropdown](https://main--69b4847d498a9bd8659db9ba.chromatic.com/?path=/docs/components-base-dropdown--docs)
- Prévia ao vivo, sozinha: [https://phorte-design-system.vercel.app/previa/Dropdown/0](https://phorte-design-system.vercel.app/previa/Dropdown/0)
- Todas as peças: [https://phorte-design-system.vercel.app/llms.txt](https://phorte-design-system.vercel.app/llms.txt)

## Como usar no código

```tsx
import { Dropdown } from '@/app/components/molecules/Dropdown';

<Dropdown items={…} triggerLabel={…}>…</Dropdown>
```

O `…` marca as props obrigatórias — ver *Propriedades*.

## Uso

### Variantes na ficha

1. [Padrão](https://phorte-design-system.vercel.app/previa/Dropdown/0)
2. [Aberto](https://phorte-design-system.vercel.app/previa/Dropdown/1)
3. [Com item destrutivo](https://phorte-design-system.vercel.app/previa/Dropdown/2)
4. [Com Item Inativo](https://phorte-design-system.vercel.app/previa/Dropdown/3)
5. [Posicionamento](https://phorte-design-system.vercel.app/previa/Dropdown/4)
6. [Controlado](https://phorte-design-system.vercel.app/previa/Dropdown/5)
7. [Com Ref](https://phorte-design-system.vercel.app/previa/Dropdown/6)
8. [Com grupos](https://phorte-design-system.vercel.app/previa/Dropdown/7)
9. [Só ícone](https://phorte-design-system.vercel.app/previa/Dropdown/8)

Para agrupar ações de um item ou de uma tela que não cabem como botões soltos — editar, duplicar, excluir.

### Quando não usar

Para escolher um valor num formulário, use o Select. Para navegar entre páginas, links (a Navbar tem os seus). Para duas ou três ações, botões à vista (Button).

### Faça

- Dê um `triggerLabel` que diga o que abre: "Ações do curso", não "Opções".
- Ponha ícone no item destrutivo (`tone="destructive"`) — a cor sozinha não basta.
- Com mais de seis ações, organize em grupos com subtítulo: `{ id, group: "Compartilhar", items }` (E63). O separador `{ id, separator: true }` só divide para quem vê.
- Use `placement` quando o botão estiver no fim da linha (`bottom-end`); perto do pé da janela ele inverte sozinho (E63).
- Para o "⋯" de cartão e de linha, `triggerIcon` com um `triggerLabel` que diga de quê: "Mais ações do curso" (E63).

### Evite

- Usar como seletor de valor: o item não fica marcado — não há `menuitemradio`.
- Esconder ação principal dentro do menu.
- Item desabilitado sem motivo à vista: ele continua focável e anunciado, mas não diz por que não funciona.

### Nota de uso

**E63:** acionador só-ícone (`triggerIcon`, com o `triggerLabel` como nome), grupos com subtítulo (`role="group"` nomeado) e inversão quando não cabe na janela. Declara `role="menu"` com foco **roving**: um único ponto de tabulação no menu inteiro, setas circulando, `Home`/`End`, e digitação buscando por prefixo (com acento — `a` alcança "Ações"). Isso **não** contradiz a decisão da Navbar: lá o papel foi removido porque prometia um item focável por vez e entregava doze links tabuláveis; aqui o contrato vem inteiro. Para escolher um **valor**, use o `Select` — este é menu de ações. **Não implementado, e dito em voz alta:** item de marcação (`menuitemradio`), que filtro rápido e ordenação pediriam. A lista entra esmaecendo e descendo 4px em 150ms, só com movimento liberado.

### Anatomia

1. **Botão** — Um Button (`triggerVariant`) com o rótulo e uma seta — ou, com `triggerIcon`, só o ícone em 44×44, com o `triggerLabel` como `aria-label`; anuncia `aria-haspopup="menu"` e `aria-expanded`.
2. **Painel** — `role="menu"`, a partir de 14rem de largura, posicionado pelo `placement`.
3. **Item** — `role="menuitem"`, 44px de altura, com ícone opcional.
4. **Separador** — `role="separator"` entre grupos.
5. **Grupo** — `role="group"` nomeado pelo subtítulo (12px, negrito, caixa alta, `--muted-foreground`), com os itens dentro (E63).

### Estados

| Estado | O que muda |
| --- | --- |
| Fechado | Só o botão; `aria-expanded="false"`. |
| Aberto | Painel à mostra, `aria-expanded="true"`, e o foco no item ativo. Entra esmaecendo e descendo 4px em 150ms, só com movimento liberado. |
| Item em foco | Anel de foco por dentro do item (`ring-inset`). |
| Item destrutivo | `tone="destructive"`: texto vermelho de interface. |
| Item inativo | `disabled`: 50% de opacidade e `aria-disabled`, mas continua focável. |
| Desabilitado | `disabled` no Dropdown desabilita o botão. |
| Invertido | Sem espaço embaixo e com espaço em cima, o painel abre para cima (`data-posicao`); vale também ao contrário (E63). |

### Comportamento

| Situação | O que acontece |
| --- | --- |
| Item escolhido | Chama o `onSelect` do item, fecha o menu e devolve o foco ao botão. |
| Clique fora | Fecha o menu — pelo `mousedown` fora e pelo foco que sai; testado na ficha (E142). |
| Abre perto do pé da janela | Mede o painel antes da pintura e, se ele sairia da tela, abre do outro lado — conferido no teste da ficha: pedido `bottom-start`, efetivo `top-start`, dentro da janela. Um ancestral com `overflow: hidden` ainda recorta: a medida é contra a janela, e o painel não usa portal. |
| Setas com grupos | Andam pelos itens de todos os grupos, na ordem, como se fosse uma lista só — conferido no teste da ficha. |
| Tab com o menu aberto | Fecha o menu e segue para o próximo elemento, sem prender o foco. |
| Modo controlado | Com `open`, quem manda é o consumidor; `onOpenChange` dispara em todo caminho de fechamento, nos dois modos. |

### Relacionados

- [Select](https://phorte-design-system.vercel.app/styleguide/md/Select.md) — Para escolher um valor num formulário.
- [Button](https://phorte-design-system.vercel.app/styleguide/md/Button.md) — O botão que abre o menu, e as ações que cabem à vista.

## Especificação

### Propriedades

| Prop | Tipo | Padrão | Obrigatória | Descrição |
| --- | --- | --- | --- | --- |
| `placement` | `'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end'` | `'bottom-start'` | não | Variante do `cva`. |
| `items` | `DropdownEntry[]` | — | sim |  |
| `triggerLabel` | `string` | — | sim | Obrigatório de propósito, pelo mesmo motivo que o `title` do `Modal`. Um acionador sem nome é anunciado apenas como "botão" — a pessoa sabe que algo abre e não sabe o quê. Isso é defeito de conteúdo, não opção de API, e o tipo é o lugar certo de dizer: o erro aparece ao escrever a chamada, não numa auditoria seis meses depois. |
| `triggerIcon` | `React.ReactNode` | — | não | Acionador só-ícone (E63), para o menu "⋯" de cartão e de linha. O ícone é decorativo, o `triggerLabel` vira o `aria-label` — e a seta some, porque o ícone já diz que ali há mais. |
| `open` | `boolean` | — | não | Presente = modo controlado. Ausente = o componente segura o estado. |
| `defaultOpen` | `boolean` | `false` | não |  |
| `onOpenChange` | `(aberto: boolean) => void` | — | não | Dispara em **todo** caminho de fechamento, nos dois modos. |
| `disabled` | `boolean` | `false` | não |  |
| `triggerVariant` | `ButtonProps['variant']` | `'outline'` | não |  |

Aceita também: `Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'>`.

### Tokens de cor

| Token | Valor | Usado como |
| --- | --- | --- |
| `--border` | #E5E5E5 | `bg-border`, `border-border` |
| `--card` | #FFFFFF | `bg-card` |
| `--destructive` | #BA0517 | `text-destructive` |
| `--foreground` | #1A1A1A | `text-ink` |
| `--muted` | #F4F6F9 | `bg-muted` |
| `--muted-foreground` | #595959 | `text-muted-foreground` |
| `--ring` | var(--footer-red)), decisão da revisão 2. Se outra peça precisar
   * dele como superfície, o nome vira de papel (ex.: --surface-brand-strong). */
  --footer-red: #7D1B1B | `ring-ring` |

## Acessibilidade

### Teclado

| Tecla | Faz |
| --- | --- |
| Enter / Espaço / ↓ | Com o foco no botão, abre o menu no primeiro item. |
| ↑ | Com o foco no botão, abre o menu no último item. |
| ↓ / ↑ | Com o menu aberto, anda entre os itens — do último volta ao primeiro. |
| Home / End | Vai direto ao primeiro ou ao último item. |
| Letra | Vai ao item que começa com ela. Acento não importa: “a” encontra “Ações”. |
| Enter / Espaço | Ativa o item e fecha o menu. |
| Esc | Fecha o menu e devolve o foco ao botão. |
| Tab | Sai do menu e o fecha, indo ao próximo elemento da página. É o comportamento de todo menu acessível: dentro dele se navega com as setas. |

### Leitor de tela

- O nome do menu vem do botão (`aria-labelledby`), e o `triggerLabel` é obrigatório no tipo — um botão sem nome seria anunciado só como "botão".
- Um só item do menu recebe Tab por vez (foco roving): o menu inteiro é uma parada.
- Cada grupo é anunciado pelo nome ao entrar nele ("Compartilhar, grupo"); o subtítulo é `presentation`, e não vira item.

### O que sustenta o estado

Usa os padrões: menu, divulgacao. Falta ouvir com leitor de tela: menu, divulgacao.

## Status e recursos

| Tipo | Recurso | Status |
| --- | --- | --- |
| Código | `app/components/molecules/Dropdown.tsx` | Disponível |
| Ficha | https://main--69b4847d498a9bd8659db9ba.chromatic.com/?path=/docs/components-base-dropdown--docs | Disponível |
| Teste de acessibilidade | axe em `npm run test` | No gate de todo commit |
| Leitor de tela | Passada da E33 | Falta ouvir |
| Design | Figma | Desativado até a Fase 2 |

### Histórico

- `0298af8` 2026-09-30 — E147: fechamento da Entrega 11 — REPORT.md, verificação cross-agent e o gate estático estrito
- Histórico completo no GitHub: https://github.com/srbunnie/Pos-Phorte-Design-System/commits/main/app/components/molecules/Dropdown.tsx
