Como criar um componente de aviso

Uma visão geral básica de como criar um componente de aviso adaptável e acessível.

Nesta postagem, quero compartilhar ideias sobre como criar um componente de aviso. Experimente o demonstração.

Demonstração

Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Os avisos são mensagens curtas não interativas, passivas e assíncronas para os usuários. Geralmente, são usados como um padrão de feedback de interface para informar o usuário sobre os resultados de uma ação.

Interações

As notificações Avisos não são iguais às notificações, alertas e comandos porque eles não são interativos. eles não devem ser dispensados ou mantidos. As notificações se destinam a informações mais importantes, mensagens síncronas que requer interação ou mensagens no nível do sistema (não no nível da página). Os avisos são mais passivos do que outras estratégias de notificação.

Marcação

A <output> é uma boa escolha para o aviso, porque ele é anunciado para a tela leitores. O HTML correto fornece uma base segura para aprimorarmos com JavaScript e CSS e haverá bastante JavaScript.

Um brinde

<output class="gui-toast">Item added to cart</output>

Podem ser mais inclusivo adicionando role="status". Isso oferece uma substituto caso o navegador não forneça aos elementos <output> a mensagem implícita cargo de acordo com as especificações.

<output role="status" class="gui-toast">Item added to cart</output>

Um contêiner de aviso

É possível mostrar mais de um aviso por vez. Para orquestrar vários um contêiner é usado. Esse contêiner também processa a posição do avisos na tela.

<section class="gui-toast-group">
  <output role="status">Wizard Rose added to cart</output>
  <output role="status">Self Watering Pot added to cart</output>
</section>

Layouts

Optei por fixar avisos inset-block-end da janela de visualização e, se mais avisos forem adicionados, eles serão empilhados a partir da borda da tela.

Contêiner da GUI

O contêiner de avisos faz todo o trabalho de layout para apresentar avisos. Está fixed à janela de visualização e usa a propriedade lógica inset para especificar bordas para fixar, além de um pouco de padding da mesma borda block-end.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

Captura de tela com o tamanho da caixa e o padding do DevTools sobrepostos em um elemento .gui-pt-br.

Além de se posicionar dentro da janela de visualização, o contêiner de avisos é uma contêiner de grade que pode alinhar e distribuir avisos. Os itens são centralizados como uma grupo com justify-content e centralizado individualmente com justify-items. Coloque um pouco de gap para que os avisos não toquem.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

Captura de tela com a sobreposição de grade CSS no grupo de avisos, desta vez
destacando o espaço e as lacunas entre os elementos filhos de avisos.

Aviso da GUI

Uma torrada individual tem um pouco de padding e alguns cantos mais suaves com border-radius, e uma função min() para ajudam no dimensionamento de dispositivos móveis e computadores. O tamanho responsivo no seguinte CSS impede que os avisos fiquem mais largos em 90% da janela de visualização ou 25ch

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Captura de tela de um único elemento .gui-pt-br, com padding e borda
do raio de destino exibido.

Estilos

Com o layout e o posicionamento definidos, adicione CSS que ajude na adaptação ao usuário configurações e interações.

Contêiner das torras

As notificações Avisos não são interativas. Tocar ou deslizar nelas não faz nada, mas atualmente consomem eventos de ponteiro. Evite que os avisos roubem cliques com o seguinte CSS.

.gui-toast-group {
  pointer-events: none;
}

Aviso da GUI

Aplicar aos avisos um tema adaptável claro ou escuro com propriedades personalizadas, HSL e uma consulta de mídia de preferência.

.gui-toast {
  --_bg-lightness: 90%;

  color: black;
  background: hsl(0 0% var(--_bg-lightness) / 90%);
}

@media (prefers-color-scheme: dark) {
  .gui-toast {
    color: white;
    --_bg-lightness: 20%;
  }
}

Animação

Um novo aviso vai aparecer com uma animação ao entrar na tela. Acomodação de movimento reduzido é feita definindo valores translate como 0 por por padrão, mas atualizar o valor do movimento para um comprimento em uma mídia de preferência de movimento consulta . Todos recebem uma animação, mas só alguns usuários têm o aviso certa distância.

Aqui estão os frames-chave usados para a animação de aviso. o CSS vai controlar entrada, espera e saída do aviso, tudo em uma animação.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

Em seguida, o elemento de notificação configura as variáveis e orquestra os frames-chave.

.gui-toast {
  --_duration: 3s;
  --_travel-distance: 0;

  will-change: transform;
  animation: 
    fade-in .3s ease,
    slide-in .3s ease,
    fade-out .3s ease var(--_duration);
}

