Pesquisa de site

Como construir um componente modal com Vue.js


Introdução

Os modais são uma convenção de experiência do usuário para direcionar a atenção de um usuário para um conteúdo que ele precisa ler ou interagir. Estes tendem a assumir a forma de pequenos blocos de conteúdo diretamente no campo de visão do usuário com algum tipo de pano de fundo obscurecendo ou ocultando o restante do conteúdo da página. Se o usuário deseja retornar à página, ele deve se envolver com o modal ou possivelmente descartá-lo.

Neste artigo você aprenderá como criar um componente modal reutilizável usando transições e slots no Vue.js.

Pré-requisitos

Para concluir este tutorial, você precisará de:

  • Node.js instalado localmente, o que você pode fazer seguindo Como instalar o Node.js e criar um ambiente de desenvolvimento local.

Passo 1 — Configurando o Projeto

Você pode usar @vue/cli para criar um novo projeto Vue.js.

Na janela do terminal, use o seguinte comando:

  1. npx @vue/cli create --default vue-modal-component-example

Isso usará as configurações padrão para criar um projeto Vue.js.

Navegue até o diretório do projeto recém-criado:

  1. cd vue-modal-component-example

Inicie o projeto para verificar se não há erros.

  1. npm run serve

Se você visitar o aplicativo local (geralmente em localhost:8080) em seu navegador da Web, verá uma mensagem Bem-vindo ao seu aplicativo Vue.js.

Com este andaime definido, você pode começar a trabalhar no componente modal.

Etapa 2 — Criando o componente modal

Primeiro, dentro do diretório do projeto, crie um novo arquivo Modal.vue em src/components.

Vamos começar definindo o modelo. Você precisará de um div para a sombra do pano de fundo, um div para atuar como caixa modal e alguns elementos para definir sua estrutura:

<template>
  <div class="modal-backdrop">
    <div class="modal">
      <slot name="header">
      </slot>

      <slot name="body">
      </slot>

      <slot name="footer">
      </slot>
    </div>
  </div>
</template>

Você pode usar adereços para fornecer o cabeçalho, o corpo e o rodapé, mas o uso de slots permitirá mais flexibilidade.

O uso de slots permite que você reutilize o mesmo modal com diferentes tipos de conteúdo do corpo.

Você pode usar um modal para mostrar uma mensagem, mas pode querer reutilizar o mesmo modal com um formulário para enviar uma solicitação.

Embora as props geralmente sejam suficientes para construir um componente, fornecer HTML por meio de uma prop exigiria que usássemos v-html para renderizá-lo - o que pode levar a ataques XSS.

Aqui, você está usando slots nomeados para permitir o uso de mais de um slot no mesmo componente.

Ao definir um slot nomeado, tudo o que você identificar com esse nome será renderizado no lugar do slot original. Pense nisso como um espaço reservado. Como um espaço reservado, um slot também pode ter um conteúdo padrão que será renderizado quando nenhum for fornecido.

Como o conteúdo fornecido substitui a tag <slot>, para garantir que as seções tenham as classes desejadas, você precisará encapsular cada slot.

Vamos definir alguns padrões para os slots, os elementos wrapper e o CSS inicial para que pareça um modal básico.

Acima do modelo, adicione o nome do componente e o método para fechar o modal:

<script>
  export default {
    name: 'Modal',
    methods: {
      close() {
        this.$emit('close');
      },
    },
  };
</script>

Em seguida, modifique o modelo para agrupar os slots, forneça valores padrão e também exiba os botões para fechar o modal:

<template>
  <div class="modal-backdrop">
    <div class="modal">
      <header class="modal-header">
        <slot name="header">
          This is the default title!
        </slot>
        <button
          type="button"
          class="btn-close"
          @click="close"
        >
          x
        </button>
      </header>

      <section class="modal-body">
        <slot name="body">
          This is the default body!
        </slot>
       </section>

      <footer class="modal-footer">
        <slot name="footer">
          This is the default footer!
        </slot>
        <button
          type="button"
          class="btn-green"
          @click="close"
        >
          Close Modal
        </button>
      </footer>
    </div>
  </div>
</template>

