Skip to content
Extensões

Guia do Componente Drawer

Painel lateral deslizante para exibição de conteúdo adicional, formulários, detalhes e notificações.

Visão Geral

O componente Drawer é um painel que desliza a partir da borda direita da tela, sobrepondo o conteúdo principal. Ideal para:

  • Formulários de edição/criação
  • Painéis de detalhes
  • Notificações e alertas
  • Configurações
  • Filtros avançados

Instalação

CSS

html
<link rel="stylesheet" href="/css/design-system/components/drawer.css">

JavaScript

html
<script src="/css/design-system/components/drawer.js" defer></script>

Uso Básico

Via JavaScript (Recomendado)

javascript
// Criar drawer simples
const drawer = Drawer.create({
    title: 'Detalhes do Item',
    content: '<p>Conteúdo do drawer aqui</p>'
});

// Fechar drawer
drawer.close();

// Ou pelo ID
Drawer.close('drawer-id');

Via HTML com Data Attributes

html
<!-- Trigger -->
<button data-drawer-trigger="meu-drawer">Abrir Drawer</button>

<!-- Template do Drawer -->
<template id="meu-drawer"
          data-drawer-title="Meu Título"
          data-drawer-size="md">
    <p>Conteúdo do drawer</p>
</template>

API JavaScript

Drawer.create(options)

Cria e exibe um novo drawer.

javascript
const drawer = Drawer.create({
    // Identificação
    id: 'meu-drawer',           // ID único (auto-gerado se omitido)

    // Conteúdo
    title: 'Título',            // Título do header
    subtitle: 'Subtítulo',      // Subtítulo (opcional)
    content: '<div>...</div>',  // Conteúdo HTML ou HTMLElement
    footer: '<button>...</button>', // Footer HTML ou HTMLElement (opcional)

    // Aparência
    size: 'md',                 // sm, md, lg, xl, full

    // Comportamento
    closeOnBackdrop: true,      // Fecha ao clicar fora
    closeOnEsc: true,           // Fecha ao pressionar ESC
    showClose: true,            // Exibe botão X
    trapFocus: true,            // Mantém foco dentro do drawer

    // Callbacks
    onOpen: (drawer) => {},     // Ao abrir
    onOpened: (drawer) => {},   // Após animação de abertura
    onClose: (drawer) => {},    // Ao fechar
    onClosed: (drawer) => {}    // Após animação de fechamento
});

Métodos da Instância

javascript
const drawer = Drawer.create({ ... });

// Fechar
drawer.close();

// Atualizar conteúdo
drawer.setTitle('Novo Título');
drawer.setContent('<p>Novo conteúdo</p>');
drawer.setFooter('<button>Novo botão</button>');

// Estado de loading
drawer.setLoading(true);
drawer.setLoading(false);

// Acessar elementos
const element = drawer.getElement();
const body = drawer.getBody();
const footer = drawer.getFooter();

Métodos Estáticos

javascript
// Fechar drawer específico
Drawer.close('drawer-id');

// Fechar todos
Drawer.closeAll();

// Obter instância
const drawer = Drawer.get('drawer-id');

// Verificar se há drawers abertos
if (Drawer.hasOpen()) { ... }

// Obter drawer ativo
const active = Drawer.getActive();

Tamanhos

ClasseLarguraUso Recomendado
sm400pxConfirmações, detalhes simples
md520pxFormulários médios (padrão)
lg700pxFormulários complexos
xl900pxDashboards, visualizações
full100%Tela cheia
javascript
Drawer.create({
    title: 'Drawer Grande',
    size: 'lg',
    content: '...'
});

Eventos

O drawer dispara eventos customizados que podem ser escutados:

javascript
// No elemento do drawer
document.addEventListener('drawer:open', (e) => {
    console.log('Drawer abrindo:', e.detail.drawerId);
});

document.addEventListener('drawer:opened', (e) => {
    console.log('Drawer aberto:', e.detail.drawerId);
});

document.addEventListener('drawer:close', (e) => {
    console.log('Drawer fechando:', e.detail.drawerId);
});

document.addEventListener('drawer:closed', (e) => {
    console.log('Drawer fechado:', e.detail.drawerId);
});

Exemplos

Drawer com Formulário

javascript
Drawer.create({
    title: 'Novo Usuário',
    size: 'md',
    content: `
        <form class="drawer__form">
            <div class="drawer__form-group">
                <label class="drawer__label drawer__label--required">Nome</label>
                <input type="text" class="input" name="nome" required>
            </div>
            <div class="drawer__form-group">
                <label class="drawer__label drawer__label--required">E-mail</label>
                <input type="email" class="input" name="email" required>
            </div>
            <div class="drawer__form-row">
                <div class="drawer__form-group">
                    <label class="drawer__label">Telefone</label>
                    <input type="tel" class="input" name="telefone">
                </div>
                <div class="drawer__form-group">
                    <label class="drawer__label">CPF</label>
                    <input type="text" class="input" name="cpf">
                </div>
            </div>
        </form>
    `,
    footer: `
        <button class="btn btn-outline" data-drawer-close>Cancelar</button>
        <button class="btn btn-primary" id="btn-salvar">Salvar</button>
    `,
    onOpened: (drawer) => {
        // Focus no primeiro input
        drawer.getBody().querySelector('input')?.focus();

        // Bind do botão salvar
        drawer.getElement().querySelector('#btn-salvar').addEventListener('click', async () => {
            drawer.setLoading(true);
            // ... lógica de salvamento
            drawer.close();
        });
    }
});