@media (prefers-reduced-motion: no-preference) {
  .gui-toast {
    --_travel-distance: 5vh;
  }
}

JavaScript

Com os estilos e o HTML acessível pelo leitor de tela pronto, o JavaScript é necessário para orquestrar a criação, adição e destruição de avisos com base nas informações eventos. A experiência do desenvolvedor com o componente de aviso deve ser mínima, fácil de começar, como este:

import Toast from './toast.js'

Toast('My first toast')

Como criar o grupo e os avisos

Quando o módulo de aviso é carregado usando JavaScript, ele precisa criar um contêiner de aviso. e adicioná-lo à página. optei por adicionar o elemento antes de body, isso tornará z-index problemas de empilhamento são improváveis porque o contêiner está acima do contêiner por todos os elementos do corpo.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

Captura de tela do grupo de avisos entre as tags de cabeçalho e corpo.

A função init() é chamada internamente para o módulo, armazenando o elemento como Toaster:

const Toaster = init()

A criação do elemento HTML do aviso é feita com a função createToast(). A requer algum texto para o aviso, cria um elemento <output>, adorna com algumas classes e atributos, define o texto e retorna o nó.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

Como gerenciar um ou vários avisos

O JavaScript agora adiciona um contêiner ao documento para conter avisos e é pronto para adicionar os avisos criados. A função addToast() orquestra o gerenciamento de um ou muitos brindes. Primeiro, verificando o número de avisos e se o movimento está correto, e usar essas informações para acrescentar o aviso ou fazer algo animação para que os outros avisos apareçam para "abrir espaço" para o novo aviso.

const addToast = toast => {
  const { matches:motionOK } = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  )

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

Ao adicionar o primeiro aviso, o Toaster.appendChild(toast) adiciona um aviso ao página acionando as animações CSS: anime para dentro, aguarde 3s, saia. O flipToast() é chamado quando há avisos existentes, empregando uma técnica chamado FLIP por Paul Lewis (em inglês). A ideia é calcular a diferença nas posições do contêiner, antes e depois da adição do novo aviso. É como marcar onde a torradeira está agora, onde ela vai estar, animando onde ela estava para ficar.

const flipToast = toast => {
  // FIRST
  const first = Toaster.offsetHeight

  // add new child to change container size
  Toaster.appendChild(toast)

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

  // PLAY
  const animation = Toaster.animate([
    { transform: `translateY(${invert}px)` },
    { transform: 'translateY(0)' }
  ], {
    duration: 150,
    easing: 'ease-out',
  })
}

A grade CSS faz o levantamento do layout. Quando um novo aviso é adicionado, a grade o coloca no início e espaça com os outros. Enquanto isso, um web animação é usada para animar o contêiner a partir da posição antiga.

Como juntar todo o JavaScript

Quando Toast('my first toast') é chamado, um aviso é criado e adicionado à página (talvez até o contêiner seja animado para acomodar o novo aviso), promessa é retornado e o aviso criado é assistiu por Conclusão da animação CSS (as três animações de frame-chave) para resolução de promessas.

const Toast = text => {
  let toast = createToast(text)
  addToast(toast)

  return new Promise(async (resolve, reject) => {
    await Promise.allSettled(
      toast.getAnimations().map(animation => 
        animation.finished
      )
    )
    Toaster.removeChild(toast)
    resolve() 
  })
}

Achei que a parte confusa desse código está na função Promise.allSettled() e mapeamento toast.getAnimations(). Como usei várias animações de frame-chave, para o brinde, para saber com confiança que todos terminaram, cada um deve ser solicitações de JavaScript, e cada uma finished de promessas observadas para conclusão. allSettled isso funciona para nós, resolvendo-se como completa quando todas as promessas foram cumpridas. O uso de await Promise.allSettled() significa a próxima linha de pode remover o elemento com segurança e presumir que o aviso terminou no ciclo de vida de ML. Por fim, chamar resolve() atende à promessa de aviso de alto nível para que os desenvolvedores podem limpar ou fazer outro trabalho quando a notificação for exibida.

export default Toast

Por fim, a função Toast é exportada do módulo, para que outros scripts importar e usar.

Como usar o componente Toast

O uso do aviso, ou da experiência do desenvolvedor da notificação, é feito importando o Toast e chamá-la com uma string de mensagem.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Se o desenvolvedor quiser fazer uma limpeza ou algo do tipo, depois que o aviso é mostrado, eles podem usar funções assíncronas await.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

Conclusão

Agora que você sabe como eu fiz isso, o que você faria‽ 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie uma demonstração, envie um tweet para mim e adicione os links acesse a seção "Remixes da comunidade" abaixo.

Remixes da comunidade