Ir ao conteúdo
SEED engenharia

Componentes

31. Drawer

estávelseed-componentes.md v2.00 · §31seção 63 de 18431-drawer-estavel-v0-30-dw1-dw4-validacao.md · MD5 66752340

Título completo no canon: Drawer — estável (v0.30) · DW1–DW4 · validação executada + visual aprovada em 2026-08-05 · destrava a central de notificações (F6)

Em uma frase: Drawer é a alternativa ao modal quando a ação não é transiente: dois regimes (standard e modal) e z-index 65 na escala do sistema.

Ficha — Drawer

Encurtada do canon; o texto inteiro está na seção ↓ e no Markdown.

Anatomia e partes

Sem subseção própria — ver a seção §31.

Propriedades e variantes

Sem subseção própria — ver a seção §31.

Estados

Sem subseção própria — ver a seção §31. Regra geral dos estados: §0, item 2.

Acessibilidade

Sem subseção própria — ver a seção §31.

Faça · não faça — 3 de 13 decisões, na ordem do canon — ver mais na seção §31.1 ↓ · §31.4 ↓

faça (decisão)não faça (descartado)
DW1 — Drawer = superfície lateral…Regime único
DW2 — Direita default…Bottom sheet como forma mobile…
DW3 — Implementação…Biblioteca própria de posicionamento

Relacionados — as 3 mais citadas (de 7):

(decisão registrada — ver Histórico da seção)

31.1 Decisões DW1–DW4#

# Decisão Porquê Descartado
DW1 Drawer = superfície lateral p/ contexto/tarefa SEM perder o estado da página, em 2 regimes: modal-drawer (default: backdrop+trap) e standard (não-modal: página interativa). CN consome drawer; o regime dela é decisão da spec da CN (F6) EUI ownFocus; M3 standard×modal side sheets; caso central: detalhe de registro do SIGA e comparação lado-a-lado Regime único
DW2 Direita default; larguras sm/md; teto ~50% da viewport desktop; <640px vira painel de largura TOTAL deslizando da direita (continuidade espacial; safe-area) Não colide com o tray §24 (degrau do popover p/ conteúdo curto) Bottom sheet como forma mobile do drawer (criaria 2 destinos verticais concorrentes p/ o polegar: tray × drawer)
DW3 Implementação: o mesmo <dialog> posicionado — modal=showModal(); standard=show() (corretamente sem trap/backdrop; Esc fecha com foco dentro); scroll lock só no modal. React: Radix Sheet MDN (show × showModal é exatamente a distinção modal×não-modal) Biblioteca própria de posicionamento
DW4 Estrutura herda MD5 (3 zonas) e os empréstimos dos Blocos 1–3; form longo prefere drawer a modal (EUI) EUI Anatomia própria

31.2 Tokens#

Herda §30 integralmente (superfície/sombra/backdrop decorativo declarado); largura sm 400 / md 560; slide 200ms ease-out com reduced-motion; --seed-safe-bottom no rodapé mobile. Nenhum par novo.

Escala de sobreposição (emenda do gate visual, 2026-08-05): z-index explícito 65 em modal e drawer, consolidando a escala do sistema: toolbar/app-chrome 60 < modal/drawer 65 < toast 70 (feedback de ação permanece visível sobre a superfície) < popover 75 (pode abrir de dentro de modal/drawer) < tooltip 80. O 65 governa o regime standard (show() fica FORA do top layer) e qualquer chrome fixo da página; no browser, showModal() vive no top layer nativo, acima de tudo — e toasts disparados com modal aberto gravam na central de notificações (decisão do marco v1.0). Origem: bug real pego na validação visual do Rafael — drawer standard sem z-index era atravessado por conteúdo da página e tinha o X encoberto pelo chrome fixo; a suite não cobre empilhamento (jsdom sem layout), reconfirmando a lição do marco v1.0.

A escala é TOKEN (leva 30, TK-P5 = «a» e SH-P32 = «a», folha de 2026-10-10, resposta dele «a, a, a»). Os números acima deixam de ser só texto: --seed-z-chrome 60 · --seed-z-modal 65 · --seed-z-toast 70 · --seed-z-popover 75 · --seed-z-tooltip 80 · --seed-z-salto 90 (seed-tokens.md §3e). Invariantes de tema, números sem unidade; os moldes da casca passam a consumir o token (pixel nenhum: é o número que já escreviam) e o produto troca o bloco --camada-* que copiava os números. O degrau novo, salto = 90, é do link «Pular para o conteúdo» (SH8): fica ACIMA do tooltip para nunca empatar com ele; a casca usava 9990, número de andaime fora da escala, e o SIGA já usava 90. A paleta de comandos (§39) herda o modal: --seed-z-modal (65) — a casca declarava 75 e só uma suíte afirmava 75, sem regra do canon (decisão da sessão, exposta a veto; pixel nenhum, a paleta vive na camada do topo do navegador). Fora desta pergunta: o mapa das peças do Radix sem seção própria (HoverCard na faixa do popover, NavigationMenu com camada local) segue como lacuna de cobertura, e os consumidores de z-index fora da casca (bancadas, painel, vitrine) ainda escrevem o número à mão.

