Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 4326

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 61k
Author
Asked: November 26, 20242024-11-26T10:56:09+00:00 2024-11-26T10:56:09+00:00

Building a VueJS dropdown menu component

  • 61k

Just for fun — let's build a reusable dropdown menu component with VueJS. You can check out the working demo here.

If you just want to use the component, you can find it on npm or github

 

Let's build the thing 🚀

We're assuming that you have a basic understanding of how the VueJS and VueJS single file components (SFC) work and that you already have a VueJS project running

1. Create a file called src/components/vue-dropdown-menu.vue and add following basic SFC structure:

<template>  </template>  <script>   export default {    } </script>  <style lang="scss" scoped>  </style> 
Enter fullscreen mode Exit fullscreen mode

As you can see — just a basic SFC structure here — nothing magical.

 

2. Add the following HTML markup to the <template> part of the SFC structure

<template>    <section class="dropDownMenuWrapper">      <button class="dropDownMenuButton">      </button>      <div class="iconWrapper">       <div class="bar1" />       <div class="bar2" />       <div class="bar3" />     </div>      <section class="dropdownMenu">       <div class="menuArrow" />       <slot/>     </section>    </section>  </template> 
Enter fullscreen mode Exit fullscreen mode

⬆️ What's happening here:

.dropDownMenuWrapper
An element that will wrap our component

.dropDownMenuButton
A button that will actually open & close our menu

.iconWrapper ( And the .bar elements )
Pure CSS icon that indicates if the menu is open or closed

.dropdownMenu
An element that will wrap the actual menu content —links and such.

.menuArrow
Just a for pointing purposes 😁

<slot/>
Content from the parent will be printed here

 

3. Add styles to the <style> part of the SFC structure