Drawer de Confirmação

javascript
// Usando helper
const confirmed = await createDrawerConfirm({
    title: 'Confirmar Exclusão',
    icon: 'delete',
    message: 'Deseja realmente excluir este item?',
    description: 'Esta ação não pode ser desfeita.',
    confirmText: 'Sim, excluir',
    cancelText: 'Cancelar',
    size: 'sm'
});

if (confirmed) {
    // Executar exclusão
}

Drawer com Conteúdo Dinâmico

javascript
const drawer = Drawer.create({
    title: 'Carregando...',
    size: 'lg',
    content: '<div class="drawer__empty"><div class="drawer__loading-spinner"></div></div>'
});

// Carregar conteúdo
const response = await fetch('/api/detalhes/123');
const data = await response.json();

drawer.setTitle(data.titulo);
drawer.setContent(`
    <div class="drawer__section">
        <h3 class="drawer__section-title">Informações</h3>
        <p>${data.descricao}</p>
    </div>
`);

Drawer com HTMLElement

javascript
// Criar elementos programaticamente
const content = document.createElement('div');
content.innerHTML = '<p>Conteúdo criado via DOM</p>';

const footer = document.createElement('div');
footer.innerHTML = '<button class="btn btn-primary">OK</button>';

Drawer.create({
    title: 'Drawer com Elements',
    content: content,
    footer: footer
});

Estrutura HTML

html
<!-- Backdrop -->
<div class="drawer-backdrop is-active" data-drawer-backdrop="drawer-id"></div>

<!-- Drawer -->
<div class="drawer drawer--md is-active" data-drawer="drawer-id" role="dialog" aria-modal="true">

    <!-- Header -->
    <div class="drawer__header">
        <div class="drawer__header-content">
            <h2 class="drawer__title">Título</h2>
            <p class="drawer__subtitle">Subtítulo opcional</p>
        </div>
        <button class="drawer__close" data-drawer-close aria-label="Fechar">
            <i class="mdi mdi-close"></i>
        </button>
    </div>

    <!-- Body -->
    <div class="drawer__body">
        <!-- Conteúdo aqui -->
    </div>

    <!-- Footer (opcional) -->
    <div class="drawer__footer">
        <button class="btn btn-outline" data-drawer-close>Cancelar</button>
        <button class="btn btn-primary">Salvar</button>
    </div>
</div>

Classes CSS

ClasseDescrição
.drawer-backdropFundo escurecido
.drawerContainer principal
.drawer--sm/md/lg/xl/fullVariantes de tamanho
.drawer.is-activeEstado aberto
.drawer__headerCabeçalho
.drawer__titleTítulo principal
.drawer__subtitleSubtítulo
.drawer__closeBotão fechar
.drawer__bodyÁrea de conteúdo
.drawer__footerRodapé com ações
.drawer__footer--start/center/betweenAlinhamento do footer
.drawer__sectionSeção de conteúdo
.drawer__section-titleTítulo de seção
.drawer__formContainer de formulário
.drawer__form-groupGrupo de campo
.drawer__form-rowLinha de campos (2 colunas)
.drawer__form-row--3Linha de campos (3 colunas)
.drawer__labelLabel de campo
.drawer__label--requiredLabel com asterisco
.drawer__errorsContainer de erros
.drawer__emptyEstado vazio
.drawer--loadingEstado de carregamento
.drawer--no-headerSem header
.drawer--no-footerSem footer
.drawer--no-paddingBody sem padding

Dark Mode

O componente suporta dark mode automaticamente através das classes:

  • .dark no elemento pai
  • [data-theme="dark"] no body
  • body.dark-mode

Acessibilidade

O drawer implementa:

  • role="dialog" e aria-modal="true"
  • aria-labelledby apontando para o título
  • Focus trap para navegação por teclado
  • Fechamento via ESC
  • Restauração do foco ao elemento anterior ao fechar
  • Suporte a prefers-reduced-motion

Responsivo

Em telas menores que 640px:

  • Drawer ocupa 100% da largura
  • Footer com botões em coluna
  • Formulários em coluna única

Comparação com Modal

AspectoModalDrawer
PosiçãoCentro da telaLateral (direita)
AnimaçãoScale + fadeSlide horizontal
Uso idealConfirmações, alertasFormulários, detalhes
ScrollInterno ao modalInterno ao drawer
TamanhoLimitadoPode ser full-height

Boas Práticas

  1. Use tamanhos apropriados: sm para confirmações, md para formulários simples
  2. Sempre inclua título descritivo: Ajuda na acessibilidade
  3. Mantenha ações no footer: Botões primários à direita
  4. Use loading states: Para operações assíncronas
  5. Evite drawers aninhados: Pode confundir o usuário
  6. Teste navegação por teclado: Verifique o focus trap

Troubleshooting

Drawer não abre

  • Verifique se o CSS e JS foram carregados
  • Verifique o console para erros

Z-index conflitante

  • O drawer usa z-index 1000-1001
  • Ajuste outros elementos se necessário

Scroll não funciona

  • Verifique se .drawer__body tem overflow-y: auto
  • O conteúdo precisa exceder a altura disponível

Focus não é capturado

  • Verifique se trapFocus: true (padrão)
  • Elementos focáveis devem estar dentro do drawer

Design System interno do GALES