Tucano v0.37.2

Menu suspenso

Menu de ações ancorado num botão. Vira de lado quando não cabe, fecha no clique fora, no Escape e quando o foco sai, e anda pelas setas do teclado. Os itens podem vir escritos no template, que é o caminho quando é o servidor quem sabe o que a pessoa pode fazer.

Exemplos

O mesmo componente, com posição e conteúdo diferentes.

Alinhado pela direita

data-placement="bottom-end": para o botão encostado na borda direita.

Pelo lado

data-placement="right-start" abre ao lado, como um submenu.

Com ícones

O ícone vai em .tuc-dropdown__icon e fica esmaecido; no item de perigo, acompanha o vermelho.

Botão de ícone

O "mais ações" de uma linha de tabela. Sem texto visível, o aria-label é obrigatório.

As posições são as mesmas do tooltip: lado top, bottom, left ou right, e alinhamento start, center ou end. O padrão é bottom-start. Quando o lado pedido não cabe e o oposto cabe melhor, o menu vira; e ele nunca vaza da tela, desliza para dentro.

Como usar

Um botão com data-tuc-dropdown apontando para o painel, e o painel logo abaixo, com hidden. Ele inicializa sozinho, inclusive o que chegar depois por HTMX.

<button type="button" class="tuc-btn is-outline" data-tuc-dropdown="#actions">Ações</button>

<div class="tuc-dropdown" id="actions" hidden>
  <div class="tuc-dropdown__label">Contrato</div>
  <button type="button" class="tuc-dropdown__item">
    <span class="tuc-dropdown__text">Editar</span>
    <span class="tuc-dropdown__shortcut">⌘E</span>
  </button>
  <a class="tuc-dropdown__item" href="/contracts/12/">
    <span class="tuc-dropdown__text">Abrir</span>
  </a>
  <hr class="tuc-dropdown__separator">
  <button type="button" class="tuc-dropdown__item is-danger">
    <span class="tuc-dropdown__text">Excluir</span>
  </button>
</div>

O hidden é obrigatório

O script vem com defer e só roda depois de a página ser desenhada. Sem hidden, o menu aparece aberto no meio da página até lá. Na inicialização o painel é tirado do fluxo e passa a ser posicionado pelo componente, que tira o hidden sozinho.

Peças do painel

Classe ou atributoPara quê
.tuc-dropdownO painel. Precisa de id, para o gatilho apontar, e de hidden
.tuc-dropdown__itemUm item. <a href> para navegar, <button type="button"> para agir
.tuc-dropdown__textO rótulo; corta com reticências quando não cabe
.tuc-dropdown__iconÍcone à esquerda, 16px, na cor de apoio
.tuc-dropdown__shortcutAtalho à direita, com números de largura fixa para as teclas alinharem
.tuc-dropdown__labelTítulo de grupo, que não é clicável
.tuc-dropdown__separatorLinha entre grupos, num <hr>
is-dangerNo item: ação destrutiva, em vermelho
aria-disabled="true"No item: desativado. As setas pulam, o clique não fecha o menu

Item com href é um <a> de verdade. O clique do meio continua abrindo em outra aba, e o menu não rouba o menu de contexto do navegador. Clicar em qualquer item fecha o menu.

No template do Django

As ações que dependem de permissão ficam de fora no servidor, e não escondidas no navegador. Numa listagem, o id do painel leva a chave da linha para cada menu apontar para o seu.

{% for contract in contracts %}
  <button type="button" class="tuc-btn is-outline is-icon is-sm" aria-label="Ações"
          data-tuc-dropdown="#actions-{{ contract.pk }}" data-placement="bottom-end">...</button>

  <div class="tuc-dropdown" id="actions-{{ contract.pk }}" hidden>
    <a class="tuc-dropdown__item" href="{% url 'contract-edit' contract.pk %}">
      <span class="tuc-dropdown__text">Editar</span>
    </a>
    {% if perms.contracts.delete_contract %}
      <hr class="tuc-dropdown__separator">
      <a class="tuc-dropdown__item is-danger" href="{% url 'contract-delete' contract.pk %}">
        <span class="tuc-dropdown__text">Excluir</span>
      </a>
    {% endif %}
  </div>
{% endfor %}

Em JavaScript

Quando os itens são calculados na hora. O componente monta o painel, e cada item recebe o que fazer ao ser escolhido.

const menu = new Tucano.Dropdown('#actions', {
  placement: 'bottom-end',
  items: [
    { label: 'Contrato' },
    { text: 'Editar', icon: 'M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4z', shortcut: '⌘E', onClick: () => editContract() },
    { text: 'Duplicar', icon: Tucano.ICON_COPY, onClick: (dropdown) => duplicateContract() },
    { text: 'Abrir', href: '/contracts/12/' },
    { text: 'Arquivar', disabled: true },
    { separator: true },
    { text: 'Excluir', variant: 'danger', onClick: () => deleteContract() },
  ],
});

menu.open();
menu.close();
menu.toggle();
menu.destroy();   // fecha e solta os listeners
Chave do itemPara quê
textO rótulo
iconO d de um path SVG de 24×24, como os Tucano.ICON_*
shortcutTexto do atalho, à direita. Só mostra: quem liga a tecla é você
onClickChamado ao escolher, com a instância do menu
hrefFaz o item ser um <a>
variant'danger' vira is-danger
disabledDesativa: aria-disabled="true" e nenhum onClick
separatorSozinho, { separator: true } vira a linha
labelSozinho, { label: 'Seção' } vira título de grupo

closeOnPick: false mantém o menu aberto depois da escolha, para itens que ligam e desligam alguma coisa.

Teclado e acessibilidade

O gatilho ganha aria-haspopup="menu" e aria-expanded, o painel é um menu e cada item um menuitem. Abrir leva o foco para dentro, e fechar o devolve ao gatilho.

TeclaAção
Enter EspaçoNo gatilho: abre com o primeiro item já destacado
CliqueNo gatilho: abre sem destacar nada; a primeira seta destaca o primeiro item
↓No gatilho: abre direto no primeiro item
↑No gatilho: abre direto no último item
↑ ↓No menu: anda entre os itens, dando a volta e pulando os desativados
Home EndPrimeiro e último item
EscFecha e devolve o foco ao gatilho
TabFecha, e o foco segue a ordem da página a partir do gatilho

Os itens ficam fora do Tab de propósito

Cada item recebe tabindex="-1". Dentro de um menu quem anda é a seta; com os itens tabuláveis, o Tab sairia do menu um item por vez, que é exatamente o que o padrão de menu evita. E o foco volta ao gatilho porque, sem isso, quem navega por teclado fecharia o menu e cairia no começo da página.

Além do Escape, o menu fecha com clique fora, quando o foco vai para outro lugar e quando o gatilho sai da tela na rolagem. O realce do item acompanha o foco, e não só o ponteiro: é a seta que move o foco, e sem isso quem usa o teclado não veria onde está.

API

Gerada do código a cada build — se algo não está aqui, não existe.

Marcação
[data-tuc-dropdown]
Em JS
new Tucano.Dropdown(alvo, opcoes)
Atributos
data-placement data-tuc-dropdown
Métodos
openAt open close toggle destroy

Opções

OpçãoPadrãoPara quê
placement'bottom-start'
itemsnull[{ text, icon, shortcut, onClick, href, variant, disabled, separator, label }]
closeOnPicktrue