Reusable dropdown components made with VueJS and TailwindCSS
<div id="app">
<v-dropdown placement="right">
<!-- Button content -->
<template v-slot:button>
<img class="w-10 h-10 rounded-full border-2 border-gray-600 mr-3" src="https://raw.githubusercontent.com/vuetailwind/storage/master/avatars/saleh-avatar.jpg" alt="Saleh Mir" />
<span class="mr-2">Saleh</span>
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M4.516 7.548c.436-.446 1.043-.481 1.576 0L10 11.295l3.908-3.747c.533-.481 1.141-.446 1.574 0 .436.445.408 1.197 0 1.615-.406.418-4.695 4.502-4.695 4.502a1.095 1.095 0 0 1-1.576 0S4.924 9.581 4.516 9.163c-.409-.418-.436-1.17 0-1.615z" />
</svg>
</template>
<!-- Opened dropdown content -->
<template v-slot:content>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Profile</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Settings</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Help</a>
<hr />
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-red-600 hover:text-white" href="#">Logout</a>
</template>
</v-dropdown>
</div>
<script>
Vue.component("v-dropdown", require("components/v-dropdown.vue"));
new Vue({
el: "#app",
});
</script>
<template>
<div class="relative">
<button class="z-10 relative flex items-center focus:outline-none select-none" @click="open = !open">
<slot name="button"></slot>
</button>
<!-- to close when clicked on space around it-->
<button class="fixed inset-0 h-full w-full cursor-default focus:outline-none" v-if="open" @click="open = false" tabindex="-1"></button>
<!--dropdown menu-->
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
<div class="absolute shadow-lg border w-48 rounded py-1 px-2 text-sm mt-4 bg-white z-10" :class="placement === 'right' ? 'right-0' : 'left-0'" v-if="open">
<slot name="content"></slot>
</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
};
},
props: {
placement: {
type: String,
default: "right",
validator: (value) => ["right", "left"].indexOf(value) !== -1,
},
},
mounted() {
const onEscape = (e) => {
if (e.key === "Esc" || e.key === "Escape") {
this.open = false;
}
};
document.addEventListener("keydown", onEscape);
this.$once("hook:beforeDestroy", () => {
document.removeEventListener("keydown", onEscape);
});
},
};
</script>
<div id="app">
<v-dropdown placement="left">
<!-- Button content -->
<template v-slot:button>
<span class="px-2 py-2 border rounded inline-flex items-center text-sm">
<span class="mr-2">Mobile Friendly Dropdown</span>
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M4.516 7.548c.436-.446 1.043-.481 1.576 0L10 11.295l3.908-3.747c.533-.481 1.141-.446 1.574 0 .436.445.408 1.197 0 1.615-.406.418-4.695 4.502-4.695 4.502a1.095 1.095 0 0 1-1.576 0S4.924 9.581 4.516 9.163c-.409-.418-.436-1.17 0-1.615z" />
</svg>
</span>
</template>
<!-- Opened dropdown content -->
<template v-slot:content>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Profile</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Settings</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Help</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Orders</a>
<a class="flex w-full justify-between items-center rounded px-2 py-1 my-1 hover:bg-indigo-600 hover:text-white" href="#">Support</a>
</template>
</v-dropdown>
</div>
<script>
Vue.component("v-dropdown", require("components/v-dropdown.vue"));
new Vue({
el: "#app",
});
</script>
<template>
<div class="relative">
<button class="z-10 relative flex items-center focus:outline-none select-none" @click="open = !open">
<slot name="button"></slot>
</button>
<!-- to close when clicked on space around it in desktop-->
<button class="fixed inset-0 h-full w-full cursor-default focus:outline-none" v-if="open" @click="open = false" tabindex="-1"></button>
<!--dropdown content: desktop-->
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
<div class="hidden md:block absolute shadow-lg border w-48 rounded py-1 px-2 text-sm mt-4 bg-white" :class="placement === 'right' ? 'right-0' : 'left-0'" v-if="open">
<slot name="content"></slot>
</div>
</transition>
<!--dropdown content: mobile-->
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
<div class="md:hidden fixed inset-x-0 bottom-0 bg-white w-full z-20 px-2 py-2 shadow-2xl leading-loose" v-if="open">
<slot name="content"></slot>
</div>
</transition>
<!-- to close when clicked on space around it in mobile-->
<div class="md:hidden fixed w-full h-full inset-0 bg-gray-900 opacity-50 z-10" @click="open = false" v-if="open"></div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
};
},
props: {
placement: {
type: String,
default: "right",
validator: (value) => ["right", "left"].indexOf(value) !== -1,
},
},
mounted() {
const onEscape = (e) => {
if (e.key === "Esc" || e.key === "Escape") {
this.open = false;
}
};
document.addEventListener("keydown", onEscape);
this.$once("hook:beforeDestroy", () => {
document.removeEventListener("keydown", onEscape);
});
},
};
</script>