31.3 Os 7 testes#

1–2. Idem §30 ✓. 3. Grayscale ✓. 4. Modal-drawer × standard a 1s (backdrop presente × página viva). 5. Um drawer por vez; drawer e modal não empilham (a exceção MD4 é só confirmação de descarte). 6. Regime evidente sem interação. 7. 360px: largura total, X 44px, footer no polegar com safe-area. (Validação executada + visual aprovada em 2026-08-05, com emenda de empilhamento originada no gate visual.)

31.4 Painel lateral à direita — empurra até o piso, depois sobrepõe (PL1–PL9) — SH-P62, folha 11#

Para quem lê sem ter visto a conversa. Pedido dele, verbatim (2026-10-18 ~11:00Z, MANIFESTO §315): "quando eu clico para ler a folha, abre uma coluna do lado direito da minha tela, ela abre com um tamanho que reduz a area do chat, mediante eu clico para expandir a largura da folha para a esquerda a area do chat começa a reduzir e chega a um limite de redução, a partir desse momento a folha passa a sobre por o chat e o chat para de reduzir. [...] estude sobre essa aplicação para melhorarmos o nosso". O estudo SH-P62 (2026-10-18; referências M3 side sheets, Fluent 2 Drawer, Primer PageLayout e WAI-ARIA APG Window Splitter, lidas naquele dia) mediu as telas: o chat só sobrepunha (CH3), o detalhe em regime de painel empurrava sem piso (a 1100 px a lista ficava com 310 px — SH-P63) e a mensageria trocava de modo num limiar fixo de 1560 px de contêiner (MS5). As respostas da folha 11 (gravadas em 2026-10-18 18:56Z, verbatim no MANIFESTO §315.6): SH62a = a (piso 640 px) · SH62b = a (UMA regra só para todo painel lateral à direita) · SH62c = b (o conteúdo coberto NÃO fica inerte; contra a recomendação a) · SH62d = b (o painel do assistente do chat migra para a regra; muda o CH3; contra a recomendação a) · SH62e = a (aplicar agora) · SH60 = a (a gaveta da mensageria nasce abaixo do cabeçalho).

Onde vale: todo painel NÃO-MODAL que abre à direita de um conteúdo vivo — o assistente do chat (§55), o detalhe em regime de painel (§49 PD1), o painel do contato da mensageria (§94 MS5) e o drawer standard deste §31 quando hospedado ao lado de um conteúdo. Não vale para o drawer modal (showModal(), DW1: o fundo é inerte por definição), para o modal do detalhe (PD17) nem para a coluna fixa do gantt (GT15). Quem executa: o módulo único 06-validacao/ferramentas/painel_lateral_bloco.py (CSS, borda de arraste, botão Ampliar e script); nenhuma tela escreve a regra à mão. Tokens (seed-tokens.md §3f): --seed-side-panel-width 420 · --seed-side-panel-width-min 320 · --seed-side-panel-content-min 640.

