Skip to content

Instantly share code, notes, and snippets.

@mustafadalga
Created March 17, 2024 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mustafadalga/62e3618f535882ff7a12f02c5c44c347 to your computer and use it in GitHub Desktop.
Save mustafadalga/62e3618f535882ff7a12f02c5c44c347 to your computer and use it in GitHub Desktop.
Modal Slide In / Up Animation with Transition in vue.js 3
<template>
<div class="app">
<button @click="store.mutations.open()">Open Modal </button>
<Modal v-if="store.state.isModalOpen"/>
</div>
</template>
<script setup>
import store from "./store";
import Modal from "./Modal.vue";
loadDynamicCDNScript();
function loadDynamicCDNScript() {
const id = "tailwindCSS";
const existingScript = document.getElementById(id);
if (existingScript) return;
const script = document.createElement("script");
script.src = "https://cdn.tailwindcss.com";
script.id = id;
script.onload = configureTailwindCSS;
document.getElementsByTagName("head")[0].appendChild(script);
};
function configureTailwindCSS() {
tailwind.config = {
theme: {
extend: {
animation: {
"modal-slide-down": "modal-slide-down .5s ease-in-out",
"modal-slide-up": "modal-slide-up .5s ease-in-out",
},
keyframes: {
"modal-slide-down": {
'from': {
transform: "translateY(-100%)"
},
'to': {
transform: "translateY(0)"
},
},
"modal-slide-up": {
'from': {
transform: "translateY(0)"
},
'to': {
transform: "translateY(-100%)"
},
},
}
}
}
}
}
</script>
<script setup>
const props = defineProps({
fillClass: {
type: String,
required: true
}
});
</script>
<template>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M0.470316 0.469909C0.610942 0.329459 0.801566 0.250569 1.00032 0.250569C1.19907 0.250569 1.38969 0.329459 1.53032 0.469909L7.00032 5.93991L12.4703 0.469909C12.539 0.396222 12.6218 0.33712 12.7138 0.296128C12.8058 0.255136 12.9051 0.233095 13.0058 0.231318C13.1065 0.229541 13.2065 0.248066 13.2999 0.285787C13.3933 0.323508 13.4781 0.379652 13.5494 0.450871C13.6206 0.52209 13.6767 0.606923 13.7144 0.700312C13.7522 0.7937 13.7707 0.893729 13.7689 0.994432C13.7671 1.09513 13.7451 1.19445 13.7041 1.28645C13.6631 1.37845 13.604 1.46125 13.5303 1.52991L8.06032 6.99991L13.5303 12.4699C13.604 12.5386 13.6631 12.6214 13.7041 12.7134C13.7451 12.8054 13.7671 12.9047 13.7689 13.0054C13.7707 13.1061 13.7522 13.2061 13.7144 13.2995C13.6767 13.3929 13.6206 13.4777 13.5494 13.5489C13.4781 13.6202 13.3933 13.6763 13.2999 13.714C13.2065 13.7518 13.1065 13.7703 13.0058 13.7685C12.9051 13.7667 12.8058 13.7447 12.7138 13.7037C12.6218 13.6627 12.539 13.6036 12.4703 13.5299L7.00032 8.05991L1.53032 13.5299C1.38814 13.6624 1.2001 13.7345 1.00579 13.7311C0.811493 13.7277 0.626108 13.6489 0.488695 13.5115C0.351282 13.3741 0.27257 13.1887 0.269142 12.9944C0.265713 12.8001 0.337836 12.6121 0.470316 12.4699L5.94032 6.99991L0.470316 1.52991C0.329866 1.38928 0.250977 1.19866 0.250977 0.999909C0.250977 0.801159 0.329866 0.610534 0.470316 0.469909Z"
:class="fillClass" />
</svg>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import store from "./store";
import IconCross from "./IconCross.vue";
const showModalContent = ref<boolean>(false);
function toggleModalContent() {
showModalContent.value = !showModalContent.value;
}
function handleSubmit() {
toggleModalContent();
}
onMounted(() => toggleModalContent())
</script>
<template>
<section class="fixed inset-0 bg-black/50 z-50">
<div class="h-screen sticky top-0 grid place-items-center p-8">
{{ showModalContent }}
<Transition enter-active-class="animate-modal-slide-down"
leave-active-class="animate-modal-slide-up"
@after-leave="store.mutations.close()">
<div
v-if="showModalContent"
class="modal-content relative w-full max-w-3xl rounded-3xl px-16 py-12 bg-white overflow-hidden shadow-[0_2px_3px_rgba(0,0,0,0.06)]">
<div class="absolute right-6 top-6">
<IconCross
@click="toggleModalContent()"
fill-class="fill-blue-500"
class="w-2 h-2 lg:w-3 lg:h-3 cursor-pointer"/>
</div>
<h1
class="font-bold text-center text-2xl text-gray-900 lg:text-3xl xl:text-4xl 2xl:text-[42px]">
Login
</h1>
<div class="max-w-[400px] mx-auto mt-8">
<div class="mb-6">
<label for="username" class="block font-normal text-gray-900 text-xs lg:text-sm mb-2">
Username</label>
<input type="text"
id="username"
placeholder="Your username"
required
class="w-full h-10 lg:h-11 xl:h-12 2xl:h-14 font-normal text-gray-900 placeholder-text-500 text-xs md:text-sm 2xl:text-base border border-solid border-gray-300 focus:border-gray-500 outline-none rounded-lg p-2 lg:px-3 2xl:px-4">
</div>
<div class="mb-6">
<label for="password" class="block font-normal text-gray-900 text-xs lg:text-sm mb-2">
Password</label>
<input type="text"
id="password"
placeholder="Your password"
required
class="w-full h-10 lg:h-11 xl:h-12 2xl:h-14 font-normal text-gray-900 placeholder-text-500 text-xs md:text-sm 2xl:text-base border border-solid border-gray-300 focus:border-gray-500 outline-none rounded-lg p-2 lg:px-3 2xl:px-4">
</div>
<div class="mb-6 flex items-center gap-3">
<button type="button"
@click="toggleModalContent()"
class="cursor-pointer w-full max-w-[10rem] bg-white border border-solid border-gray-200 hover:border-gray-500 text-blue-500 text-center rounded-lg font-bold text-xs lg:text-sm xl:text-base h-9 xl:h11 2xl:h-12">
Cancel
</button>
<button type="button"
@click="handleSubmit"
class="cursor-pointer w-full bg-blue-500 text-white text-center rounded-lg font-bold text-xs lg:text-sm xl:text-base h-9 xl:h11 2xl:h-12">
Login
</button>
</div>
</div>
</div>
</Transition>
</div>
</section>
</template>
import { reactive } from "vue";
const store = ({
state: reactive({
isModalOpen: false
}),
getters: {},
mutations: {
open() {
store.state.isModalOpen = true;
},
close() {
store.state.isModalOpen = false;
}
}
})
export default store;
@mustafadalga
Copy link
Author

Demo : https://play.vuejs.org/#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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment