Skip to content
Versão preliminar. Cores, componentes e layout ainda vão mudar: falta receber o padrão de interface do TJES (template-basico-vue) e o manual de identidade visual.

BaseApprovalBar ​

Barra de decisão que fica presa embaixo da tela enquanto o fiscal confere uma medição ou um aditivo. Mostra o que está sendo decidido e quanto vale, e reúne as ações: devolver com justificativa, assinar (quando a pessoa também assina) e aprovar.

Nesta página as barras aparecem paradas dentro dos exemplos, para não se sobreporem. Numa tela de verdade, ela fica presa embaixo.

Medição 29 · Fórum de Alegre · R$ 412.903,55
Lançada por Paulo Ramos em 20/10 · 3 itens com observação

Nenhuma decisão ainda.

Uso ​

  • Use uma barra por tela, só quando a pessoa pode decidir. Sem permissão para decidir, não mostre a barra (v-if); com permissão mas sem poder decidir agora, use disabled e diga o motivo no resumo.
  • O resumo (slot padrão) repete o documento, a obra e o valor: ninguém aprova sem ver quanto. A segunda linha traz quem lançou, quando e o que merece atenção.
  • Aprovar é o único botão primário da tela enquanto a barra existe. O texto diz verbo e objeto: "Aprovar medição", não "OK".
  • Devolver usa o contorno de atenção, nunca o vermelho de perigo: devolver não destrói nada, só volta o fluxo um passo. Sempre pede justificativa; sem texto, "Devolver medição" não envia.
  • A barra abre o modal de devolução. Quem salva também fecha: use sempre v-model:return-open e, depois do evento return, troque loading para 'return', salve e feche. Se der erro, deixe o modal aberto, e a justificativa continua lá.
  • Depois de uma decisão salva, leve o foco a um ponto estável (o título da página ou a mensagem de resultado) e anuncie o resultado com BaseToast ou uma região role="status". Se a barra sai da tela com v-if, o botão que tinha o foco some junto; para a devolução, passe esse ponto em return-focus.
  • Coloque a barra como último filho da área que rola. Ela é position: sticky e passa por cima do que rola embaixo dela, então a área precisa deixar livre a altura da barra no scroll-padding-bottom, senão um campo com foco perto do fim da tela fica escondido. A barra mede a própria altura e publica em --tj-approval-height no <html>; o CSS dela já aplica esse valor na página e na área que tem a barra como filho direto. Se a área que rola for outra, aplique scroll-padding-bottom: var(--tj-approval-height) nela.
  • Em telas baixas (até 480 px de altura, como celular deitado ou zoom de 400%), a barra deixa de ser presa e fica no fim do conteúdo, para não tomar metade da tela.
  • No celular o resumo ocupa a primeira linha, os botões dividem a largura e "Devolver com justificativa" encurta para "Devolver".

Com assinatura ​

Para quem aprova e assina no mesmo lugar, signable mostra o botão Assinar entre devolver e aprovar.

Aditivo 3 · Fórum de Serra · + R$ 86.400,00
Prazo de 60 dias a mais · enviado por Ana Lima em 21/10

Estados ​

Medição 30 · R$ 98.120,00
Salvando a aprovação
Salvando a aprovação…
Medição 31 · R$ 54.300,00
Aguardando o parecer da engenharia para liberar a decisão
  • loading: o botão da ação que está salvando mostra progresso e continua no foco do teclado; os outros esperam desabilitados. Com 'return', o modal de devolução fica aberto e não fecha até terminar.
  • disabled: nenhuma ação pode ser tomada. Os botões continuam alcançáveis pelo teclado com aria-disabled, para a pessoa saber que a decisão existe, mas não fazem nada. Explique o motivo no resumo, porque um botão desabilitado não diz por quê. Se o modal de devolução estiver aberto, ele fecha sem devolver.

Estilo ​

PropriedadeToken
Fundo--tj-layer-01 a 92% com desfoque do que passa por trás; sólido com transparência reduzida no sistema
Borda de cima1 px em --tj-border-subtle-02
Sombra--tj-shadow-rgb a 18%, para cima
Espaço interno--tj-space-3 em cima e embaixo; --tj-space-8 dos lados no computador e --tj-space-4 abaixo de 992 px
Espaço entre partes--tj-space-4 entre resumo e botões e entre botões
Resumo--tj-text-sm, --tj-text-secondary; <strong> em --tj-text-primary e --tj-weight-semibold. Quebra linha em vez de cortar com reticências, diferente do protótipo, para nunca esconder o valor que está sendo decidido
Recuo de rolagemscroll-padding-bottom com --tj-approval-height (altura medida da barra); sem medição, --tj-control-md + 2 × --tj-space-3
Tela baixaAté 480 px de altura: position: static e recuo de rolagem zerado
BotõesBaseButton médio (--tj-control-md, 40 px): devolver com contorno de atenção (--tj-button-warning-border, --tj-support-warning-text), assinar secundário, aprovar primário
Camadaz-index 1030: acima da página, abaixo do modal (1050) e dos avisos (1060)
DesabilitadaBotões com aria-disabled na aparência de desabilitado do BaseButton, com o anel de foco
Alto contrasteFundo Canvas e borda CanvasText; botões desabilitados em GrayText

Código ​