# Decisão Porquê Descartado
PL1 W é a largura que o CONTEÚDO tem com o painel fechado (da borda esquerda do conteúdo à borda direita da região que o painel divide), medida ao vivo — não a da janela. A coluna de menu e a lista da mensageria ficam fora de W; quando elas abrem ou fecham, a regra se reavalia sozinha A pergunta é "quanto sobra para o conteúdo"; medir a janela faria o menu aberto (280 px) e o recolhido (56 px) darem o mesmo modo Limiar por media query de janela (o defeito de dois números que o CP27/CP28 já separou)
PL2 O modo, por uma função só: W < piso (640) → tela cheia (o painel ocupa a região; a 390 px, de uma vez) · W < piso + mínimo (320) + vão → sobrepõe de cara (o conteúdo não encolhe) · painel + vão ≤ W − piso → empurra (conteúdo = W − painel − vão, nunca < 640) · senão → sobrepõe com o conteúdo parado no piso É a regra que ele descreveu, e as duas formas da hipótese dele ("telas grandes pode reduzir o chat", "telas menores ... já sobrepor de uma vez") saem do mesmo número Dois limiares independentes (SH62b = a); só sobrepor (o CH3 antigo); só empurrar (a lista de 310 px da SH-P63)
PL3 Largura: abre com 420; se 420 deixaria o conteúdo abaixo do piso, abre com a largura que ainda cabe sem cobrir (W − 640 − vão), nunca abaixo de 320. O teto do arraste é W − 48 (--seed-sp-12): sempre sobra uma faixa do conteúdo à vista Abrir já cobrindo, quando havia espaço para não cobrir, contradiz o pedido; o teto deixa o conteúdo alcançável pelo ponteiro Abrir sempre com 420
PL4 Empurrar = reserva no CONTEÚDO (margin-right), com o painel posicionado na borda direita da região; sobrepor = o painel por cima do conteúdo parado Margem no conteúdo, e não recuo na região, porque na mensageria a lista tem 30 % da casca e encolheria junto (medido no desenho) padding-right na região
PL5 A borda esquerda do painel é role="separator" focável (WAI-ARIA APG, Window Splitter): ←/↑ alarga 16 px (--seed-sp-4), →/↓ estreita, Shift multiplica por 48 px (--seed-sp-12), Home = mínimo, End = máximo, Enter alterna padrão ↔ máximo; duplo clique volta ao padrão. Alvo de 24 px (44 px com ponteiro grosso). aria-valuenow/min/max em px e aria-valuetext em português com o modo ("410 px de painel do detalhe; conteúdo com 640 px; o painel empurra o conteúdo") — desvio declarado do APG (que usa %), porque o máximo muda com W e px é o que a pessoa entende APG para o teclado; WCAG 2.5.8 para o alvo Valor em % (o APG puro)
PL6 Alternativa sem arraste: botão Ampliar/Reduzir no cabeçalho do painel (aria-pressed, rótulo e ícone acompanhando — os glifos do PD17), padrão ↔ máximo; some em tela cheia WCAG 2.5.7 (o mesmo raciocínio do PD17 e do §51) Só a borda
PL7 O conteúdo NUNCA fica inerte com o painel aberto (SH62c = b): continua interativo, inclusive a parte coberta. A troca de modo é anunciada na região role="status" da tela ("O painel do assistente agora sobrepõe o conteúdo."). Foco: o que cada tela já tinha, sem armadilha de Tab — na mensageria abrir pelo ⓘ leva o foco para dentro e Esc com o foco dentro fecha e devolve o foco ao ⓘ (SH-P59); no chat o painel fecha pelo » (CH2) e não há Esc (não implementado, já era assim); no detalhe em regime painel também não há Esc que feche o painel (o Esc nativo é do modal, PD14) (o Esc do chat e do detalhe foi superado na leva 49, SH-P76: o Esc com o foco dentro do painel aberto fecha o painel e devolve o foco ao gatilho nas três telas; ver «Esc: uma camada por vez» abaixo da tabela) Decisão dele. Custo conhecido, medido pela revisão independente da leva 45 (2026-10-24, 70 paradas de Tab por tela, Chrome): com o painel aberto, as paradas de Tab que caem sob o painel são, na mensageria, 15 de 65 a 1440 px e 28 de 65 a 1100 px; no chat e no detalhe, 0 a 1 (a 1 é a região rolável da tabela do chat). Não é regressão: no HEAD 4ef6d346 a mensageria dava 19 de 63 e 31 de 63 (o ⓘ e os botões do cabeçalho deixaram de ficar sob o painel, PL9). O SC 2.4.11 pede que o foco não fique inteiramente encoberto; nenhuma mitigação está no código (rolar ou estreitar o painel ao focar algo encoberto seria decisão nova dele, porque SH62c = b) inert enquanto sobrepõe (recomendação a da folha)
PL8 Tela cheia (W < 640): o painel toma a região inteira. Onde o conteúdo é uma lista ou tabela (chat, detalhe) o conteúdo sai do fluxo enquanto o painel está aberto ("substitui", o que o detalhe já fazia a 390 px); na mensageria o painel cobre a casca abaixo do cabeçalho da conversa (PL9) e o resto da conversa sai da vista e do Tab O conteúdo inteiro sob um painel inteiro não tem leitura; esconder é o honesto Empilhar abaixo (o chat antigo a 390 px: o painel ficava a 697 px do topo, fora da primeira tela)
PL9 O painel que cobre nasce abaixo do cabeçalho do conteúdo quando o botão que o abre mora nesse cabeçalho (SH60 = a): na mensageria, com o painel sobrepondo ou em tela cheia, ele começa a 64 px (MS10) e o ⓘ ligado continua visível e clicável. Parâmetro topo_cobrindo do módulo O botão de alternar que some sob o que ele abriu deixa a pessoa sem o caminho de volta pelo mesmo lugar O painel sobre o cabeçalho (o estado anterior: o ⓘ coberto nas 3 larguras, medido)
PL10 A coluna larga do rail cede ao piso (SH-P69, leva 47). O piso de 640 px vale também contra a Expandida do rail (160 px): com o painel aberto, se o conteúdo ficaria abaixo de 640 px com a Expandida e a Encurtada (80 px) o leva a 640 px ou mais, o rail usa os 80 px (data-rail-cede="painel" no .app; a preferência data-rail não muda) e o painel NÃO vai a tela cheia. A decisão é medida ao vivo, não calculada (na mensageria a lista de 30 % absorve parte dos 80 px), e depende só da largura: o mesmo resultado subindo ou descendo a janela. A troca de data-rail/data-sidebar no .app é reavaliada antes do quadro seguinte (SH-P73: nenhum quadro pintado com o conteúdo na largura de antes). O empurrão anima (SH-P71, leva 48): o margin-right do conteúdo leva --seed-dur-productive (150 ms; só depois de data-painel-pronto, nunca durante o arraste da borda; movimento reduzido: de uma vez — veredito dele, folha 14, Q5 = c, 2026-10-28, verbatim: "aplicar 150 ms direto.", que supersede a D14 do SH29), mas a decisão do modo, o piso de 640 px e esta cessão leem o estado FINAL (o módulo adianta as transições, lê e devolve), e o ResizeObserver do módulo reavalia NA MESMA passada do layout, sem esperar o quadro seguinte (SH-P75: antes, 3 de 3 telas pintavam um quadro com o conteúdo errado; depois, 3 de 3 certas). Prova: render-rail.mjs R18-p (varredura nos dois sentidos) e R18-q (esta mede CONGELADA, cega ao meio da animação); prova-rail-cobaias.mjs; prova-animacao-150.mjs prova o meio da animação.

