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:
- 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:
- cd vue-modal-component-example
Inicie o projeto para verificar se não há erros.
- 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.