vue
<script setup lang="ts">
import { BaseApprovalBar } from '@lab-inovacao/design-system'
</script>

<BaseApprovalBar
  v-if="canDecide"
  v-model:return-open="returning"
  :label="`Decisão da medição ${measurement.number}`"
  approve-label="Aprovar medição"
  :return-title="`Devolver a medição ${measurement.number}?`"
  :return-description="`A medição volta para ${measurement.launchedBy} com a sua justificativa.`"
  return-confirm-label="Devolver medição"
  :loading="saving"
  return-focus="#page-title"
  @approve="approveMeasurement"
  @return="returnMeasurement"
>
  <strong>Medição {{ measurement.number }} · {{ formatMoney(measurement.totalCents) }}</strong><br>
  Lançada por {{ measurement.launchedBy }} em {{ measurement.launchedAt }}
</BaseApprovalBar>
PropriedadeTipoPadrãoDescrição
labelstring—Nome da região, por exemplo "Decisão da medição 29"; obrigatório
approveLabelstring'Aprovar'Verbo e objeto da decisão principal
returnLabelstring'Devolver com justificativa'Texto do botão de devolver
returnShortLabelstring'Devolver'Texto no celular; precisa ser o começo de returnLabel, que continua sendo o nome lido
signLabelstring'Assinar'Texto do botão de assinar
signablebooleanfalseMostra o botão de assinar
disabledbooleanfalseTrava todas as ações; os botões continuam focáveis com aria-disabled
loading'approve' | 'return' | 'sign'—Ação que está salvando
savingTextsPartial<Record<'approve' | 'return' | 'sign', string>>'Salvando a aprovação…', 'Salvando a devolução…', 'Salvando a assinatura…'Texto lido pelo leitor de tela enquanto cada ação salva
actionsLabelstring'Ações da decisão'Nome da barra de ferramentas que agrupa os botões
returnTitlestring'Devolver para correção?'Pergunta do modal de devolução
returnDescriptionstring—O que acontece e com quem
returnConfirmLabelstring'Devolver'Botão que confirma a devolução
reasonHelperTextstring—Ajuda embaixo da justificativa
reasonPlaceholderstring—Exemplo dentro da justificativa
returnFocusHTMLElement | string—Recebe o foco quando o modal de devolução fecha e a barra já saiu da tela; elemento ou seletor CSS
v-model:return-openbooleanfalseModal de devolução aberto

Eventos: approve, return(reason: string) com a justificativa sem espaços nas pontas e nunca vazia, sign.

Slot padrão: o resumo do que está sendo decidido.

Acessibilidade ​

  • A barra é uma <section> com aria-label, ou seja, uma região: o leitor de tela acha a decisão pelo atalho de regiões.
  • Os botões ficam num role="toolbar" (padrão Toolbar do APG) com aria-label e aria-describedby no resumo, então ao chegar nos botões o leitor de tela ouve também o documento e o valor.
  • Teclado: a barra é uma parada só do Tab. ← e → passam de um botão para outro (voltando ao início no fim), Home e End vão ao primeiro e ao último, e Enter ou Espaço acionam. Botões que esperam outra ação salvar são pulados, e o Tab volta ao último botão usado. Com Alt, Ctrl, Shift ou Cmd as setas ficam com o navegador.
  • Com disabled, os botões continuam no teclado com aria-disabled (o APG permite quando é importante saber que a função existe): o leitor de tela anuncia "indisponível" e lê o resumo, que diz o motivo. Se a barra trava com o foco num botão, o foco fica nele.
  • O modal de devolução é o BaseConfirmModal com role="alertdialog": o foco vai para a justificativa, fica preso no modal e volta ao botão de devolver ao fechar, mesmo no Safari, que não dá foco ao botão clicado. Se a barra saiu da tela, o foco vai para return-focus. Esc, "Voltar" e o botão de fechar fecham sem devolver; clicar fora não fecha, para não perder o texto.
  • No celular, "Devolver" aparece na tela e "Devolver com justificativa" continua como nome lido. O texto visível é o começo do nome, como pede o critério 2.5.3 (rótulo no nome).
  • A cor nunca é o único sinal: cada botão tem texto e ícone próprios. O carregamento é marcado com aria-busy no botão e anunciado por uma região role="status" escondida ("Salvando a aprovação…"), porque NVDA e JAWS não leem aria-busy num botão com foco. O resultado da decisão é anunciado por quem salva (veja Uso).
  • Depois da decisão, o foco vai para um ponto estável escolhido pela tela, nunca cai no <body> (veja Uso).
  • Foco não encoberto (critério 2.4.11): a área que rola deixa livre a altura da barra com scroll-padding-bottom, então um controle com foco nunca fica escondido atrás dela. Em telas baixas a barra deixa de ser presa (critério 1.4.10, refluxo).
  • O foco aparece com o contorno sólido e o anel --tj-ring dos botões. A barra não tem animação própria; o indicador de carregamento do BaseButton gira, e com movimento reduzido no sistema gira devagar, sem as transições do botão.
  • Testado com axe no estado padrão, com assinatura, sem resumo, desabilitado, salvando cada uma das três ações, com o modal recém-aberto, com a justificativa em erro e salvando a devolução com o modal aberto. O jsdom não aplica media queries, então o layout do celular, a transparência reduzida e o alto contraste não têm teste automatizado.