Esc: uma camada por vez (SH-P76, leva 49; SH-P78, leva 50). Um Esc fecha UMA camada. O menu ou a lista que está aberta por cima do painel (o menu de conta do SH21, o menu de envio e a lista de pessoa do detalhe) trata o próprio Esc, chama preventDefault e devolve o foco ao próprio gatilho; o módulo do painel (painel_lateral_bloco.py) só fecha o painel quando o Esc chega sem defaultPrevented e fora da composição de IME (método de entrada; isComposing). A mensageria usa o mesmo guarda no molde (tela-mensageria-template.html; até a leva 49 o guarda do molde não olhava isComposing — defeito latente, porque o painel da mensageria não tem campo editável). Medido na leva 50: os 4 casos novos do menu de envio e da lista de pessoa passaram de primeira (não havia defeito); o caso da composição na mensageria reprovava antes do conserto (93 de 94) e passa depois (94 de 94). A composição é simulada (KeyboardEvent com isComposing: true); IME real NÃO MEDIDO. Prova: prova-painel-lateral.mjs, casos E-*-menu, E3-* e E-mensageria-ime; cobaias prova-painel-lateral-cobaias.mjs, 5 de 5.

Persistência: a largura escolhida é preferência da pessoa (PD1), guardada por painel (localStorage, chave seed-painel-<nome> na referência; no produto é campo da conta) e limitada aos limites da janela de agora.

Prova: 06-validacao/provas/painel-lateral/prova-painel-lateral.mjs (no rito): a função pura, o modo das três telas a 1440, 1100 e 390 px (e 1700 na mensageria) nos dois temas, o arraste além do piso, o teclado, o Ampliar, a persistência, o ⓘ da mensageria e a CH3 nova. Placar declarado antes: 71 de 71 nas telas; 22 PASS · 49 FAIL nas telas da regra antiga (cobaia do HEAD 4ef6d346); o ⓘ é medido nos dois temas.

BancadaSEED DS v2 — Preview Bloco 4 · sub-bloco F (superfícies completas 7/7) — v0.32c (CP-P4)abrir em página própria ↗

Também cita o §31: banco-cn, tela-atividade, tela-busca, tela-chat, tela-detalhe, tela-gantt, tela-lista, tela-mensageria, tela-onboarding, tela-portal-cliente, tela-portal-parceiro, tela-quadro, tela-quadro-funil, tela-referencia, tela-shell, tela-shell-abas, tela-shell-sem-topbar, tela-tabela.

Esc