Vue.js Modal Portal Demo
by Torwan
HTML
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/tailwind.min.css">
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/portal-vue.js"></script>
<div id="app" class="font-sans min-h-screen flex items-center justify-center">
<!-- Imagine this is surrounded... -->
<new-tweet-button></new-tweet-button>
<!-- ...by tons of other markup for your site. -->
<!-- At the very end of your markup to avoid absolute/relative positioning bugs... -->
<portal-target name="modals"></portal-target>
</div>
CSS
.fade-shrink-in-enter-active,
.fade-shrink-in-leave-active {
transition: all .3s ease;
}
.fade-shrink-in-enter,
.fade-shrink-in-leave-to {
opacity: 0;
transform: scale(1.1);
}
Babel + JSX
Vue.use(PortalVue)
Vue.component('new-tweet-button', {
template: `
<button class="bg-blue text-white font-semibold px-4 py-2 rounded hover:bg-blue-dark inline-flex items-center"
@click="openModal"
>
<span>New Tweet</span>
<portal to="modals">
<new-tweet-modal :open="open" @close="close"></new-tweet-modal>
</portal>
</button>
`,
data() {
return {
open: false,
}
},
methods: {
openModal() {
this.open = true
},
close() {
this.open = false
}
}
})
Vue.component('new-tweet-modal', {
template: `
<transition name="fade-shrink-in" appear>
<div v-show="open" @click="close" class="absolute pin p-8" style="background-color: hsla(0, 0%, 0%, .5)">
<div @click.stop class="max-w-sm w-full mx-auto bg-white rounded p-6 mt-8 z-10 shadow-lg">
<h1 class="font-normal text-xl text-center mb-6">
Compose New Tweet
</h1>
<div class="mb-6">
<textarea rows="3" class="text-lg w-full block appearance-none border px-4 py-2 rounded" v-model="tweet" placeholder="What's happening?"></textarea>
</div>
<div class="text-right">
<button type="button" class="text-grey-darker hover:text-black hover:underline mr-6" @click="close">
Cancel
</button>
<button type="button" class="bg-blue text-white font-semibold px-4 py-2 rounded hover:bg-blue-dark">
Post
</button>
</div>
</div>
</div>
</div>
</transition>
`,
props: ['open'],
data() {
return {
tweet: '',
}
},
methods: {
close() {
this.tweet = ''
this.$emit('close')
},
},
created() {
document.addEventListener('keydown', (e) => {
if (this.open && e.keyCode == 27) {
this.close()
}
})
}
})
const app = new Vue({
el: '#app',
})