Tucano v0.37.2

Gaveta

Painel que entra por uma borda e ocupa o eixo inteiro dela: filtros de uma lista, menu no celular, carrinho, detalhe de um registro. Divide com o modal a mecânica do <dialog> — o que muda é a geometria e o movimento: no centro o diálogo cresce, na borda ele desliza, porque é assim que a gaveta diz de onde veio.

Filtros

Refine a lista de contratos.

Situação

Exemplos

Todos abaixo são o mesmo Tucano.drawer() com opções diferentes.

Os quatro lados

side escolhe a borda; right é o padrão.

Largura da coluna

size nas laterais: 18, 24 ou 34rem.

Tons

tone muda o brilho, que nasce da borda em que a gaveta encosta.

Sem fechar por fora

closeOnBackdrop: false e closable: false, como no modal.

Conteúdo livre

content(nó) preenche o corpo, que rola quando o conteúdo passa da altura.

Menu de aplicação

Gaveta pela esquerda com a lista de navegação dentro — o exemplo completo está no menu lateral.

Como usar

Os mesmos dois caminhos do modal. Pelo JavaScript a gaveta é montada na hora e sai do DOM ao fechar; pelo template ela já está na página, e o componente só abre, fecha e anima.

Em JavaScript

Tucano.drawer({
  title: 'Filtros',
  text: 'Refine a lista de contratos.',
  side: 'right',      // left | right | top | bottom
  size: 'md',         // sm | md | lg — nas laterais, largura da coluna
  tone: 'default',    // default | danger | success | warning
  actions: [
    { text: 'Limpar', variant: 'ghost' },
    { text: 'Aplicar', variant: 'primary', onClick: (drawer) => apply() },
  ],
}).content(form);

// Montada antes, aberta depois
const drawer = new Tucano.Drawer({ title: 'Carrinho', side: 'right' });
drawer.content(list).open();
drawer.close();

actions segue as mesmas regras do modal: text, variant (padrão outline), onClick, que recebe a instância, e closes: false para manter aberta. onClose(reason, drawer) recebe 'button', 'action', 'escape', 'backdrop' ou 'api'.

No template do Django

O caso típico é o formulário de filtros de uma lista: ele é renderizado pelo servidor, com os valores que vieram no request.GET, e a gaveta só o esconde até alguém pedir.

<dialog class="tuc-drawer is-right is-md" id="filters" aria-labelledby="filters-title">
  <form class="tuc-drawer__panel" method="get">
    <div class="tuc-drawer__top">
      <div class="tuc-drawer__header">
        <h2 class="tuc-drawer__title" id="filters-title">Filtros</h2>
      </div>
      <button type="button" class="tuc-btn is-ghost is-icon is-sm tuc-drawer__close" aria-label="Fechar" data-tuc-drawer-close>…</button>
    </div>
    <div class="tuc-drawer__body">
      {{ form.as_div }}
    </div>
    <div class="tuc-drawer__footer">
      <a class="tuc-btn is-ghost" href="?">Limpar</a>
      <button class="tuc-btn is-primary">Aplicar</button>
    </div>
  </form>
</dialog>

<button class="tuc-btn is-outline" data-tuc-drawer="#filters">Filtros</button>
AtributoOndePara quê
data-tuc-drawer="#id"GatilhoAbre a gaveta do seletor ao clicar
data-tuc-drawer-closeBotão dentro da gavetaFecha, com o motivo 'button'
data-closable="false"<dialog>O Esc deixa de fechar
data-backdrop="false"<dialog>O clique no fundo deixa de fechar

Todo dialog.tuc-drawer é adotado no carregamento e a cada htmx:afterSwap. As classes internas seguem o nome do componente: __panel, __top, __header, __title, __text, __close, __body e __footer. No template, escreva o X e o aria-labelledby: pelo JavaScript os dois saem prontos, à mão não.

Lados e tamanhos

A caixa estica no eixo da borda em que encosta — nas laterais ocupa a altura toda, em cima e embaixo a largura toda — e a borda da caixa fica só do lado que toca a página.

sideClasseEntra deO que size controla
rightis-rightDireita — o padrãoLargura da coluna
leftis-leftEsquerdaLargura da coluna
topis-topCimaNada: a largura é a da tela e a altura, a do conteúdo
bottomis-bottomBaixoNada: a largura é a da tela e a altura, a do conteúdo
sizeLargura nas laterais
sm18rem
md24rem — o padrão
lg34rem

Gaveta lateral é uma coluna, não um cartão, e por isso a escala é outra que a do modal. No celular (abaixo de 40rem) ela para em min(20rem, 85vw) qualquer que seja o tamanho: coluna estreita ali não se lê, e a faixa que sobra mostra que a página continua atrás. Os botões do rodapé ocupam a linha, como no modal.

O brilho nasce da borda

No modal a luz vem do centro da tela. Na gaveta ela sai do lado em que o painel encosta, e a elipse é maior: nascendo na borda, só metade dela fica na tela. Centrada, a luz apontaria para o meio da página enquanto o painel entra pela lateral.

Teclado e acessibilidade

É um <dialog> aberto com showModal(), igual ao modal: a página atrás fica inerte, o foco não escapa e volta para quem abriu ao fechar.

TeclaAção
Tab Shift+TabAnda entre os controles da gaveta, sem voltar para a página
Enter EspaçoAciona o botão focado
EscFecha com animação e devolve o foco; não faz nada com closable: false

Divide o motor com o modal

Top layer, foco preso, Esc e foco devolvido moram numa base só, e não em duas implementações parecidas. É isso que impede uma correção feita num deles de deixar de valer no outro.

Com title, a gaveta ganha aria-labelledby. O X é um .tuc-btn com aria-label="Fechar". Com prefers-reduced-motion, a entrada dura 100 ms e não desliza.

API

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

Marcação
dialog.tuc-drawer [data-tuc-drawer-close] [data-tuc-drawer]
Em JS
new Tucano.Drawer(opcoes) Tucano.drawer()
Atributos
data-backdrop data-closable data-tuc-drawer
Métodos
open close content

Opções

OpçãoPadrãoPara quê
titlenull
text''
side'right'left | right | top | bottom
size'md'sm | md | lg — nas laterais, largura da coluna
tone'default'default | danger | success | warning
closabletrue
closeOnBackdroptrue
actionsnull[{ text, variant, onClick, closes }] — closes:false mantem aberto
onClosenull
className''