.dropDownMenuWrapper {   position: relative;   width: 500px;   height: 80px;   border-radius: 8px;   background: white;   border: 1px solid #eee;   box-shadow: 10px 10px 0 0 rgba(black,.03);   -webkit-tap-highlight-color: rgba(0, 0, 0, 0);    * {     box-sizing: border-box;     text-align: left;   }    .dropDownMenuButton {     border: none;     font-size: inherit;     background: none;     outline: none;     border-radius: 4px;     position: absolute;     top: 0;     left: 0;     display: flex;     align-items: center;     padding: 0 70px 0 20px;     margin: 0;     line-height: 1;     width: 100%;     height: 100%;     z-index: 2;     cursor: pointer;   }    .dropDownMenuButton--dark {     color: #eee;   }    .iconWrapper {     width: 25px;     height: 25px;     position: absolute;     right: 30px;     top: 50%;     transform: translate(0,-50%);     z-index: 1;      .bar1 {       width: 100%;       max-width: 28px;       height: 3px;       background: blue;       position: absolute;       top: 50%;       left: 50%;       border-radius: 9999px;       transform: translate(-50%, calc(-50% - 8px) );       transition: all 0.2s ease;     }      .bar1--dark {       background: #eee;     }      .bar1--open {       transform: translate(-50%, -50%) rotate(45deg);       margin-top: 0;       background: red;     }      .bar2 {       width: 100%;       max-width: 28px;       height: 3px;       background: blue;       position: absolute;       top: 50%;       left: 50%;       border-radius: 9999px;       opacity: 1;       transform: translate(-50%, -50%);       transition: all 0.2s ease;     }      .bar2--dark {       background: #eee;     }      .bar2--open {       opacity: 0;     }      .bar3 {       width: 100%;       max-width: 28px;       height: 3px;       background: blue;       position: absolute;       top: 50%;       left: 50%;       border-radius: 9999px;       transform: translate(-50%, calc(-50% + 8px) );       transition: all 0.2s ease;     }      .bar3--dark {       background: #eee;     }      .bar3--open {       top: 50%;       transform: translate(-50%, -50% ) rotate(-45deg);       background: red;     }    }    .iconWrapper--noTitle {     left: 0;     top: 0;     bottom: 0;     right: 0;     width: auto;     height: auto;     transform: none;   }    .dropdownMenu {     position: absolute;     top: 100%;     width: 100%;     min-width: 300px;     min-height: 10px;     border-radius: 8px;     border: 1px solid #eee;     box-shadow: 10px 10px 0 0 rgba(black,.03);     background: white;     padding: 10px 30px;     animation: menu 0.3s ease forwards;      .menuArrow {       width: 20px;       height: 20px;       position: absolute;       top: -10px;       left: 20px;       border-left: 1px solid #eee;       border-top: 1px solid #eee;       background: white;       transform: rotate(45deg);       border-radius: 4px 0 0 0;     }      .menuArrow--dark {       background: #333;       border: none;     }      .option {       width: 100%;       border-bottom: 1px solid #eee;       padding: 20px 0;       cursor: pointer;       position: relative;       z-index: 2;        &:last-child {         border-bottom: 0;       }        * {         color: inherit;         text-decoration: none;         background: none;         border: 0;         padding: 0;         outline: none;         cursor: pointer;       }      }      .desc {       opacity: 0.5;       display: block;       width: 100%;       font-size: 14px;       margin: 3px 0 0 0;       cursor: default;     }    }    .dropdownMenu--dark {     background: #333;     border: none;      .option {       border-bottom: 1px solid #888;     }      * {       color: #eee;     }    }    @keyframes menu {     from { transform: translate3d( 0, 30px ,0 ) }     to { transform: translate3d( 0, 20px ,0 ) }   }  }  .dropDownMenuWrapper--noTitle {   padding: 0;   width: 60px;   height: 60px; }  .dropDownMenuWrapper--dark {   background: #333;   border: none; } 
Enter fullscreen mode Exit fullscreen mode

Pretty basic styling — We are not going thru all these — as you can style your component anyway you like.

 

4. Add some function to our component.
Previously we added the .dropDownMenuButton -button to the template, and now we are going expand that element to actually do something. Modify the element as follows:

<button class="dropDownMenuButton" ref="menu" @click="openClose">{{menuTitle}}</button> 
Enter fullscreen mode Exit fullscreen mode

⬆️ What's happening here:

  1. We added the @click="openClose" which will fire the method openClose when we click the button.
  2. We added the ref="menu" that refers to the element — we need this later on.
  3. We added the template tag {{menuTitle}} that will show us our menu title.

 

— then, let's create the openClose method to control the opening and closing the menu. So modify the <script> part of the structure like this:

export default {   props: [ "menuTitle" ], // Menu title from the parent   data() {     return {       isOpen: false // Variable if the menu is open or closed   },   methods: {      openClose() {        // Toggle between open or closed ( true || false )       isOpen = !isOpen      }    } } 
Enter fullscreen mode Exit fullscreen mode

⬆️ What's happening here:

We added the openClose method to toggle isOpen variable between true and false — we also added the menuTitle prop so we can pass our menus title from the parent.

— to make things actually work, we need to add the isOpen variable to the template:

Modify the .bar1 & .bar2 & .bar3 elements as follows:

<div class="bar1" :class="{ 'bar1--open' : isOpen }" /> <div class="bar2" :class="{ 'bar2--open' : isOpen }" /> <div class="bar3" :class="{ 'bar3--open' : isOpen }" /> 
Enter fullscreen mode Exit fullscreen mode

Also modify the .dropdownMenu as follows:

<section class="dropdownMenu" v-if="isOpen" >       <div class="menuArrow" />       <slot/> </section> 
Enter fullscreen mode Exit fullscreen mode

⬆️ What's happening here:

We added the :class="{ 'bar1--open' : isOpen }" to the bar -elements — we toggle classes based on the value of isOpen so we can get that nice icon animation that you can see in the demo.

In the .dropdownMenu -element we added the v-if="isOpen" part — if isOpen is true show the menu and vice versa.

Congrats 🏆

You now have a working component! BUT… We can make it even better. For the UI/UX purposes — we should add a function that closes the menu if the user clicks anywhere else on the document. To add that, we have to expand the openClose method and add a new method called catchOutsideClick.

First let's expand the openClose method, modify the method to look like this:

openClose() { var _this = this    const closeListerner = (e) => {      if ( _this.catchOutsideClick(e, _this.$refs.menu ) )       window.removeEventListener('click', closeListerner) , _this.isOpen = false     }     window.addEventListener('click', closeListerner)     this.isOpen = !this.isOpen  }, 
Enter fullscreen mode Exit fullscreen mode

 
— then we need to add a new method called catchOutsideClick;

catchOutsideClick(event, dropdown)  {    // When user clicks menu — do nothing   if( dropdown == event.target )     return false    // When user clicks outside of the menu — close the menu   if( this.isOpen && dropdown != event.target )     return true  } 
Enter fullscreen mode Exit fullscreen mode

⬆️ What's happening here:

We added an eventListener to catch all click events — when we catch one, we pass the event and the clicked element to catchOutsideClick method which will then check if the click is on the menu or outside of it. If the menu is open and the click was outside the menu — we will remove the eventListener and close the menu.

Bonus 🎉

You might have noticed earlier — that we have a bunch of --dark classes in the styles. That's because we want our component to support a dark mode if the user prefers that.

So to make those styles work we are adding a bit more code to our component.

First, let's make our template to look like this:

<section class="dropDownMenuWrapper" :class="{ 'dropDownMenuWrapper--dark' : isDarkMode, 'dropDownMenuWrapper--noTitle' : !menuTitle }">    <button class="dropDownMenuButton" ref="menu" @click="openClose" :class="{ 'dropDownMenuButton--dark' : isDarkMode }">       {{ menuTitle }}   </button>    <div class="iconWrapper" :class="{ 'iconWrapper--noTitle' : !menuTitle }">     <div class="bar1" :class="{ 'bar1--open' : isOpen , 'bar1--dark' : isDarkMode }" />     <div class="bar2" :class="{ 'bar2--open' : isOpen , 'bar2--dark' : isDarkMode }" />     <div class="bar3" :class="{ 'bar3--open' : isOpen , 'bar3--dark' : isDarkMode }" />   </div>    <section class="dropdownMenu" v-if="isOpen" :class="{ 'dropdownMenu--dark' : isDarkMode }">     <div class="menuArrow" :class="{ 'menuArrow--dark' : isDarkMode }" />     <slot/>   </section>  </section> 
Enter fullscreen mode Exit fullscreen mode

 
Second, add new variable called isDarkMode and prop called darkMode:

props: [ "darkMode", "menuTitle" ], data() {   return {     isOpen: false,     isDarkMode: false   } } 
Enter fullscreen mode Exit fullscreen mode

 
Third, add watcher to watch darkMode prop:

watch: {   darkMode(val) {      // Force dark mode     if( !val )       this.isDarkMode = false      // Force dark mode     if( val == 'force' )       this.isDarkMode = true      // Switch dark / light mode automatically according to what user prefer     if( val == 'auto' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches )         this.isDarkMode = true    } } 
Enter fullscreen mode Exit fullscreen mode

Whats happening here ⬆️:

We added a new prop and variable to indicate what style we want to use — we also added conditional classes to all HTML elements — so that if isDarkMode is true we add a special --dark class to elements and lastly we added a watcher to change the mode accordingly.

darkMode prop accepts three kinds of values:
false → Always show light mode
force → Always show dark mode
auto → Automatically change according to what user prefs

You can find the whole code for the component here

How to use

  1. Include the component
  2. Use it
<dropdown-menu menu-title="Vue Dropdown Menu" dark-mode="auto">    <section class="option">     <button @click="sayHello">This is button for method</button>     <span class="desc">This is Vue dropdown menu method that says hello for you.</span>   </section>    <section class="option">    <a href="https://duckduckgo.com">This is basic a -link</a>    <span class="desc">Clicking this takes you somewhere else.</span>   </section>    <section class="option">     <router-link to="/about">This is Vue router link</router-link>     <span class="desc">Clicking this takes you somewhere else.</span>   </section>  </dropdown-menu> 
Enter fullscreen mode Exit fullscreen mode

 

🎉✌️🙏

 

javascripttutorialvuewebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    ES6 - A beginners guide - Template Literals

    • 0 Answers
  • Author

    Understanding Higher Order Functions in JavaScript.

    • 0 Answers
  • Author

    Build a custom video chat app with Daily and Vue.js

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.