Componentes
18. Spinner
seed-componentes.md v1.43 · §18seção 20 de 9818-spinner-estavel-validado-pelo-rafael-em-2026-08.md · MD5 d67c84a8Título completo no canon: Spinner — estável · validado pelo Rafael em 2026-08-04 (2 ciclos de crítica → emendas R3-b/R3-c; suite v0.17)
Base (3 rodadas, 2026-08-04): R1 — NN/g (progress indicators: <1s nada, loop 2–10s; skeleton 101), Carbon (loading lg/sm, inline loading 4 estados, um indicador por vez), Smashing. R2 — React Aria useProgressBar (spinner = progressbar circular), shadcn/Radix, EUI (herança). R3 — SAP Fiori busy indication (delay padrão de 1s em todos os controles; bloquear o mínimo), EC Europa (sempre com mensagem; ≤1 lg por página; nunca <1s), issue de a11y do UI5 (equivalente textual anunciado), aria-busy (escopo = a região que muda; segura anúncios parciais).
18.1 A RÉGUA DE ESPERA (transversal do sub-bloco B — governa §18–§20; análoga à régua do §7.1)#
| Espera | Indicador |
|---|---|
| < 1s | NADA — indicador que pisca é ruído (NN/g; Fiori: delay padrão 1s; EC) |
| 1–10s, fração desconhecida | Spinner para ação/processo local · skeleton para carga de ESTRUTURA de conteúdo |
| > 10s ou fração conhecida | Progress determinate + texto de status; pode nascer indeterminada e converter (Carbon/m2) |
R3-b (emenda da validação visual do Rafael, 2026-08-04 — supersede parcial do R3): a ENTRADA do indicador tem TRÊS regimes, não um. A crítica "o delay de 1s vai parecer travado" foi medida contra a evidência e está correta em dois dos três casos — o R3 original colapsava situações distintas:
- Ação do usuário (clicar salvar, alternar switch): reconhecimento IMEDIATO ≤100ms — o próprio controle entra em loading (spinner sm do §1), SEM delay. Fonte: os 3 limites de Nielsen (0,1s = reação instantânea obrigatória; o limite de 1s pressupõe que o clique JÁ foi reconhecido); Carbon inline loading mostra o estado ativo imediatamente após a ação.
- Carga inicial de tela/região VAZIA: skeleton IMEDIATO, sem delay — não há UI antiga para segurar a percepção; 1s de branco lê como travamento. Fonte: Carbon usa skeleton "on initial page load"; o delay do Fiori pressupõe explicitamente "mostrar a UI por um segundo" — ou seja, UI existente.
- Refresh sobre conteúdo EXISTENTE (tabela recarregando, filtro aplicado): delay de 1s antes do indicador (Fiori/EC/NN/g) — a UI antiga permanece visível e utilizável; flash de overlay em resposta rápida seria pior que a espera.
R3-c (2ª crítica do Rafael na mesma validação — "o refresh lento ainda demora"): os regimes COMPÕEM, não se excluem. O regime 3 quase nunca existe sozinho: refresh de região no ERP quase sempre nasce de uma AÇÃO do usuário (filtro, recarregar) — e então o regime 1 SE APLICA JUNTO: o controle disparador reconhece em ≤100ms (loading do §1) e o indicador de REGIÃO entra só se a espera passar de 1s. O vácuo percebido não era o delay — era refresh sem ack no controle, que a régua agora PROÍBE: toda espera do regime 3 disparada por ação exige ack imediato no controle disparador. Refresh iniciado pelo sistema (dado chegando por conexão viva) é o único caso de delay sem ack — e ali o usuário não pediu nada, então não há clique órfão. Nielsen fecha: o limite de 1s sem indicador pressupõe o clique reconhecido em ≤0,1s. O delay de 1s fica parametrizado (--seed-wait-delay, default 1000ms) e será revisitado com telemetria real do ERP na Fase 4 — pendência declarada, não decisão aberta.
Complemento mantido: uma vez exibido, permanência mínima de ~400ms (anti-flash de saída — inferência de engenharia declarada). Descartado: delay único de 1s para tudo (o R3 original — reprovado na validação visual); regime 3 sem ack no controle (reprovado na 2ª crítica).
18.2 Decisões R1–R6#
| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| R1 | Dois tamanhos com papéis: lg para seção/página com overlay semitransparente BLOQUEANTE; sm inline (célula, linha) — e o spinner do botão É o loading do §1 (herda) | Carbon: lg = takeover com overlay; inline desabilita os controles envolvidos contra ação repetida | Tamanho único |
| R2 | NUNCA nu: sempre frase junto ("Carregando propostas…"); máx. UM lg por página | EC: sempre com mensagem; não mais de um por página (salvo sm em componentes); UI5 a11y: equivalente textual anunciado | Spinner mudo |
| R3 | Delay 1s cancelável — restrito ao regime 3 (refresh sobre conteúdo existente); ver emenda R3-b no §18.1 para ação do usuário (imediato no controle) e carga inicial (skeleton imediato) | Fiori/EC/NN/g, contextualizados pela emenda | Exibição imediata em refresh (flash); delay único para tudo (R3 original, superseded pela R3-b) |
| R4 | ARIA: SVG decorativo (aria-hidden); a região que carrega recebe aria-busy="true" (segura anúncios parciais) + live region role="status" pré-existente (1.13-b) anuncia UMA frase no início e UMA no fim ("Propostas carregadas") |
aria-busy: um anúncio coerente ao voltar a false; sem live region + indicador visível, ninguém percebe nada | Anunciar cada mutação; role no SVG |
| R5 | prefers-reduced-motion: sem rotação — indicador estático + a frase carrega o sinal |
WCAG 2.3.3; padrão §0.5/§8.3 | Rotação sempre |
| R6 | UM sinal de espera por região: nunca spinner sobre skeleton nem dois indicadores no mesmo alvo | Carbon: evitar múltiplos simultâneos; redundância é ruído | Empilhar sinais |
18.3 Anatomia e tokens#
lg 32px / sm 16px; traço 3px/2px em --seed-action-primary-bg sobre trilho --seed-border-subtle; rotação 900ms linear contínua; overlay do lg: página com véu (--seed-surface-page a 60%) + spinner centrado + frase abaixo. Frase em --seed-text-secondary.
18.4 Os 7 testes#
- Traço sobre trilho ≥3:1 (medir). 2. Espera sinalizada por movimento + frase (nunca só cor). 3. Grayscale: movimento + frase carregam. 4. lg×sm distinguíveis pelo escopo (overlay × inline). 5. Um lg por página; uma live region por região. 6. Estado visível: a frase diz O QUE carrega. 7. 360px: overlay cobre a viewport com frase legível; sm cabe na linha de tabela densa. (Verificado: suite v0.17, aprovado.)