DEV Community

Cover image for Make Reusable and Animated Drop-down with Vue, Tailwind and Laravel
Mostafa Said
Mostafa Said

Posted on

Make Reusable and Animated Drop-down with Vue, Tailwind and Laravel

Hello everyone πŸ‘‹,

There is no doubt that reusable components is one of the main keys that makes developers life easier. Not only it makes the code elegant and sexy 🌢️, but also it can be used in multiple projects and with just few tweaks and Voilà! You have another version of your component effortlessly. In this article, I'm going to help you creating Vue JS 3 drop-down component with composition API styled Tailwind CSS and imported in Laravel 9 project. Our component is going to be reusable, dynamic and animated as well.

Take a peak on what we will be creating πŸ‘‡ :

I'm sure you can style it better than this, I just wanted to show you variety of options.

Another peak on the code πŸ‘‡ :



<Dropdown title="Users Dropdown">
  <Dropdowncontent>
    @foreach ($users as $user)
    <Dropdownitems item="{{ $user->name }}" 
                   href="/users/{{ $user->id }}">
    </Dropdownitems>
    @endforeach
  </Dropdowncontent>
</Dropdown>


Enter fullscreen mode Exit fullscreen mode

Before we start, please raise your hand if you have any questions:

πŸ™‹β€β™‚οΈ You keep mentioning 'reusable', what does it mean?

  • A reusable component is a component that can be used in various parts of an application. For example, once we build our drop-down today you can use it in your web app as many times as you want without having to duplicate your code.

1- Prerequisites:

  • I'm expecting that you have very basic knowledge of Vue composition API, Tailwind CSS and Laravel. If you don't, just come along for the ride and follow each step carefully and I guarantee you'll be able to do it by yourself by the time you finish this one.
  • I will build the reusable component from scratch continuing on using the same REPO that we created in the previous article.
  • If you would like to code along without my repo just make sure to install Laravel, Tailwind and Vue Js 3. Also create a database with users table or whatever and connect to it as we will be looping over the table. Instructions on how to do so is in PART 1.
  • Make sure you're running the command npm run watch all the time.

2- Allow Tailwind to read Vue files:

  • Head to tailwind.config.js and within the content array, be specific about your Vue files path.


module.exports = {
  content: [
    "./resources/**/*.blade.php",
    "./resources/**/*.js",
    "./resources/**/*.vue",
    "./resources/js/components/*.vue",
    "./resources/js/**/*.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};


Enter fullscreen mode Exit fullscreen mode
  • Insure npm run watch is running in the terminal.

3- Create a general Dropdown.vue component:

  • Head to resources/js/components and create new file called Dropdown.vue.
  • Within this file just build a normal drop-down component without looping over any data in database, just a toggle with showUsers function and some tailwind.

  • Dropdown.vue :



<template>
  <div v-click-outside="onClickOutside" @click="showUsers">
    <!-- toggler -->

    <button
      class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 
             text-sm font-semibold lg:inline-flex lg:w-48">
      Users
    </button>

    <!-- Content Container -->

    <div v-show="usersToggle" class="mt-2 w-full rounded-lg bg- 
                                     blue-100 py-2">
      <!-- Drop-down Items -->

      <a href="#" class="block text-sm my-1 mx-1"> 
           Random user name
      </a>
    </div>
  </div>
</template>


Enter fullscreen mode Exit fullscreen mode


<script>
import vClickOutside from "click-outside-vue3";
import { ref } from "vue";
export default {
    name: "Dropdown",
    directives: {
        clickOutside: vClickOutside.directive,
    },
    setup() {
        const usersToggle = ref(false);

        const showUsers = () => {
            usersToggle.value = !usersToggle.value;
        };

        const onClickOutside = (event) => {
            usersToggle.value = false;
        };

        return {
            usersToggle,
            showUsers,
            onClickOutside,
        };
    },
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Import your component in resources/js/app.js.


require("./bootstrap");

import { createApp } from "vue";
import Dropdown from "./components/Dropdown";
import vClickOutside from "click-outside-vue3";

createApp({
  components: {
    Dropdown,
  },
})
  .use(vClickOutside)
  .mount("#app");


Enter fullscreen mode Exit fullscreen mode
  • Just a reminder that the vClickOutside is an npm package we used in the previous tutorial to close the drop-down when we click anywhere on the screen.

  • With nothing else, if you run the command php artisan serve in your Laravel's main directory you will see a functional ugly drop-down 🍾 But don't worry, we will make it prettier later.

4- Create component parts:

Now we're going to slice our component as we need 3 parts. One for the toggler, one for the content container and one for the drop-down items. But first we must create and import them.

  • Head to resources/js/components/ and create the other two files Dropdowncontent.vue and Dropdownitems.vue.
  • Just make them contain <template> and <script> tags and leave them empty for now.
  • Import them in resources/js/app.js like we did with the first one.

  • resources/js/app.js :



import { createApp } from "vue";
import Dropdown from "./components/Dropdown";
import Dropdowncontent from "./components/Dropdowncontent";
import Dropdownitems from "./components/Dropdownitems";
import vClickOutside from "click-outside-vue3";

createApp({
  components: {
    Dropdown,
    Dropdowncontent,
    Dropdownitems,
  },
})
  .use(vClickOutside)
  .mount("#app");


Enter fullscreen mode Exit fullscreen mode

5- Slice Dropdown.vue:

  • Head to resources/js/components/Dropdown.vue and copy the div the contains the link and leave in it's place <slot /> tag and wrap the botton with <slot name="toggler"></slot> tags.
  • Head to resources/js/components/Dropdowncontent.vue and paste the div in the template. Then copy the link inside the div from Dropdowncontent.vue to Dropdownitems.vue and leave in it's place <slot /> tag as well.
  • Your components should look like this:

  • Dropdown.vue :



<template>
  <div v-click-outside="onClickOutside" @click="showUsers">
    <!-- toggler -->

    <slot name="toggler">

      <button
        class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 
               text-sm font-semibold lg:inline-flex lg:w-48">
        Users
      </button>

    </slot>

    <!-- Content Container -->

    <slot />

  </div>
</template>


Enter fullscreen mode Exit fullscreen mode
  • Dropdowncontent.vue :


<template>
  <div v-show="usersToggle" class="mt-1 w-full rounded-xl bg-blue- 
                                   100 py-2">

    <slot />

  </div>
</template>


Enter fullscreen mode Exit fullscreen mode


<script>
export default {
    name: "Dropdowncontent",
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Dropdownitems.vue :


<template>
  <a href="#" class="block text-sm my-1 mx-1"> A user name </a>
</template>


Enter fullscreen mode Exit fullscreen mode


<script>
export default {
    name: "Dropdownitems",
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Head to welcome.blade.php and let's break it down in the body.

  • welcome.blade.php :



<!DOCTYPE html>
<html lang="en">
  <head>
    <title>dropdown</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet" />
    <script src="{{ asset('js/app.js') }}" defer></script>
  </head>

  <body>
    <main class="max-w-6xl mx-auto mt-6 lg:mt-20 space-y-6" 
          id="app">
      <div class="max-w-xl mx-auto mt-10 text-center">
        <div class="space-y-2 lg:space-y-0 lg:space-x-4 mt-8">
          <div
            class="relative lg:inline-flex items-center bg-blue- 
                   100 rounded-xl">

            <Dropdown>

              <Dropdowncontent>

                <Dropdownitems></Dropdownitems>

              </Dropdowncontent>

            </Dropdown>

          </div>
        </div>
      </div>
    </main>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode
  • Now if you check it on browser, you will see the same thing but the toggler won't work. You might be wondering about how the heck are we going to pass usersToggle value from Dropdown.vue to Dropdowncontent.vue and really it's pretty simply, we're going to use Vue's provide/inject.

6- Provide/Inject:

  • Head to Dropdown.vue and import provide from vue, use it in the setup.

  • Dropdown.vue :



<script>
import vClickOutside from "click-outside-vue3";
import { provide, ref } from "vue";
export default {
    name: "Dropdown",
    directives: {
        clickOutside: vClickOutside.directive,
    },
    setup() {
        const usersToggle = ref(false);

        const showUsers = () => {
            usersToggle.value = !usersToggle.value;
        };

        const onClickOutside = (event) => {
            usersToggle.value = false;
        };

        provide("usersToggle", usersToggle);

        return {
            usersToggle,
            showUsers,
            onClickOutside,
        };
    },
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Head to Dropdowncontent.vue and import inject, use it and return the value.

  • Dropdowncontent.vue :



<script>
import { inject } from "vue";
export default {
    name: "Dropdowncontent",

    setup() {
        const usersToggle = inject("usersToggle");

        return {
            usersToggle,
        };
    },
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • And just like that, our drop-down is working again.

7- Pass the data from Blade to Vue:

  • To make this component more reusable and dynamic, let's head to Dropdown.vue and instead of hard coding the word 'Users' leave instead {{ title }} and accept title as a prop in the script.

  • Dropdown.vue :



<template>
  <div v-click-outside="onClickOutside" @click="showUsers">
    <!-- toggler -->

    <button
      class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 
             text-sm font-semibold lg:inline-flex lg:w-48">

      {{ title }}

    </button>

    <!-- Content Container -->

    <slot />

  </div>
</template>


Enter fullscreen mode Exit fullscreen mode


<script>
import vClickOutside from "click-outside-vue3";
import { provide, ref } from "vue";
export default {
    name: "Dropdown",
    props: ["title"],
    directives: {
        clickOutside: vClickOutside.directive,
    },
    setup() {
        const usersToggle = ref(false);

        const showUsers = () => {
            usersToggle.value = !usersToggle.value;
        };

        const onClickOutside = (event) => {
            usersToggle.value = false;
        };

        provide("usersToggle", usersToggle);

        return {
            usersToggle,
            showUsers,
            onClickOutside,
        };
    },
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Head to Dropdownitems.vue and do the same. Remove 'A user name' and leave instead {{ itemName }} and accept it as a prop.

  • Dropdownitems.vue :



<template>
  <a href="#" class="block text-sm my-1 mx-1"> {{ item }} </a>
</template>


Enter fullscreen mode Exit fullscreen mode


<script>
export default {
    name: "Dropdownitems",
    props: ["item"],
};
</script>


Enter fullscreen mode Exit fullscreen mode
  • Head to welcome.blade.php and pass on the values.

  • welcome.blade.php :



<Dropdown title="Users Dropdown">

  <Dropdowncontent>

    <Dropdownitems item="Testing Items" href="#" />

  </Dropdowncontent>

</Dropdown>


Enter fullscreen mode Exit fullscreen mode
  • Use @foreach loop to loop over all users in database and echo out their names.


<Dropdown title="Users Dropdown">

  <Dropdowncontent>

    @foreach ($users as $user)
    <Dropdownitems item="{{ $user->name }}" 
                   href="/users/{{ $user->id }}">
    </Dropdownitems>
    @endforeach

  </Dropdowncontent>

</Dropdown>


Enter fullscreen mode Exit fullscreen mode

And there you go πŸš€ This is a complete dynamic and reusable drop-down component. You can even add to classes by passing them from blade just like we did with title, item name and href.

Dynamic dropdown component

8- Adding animation and styles:

  • You can get SVG website and add it to the button in Dropdown.vue.
  • I added a class conditionally for it to rotate when the usersToggle is true.
  • Also i made few changes to Tailwind CSS styles.

  • Dropdown.vue :



<template>
  <div class="relative" v-click-outside="onClickOutside" @click="showUsers">
    <slot name="toggler">
      <button
        class="text-white bg-gradient-to-r from-purple-600 to- 
               blue-500 rounded-xl flex max-h-52 w-full overflow- 
               auto py-2 pl-3 pr-9 text-sm font-semibold 
               lg:inline-flex lg:w-52">
        {{ title }}
        <svg
          class="absolute"
          :class="usersToggle
? '-rotate-90 transform transition duration-500 ease-in-out'
: 'rotate-90 transform transition duration-500 ease-in-out'"
          style="right: 12px"
          width="22"
          height="22"
          viewBox="0 0 22 22">
          <g fill="none" fill-rule="evenodd">
            <path
              stroke="#000"
              stroke-opacity=".012"
              stroke-width=".5"
              d="M21 1v20.16H.84V1z"
            ></path>
            <path
              fill="#222"
              d="M13.854 7.224l-3.847 3.856 3.847 3.856-1.184 1.184-5.04-5.04 5.04-5.04z"
            ></path>
          </g>
        </svg>
      </button>
    </slot>

    <slot />
  </div>
</template>


Enter fullscreen mode Exit fullscreen mode
  • I used Vue's <transition> tags combined with tailwind CSS classes to make a smooth effect for the drop-down.

  • Dropdwoncontent.vue :



<template>
  <transition
    :duration="1000"
    enter-active-class="transform transition duration-300 ease- 
    custom"
    enter-class="-translate-y-1/2 scale-y-0 opacity-0"
    enter-to-class="translate-y-0 scale-y-100 opacity-100"
    leave-active-class="transform transition duration-300 ease- 
    custom"
    leave-class="translate-y-0 scale-y-100 opacity-100"
    leave-to-class="-translate-y-1/2 scale-y-0 opacity-0">
    <div
      v-show="usersToggle"
      class="absolute left-0 right-0 z-50 mt-2 w-full rounded-lg 
             bg-gradient-to-r from-purple-600 to-blue-500 py-1 px- 
             3 text-left text-sm font-semibold text-white 
             transition duration-300 py-2">
      <slot />
    </div>
  </transition>
</template>


Enter fullscreen mode Exit fullscreen mode
  • Dropdownitems.vue :


<template>
  <a
    href=""
    class="px-1 py-1 my-1 rounded-lg block hover:bg-gradient-to-bl 
           focus:bg-gradient-to-bl focus:ring-4 focus:ring-blue- 
           300 dark:focus:ring-blue-800">

    <slot>{{ item }}</slot>

  </a>
</template>


Enter fullscreen mode Exit fullscreen mode

9- Extracting UsersDropdown blade component:

  • This is not necessary as the code still looks great but we can extract a blade component for it and.
  • Create new folder named components in resources/views and create inside it a new file named users-dropdown.blade.php.
  • Cut your drop-down code and paste it inside, leave in it's place <x-users-dropdown />.
  • That will leave us with only this code in our welcome.blade.php:



          <!-- Only this -->

          <x-users-dropdown />



Enter fullscreen mode Exit fullscreen mode

Congratulations πŸ₯³ Now you have a Vue animated, dynamic, reusable and colorful drop-down component inside a blade component with a very elegant code and cool Tailwind styles.

That's it for this tutorial and I hope you enjoyed reading it as I made huge effort due to the article being deleted and I had to rewrite it again. If you liked it please leave a reaction and if you have any questions leave it below or send me a DM on TWITTER directly.

Have a great day and happy coding πŸ‘‹

Top comments (2)

Collapse
 
mreduar profile image
Eduar Bastidas

Nice!

Collapse
 
moose_said profile image
Mostafa Said

Thanks πŸ™