Em seguida, abaixo do modelo, adicione estilos para o componente:

<style>
  .modal-backdrop {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .modal {
    background: #FFFFFF;
    box-shadow: 2px 2px 20px 1px;
    overflow-x: auto;
    display: flex;
    flex-direction: column;
  }

  .modal-header,
  .modal-footer {
    padding: 15px;
    display: flex;
  }

  .modal-header {
    position: relative;
    border-bottom: 1px solid #eeeeee;
    color: #4AAE9B;
    justify-content: space-between;
  }

  .modal-footer {
    border-top: 1px solid #eeeeee;
    flex-direction: column;
    justify-content: flex-end;
  }

  .modal-body {
    position: relative;
    padding: 20px 10px;
  }

  .btn-close {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    font-size: 20px;
    padding: 10px;
    cursor: pointer;
    font-weight: bold;
    color: #4AAE9B;
    background: transparent;
  }

  .btn-green {
    color: white;
    background: #4AAE9B;
    border: 1px solid #4AAE9B;
    border-radius: 2px;
  }
</style>

Todas essas peças juntas completam seu componente modal. Você pode importar este novo componente em App.vue e observá-lo em ação.

Modifique App.vue para alterar o modelo e adicione showModal, closeModal e isModalVisible:

<template>
  <div id="app">
    <button
      type="button"
      class="btn"
      @click="showModal"
    >
      Open Modal!
    </button>

    <Modal
      v-show="isModalVisible"
      @close="closeModal"
    />
  </div>
</template>

<script>
  import modal from './components/Modal.vue';

  export default {
    name: 'App',
    components: {
      Modal,
    },
    data() {
      return {
        isModalVisible: false,
      };
    },
    methods: {
      showModal() {
        this.isModalVisible = true;
      },
      closeModal() {
        this.isModalVisible = false;
      }
    }
  };
</script>

Este código importará o componente Modal e exibirá um botão Open Modal para interagir.

Observação: em seu arquivo App.js, você pode, opcionalmente, referenciar os slots e substituir o conteúdo padrão:

<Modal
  v-show="isModalVisible"
  @close="closeModal"
>
  <template v-slot:header>
    This is a new modal header.
  </template>

  <template v-slot:body>
    This is a new modal body.
  </template>

  <template v-slot:footer>
    This is a new modal footer.
  </template>
</Modal>

Visualize o aplicativo em seu navegador da Web e verifique se o modal se comporta conforme o esperado, abrindo-o e fechando-o.

Passo 3 — Adicionando Transições

Você pode fazer com que pareça abrir e fechar de forma mais suave usando uma transição.

O Vue fornece um componente wrapper chamado transition que permite adicionar transições para entrar e sair. Esse componente wrapper pode ser usado para qualquer elemento ou componente e permite ganchos CSS e JavaScript.

Toda vez que um componente ou elemento envolvido por uma transition é inserido ou removido, o Vue verificará se o elemento fornecido possui transições CSS e as adicionará ou removerá no momento certo. O mesmo vale para ganchos de JavaScript, mas, neste caso, você usará apenas CSS.

Quando um elemento é adicionado ou removido, seis classes são aplicadas para as transições de entrada e saída. Cada um deles será prefixado com o nome da transição.

Primeiro, vamos começar adicionando um componente wrapper de transição ao modal:

<template>
  <transition name="modal-fade">
    <div class="modal-backdrop">
      <div class="modal">
        ...
      </div>
    </div>
  </transition>
</template>

Agora vamos adicionar uma transição para que a opacidade desapareça lentamente usando as classes aplicadas:

<style>
  ...

  .modal-fade-enter,
  .modal-fade-leave-to {
    opacity: 0;
  }

  .modal-fade-enter-active,
  .modal-fade-leave-active {
    transition: opacity .5s ease;
  }
</style>

Inicialmente, o modal aparecerá oculto com um valor de propriedade opacity de 0.

Quando o modal abrir, ele terá a classe .modal-fade-enter-active aplicada e uma transição CSS para a propriedade opacity será aplicada sobre .5 segundos com a função de temporização da animação ease.

Ao sair do modal, fará o inverso. A classe modal-fade-leave-active será aplicada e o modal desaparecerá.

Visualize o aplicativo em seu navegador da Web e verifique se o modal aparece e desaparece gradualmente.

Passo 4 — Adicionando Acessibilidade

Você desejará tornar seu componente modal acessível com atributos ARIA.

Adicionar role=dialog ajudará o software de assistência a identificar o componente como sendo um diálogo de aplicativo separado do restante da interface do usuário.

Você também precisará rotulá-lo corretamente com os atributos aria-labelledby e aria-descriptby.

E adicione os atributos aria-label aos botões de fechamento.

<template>
  <transition name="modal-fade">
    <div class="modal-backdrop">
      <div class="modal"
        role="dialog"
        aria-labelledby="modalTitle"
        aria-describedby="modalDescription"
      >
        <header
          class="modal-header"
          id="modalTitle"
        >
          <slot name="header">
            This is the default title!
          </slot>
          <button
            type="button"
            class="btn-close"
            @click="close"
            aria-label="Close modal"
          >
            x
          </button>
        </header>

        <section
          class="modal-body"
          id="modalDescription"
        >
          <slot name="body">
            This is the default body!
          </slot>
        </section>

        <footer class="modal-footer">
          <slot name="footer">
            This is the default footer!
          </slot>
          <button
            type="button"
            class="btn-green"
            @click="close"
            aria-label="Close modal"
          >
            Close Modal
          </button>
        </footer>
      </div>
    </div>
  </transition>
</template>

A versão final do componente modal agora deve se parecer com:

<script>
  export default {
    name: 'Modal',
    methods: {
      close() {
        this.$emit('close');
      },
    },
  };
</script>

<template>
  <transition name="modal-fade">
    <div class="modal-backdrop">
      <div class="modal"
        role="dialog"
        aria-labelledby="modalTitle"
        aria-describedby="modalDescription"
      >
        <header
          class="modal-header"
          id="modalTitle"
        >
          <slot name="header">
            This is the default tile!
          </slot>
          <button
            type="button"
            class="btn-close"
            @click="close"
            aria-label="Close modal"
          >
            x
          </button>
        </header>

        <section
          class="modal-body"
          id="modalDescription"
        >
          <slot name="body">
            This is the default body!
          </slot>
        </section>

        <footer class="modal-footer">
          <slot name="footer">
            This is the default footer!
          </slot>
          <button
            type="button"
            class="btn-green"
            @click="close"
            aria-label="Close modal"
          >
            Close me!
          </button>
        </footer>
      </div>
    </div>
  </transition>
</template>

<style>
  .modal-backdrop {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .modal {
    background: #FFFFFF;
    box-shadow: 2px 2px 20px 1px;
    overflow-x: auto;
    display: flex;
    flex-direction: column;
  }

  .modal-header,
  .modal-footer {
    padding: 15px;
    display: flex;
  }

  .modal-header {
    position: relative;
    border-bottom: 1px solid #eeeeee;
    color: #4AAE9B;
    justify-content: space-between;
  }

  .modal-footer {
    border-top: 1px solid #eeeeee;
    flex-direction: column;
  }

  .modal-body {
    position: relative;
    padding: 20px 10px;
  }

  .btn-close {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    font-size: 20px;
    padding: 10px;
    cursor: pointer;
    font-weight: bold;
    color: #4AAE9B;
    background: transparent;
  }

  .btn-green {
    color: white;
    background: #4AAE9B;
    border: 1px solid #4AAE9B;
    border-radius: 2px;
  }

  .modal-fade-enter,
  .modal-fade-leave-to {
    opacity: 0;
  }

  .modal-fade-enter-active,
  .modal-fade-leave-active {
    transition: opacity .5s ease;
  }
</style>

Esta é a base para um componente modal com acessibilidade e transições.

Conclusão

Neste artigo, você construiu um componente modal com Vue.js.

Você experimentou slots para permitir que seu componente seja reutilizável, transitions para criar uma melhor experiência do usuário e atributos ARIA para tornar seu componente mais acessível .

Se você quiser aprender mais sobre Vue.js, confira nossa página de tópicos Vue.js para ver exercícios e projetos de programação.

Artigos relacionados: