Search code examples
vue.jshoverdropdownnuxt.jsbulma

How do I make the hoverable dropdown close when navigating to a differerent link in Bulma?


HERE is a SANDBOX with the issue on it

  • I have a hoverable dropdown inside a Navbar
  • When I move to a different page, the dropdown is still open
  • I have tried this in plain Bulma, the issue still remains
  • I am on Nuxt.js using SSR
  • I am using nuxt-link / equivalent of Vue router-link to navigate to a different page.

Here is the my default.vue file

<template>
  <div class="ch-container">
    <header class="ch-header">
      <nav
        class="navbar is-fixed-top"
        role="navigation"
        aria-label="main navigation"
      >
        <div class="navbar-brand">
          <nuxt-link class="navbar-item" to="/">
            <img
              alt="CH Logo"
              src="https://i.imgur.com/v35Kfc9.png"
              width="28"
              height="28"
            />
          </nuxt-link>

          <a
            role="button"
            class="navbar-burger burger"
            aria-label="menu"
            aria-expanded="false"
            data-target="navbarBasicExample"
          >
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </a>
        </div>

        <div id="navbarBasicExample" class="navbar-menu">
          <div class="navbar-start">
            <nuxt-link class="navbar-item" to="/news">
              News
            </nuxt-link>

            <nuxt-link class="navbar-item" to="/resources">
              Resources
            </nuxt-link>

            <nuxt-link class="navbar-item" to="/tickers">
              Tickers
            </nuxt-link>

            <div class="navbar-item has-dropdown is-hoverable">
              <a class="navbar-link">
                More
              </a>
              <div class="navbar-dropdown">
                <a class="navbar-item">
                  FAQ
                </a>
                <nuxt-link class="navbar-item" to="/contact">
                  Contact
                </nuxt-link>
                <hr class="navbar-divider" />
                <a class="navbar-item">
                  Feature Request
                </a>
              </div>
            </div>
          </div>

          <div class="navbar-end">
            <div class="navbar-item">
              <a href="#">
                <fa :icon="faMoon" />
              </a>
            </div>
            <div class="navbar-item has-dropdown is-hoverable">
              <a class="navbar-link is-arrowless">
                <fa :icon="faExclamationCircle" />
              </a>
              <div class="navbar-dropdown">
                <a class="navbar-item">
                  No new notifications
                </a>
              </div>
            </div>
            <div class="navbar-item">
              <div class="buttons">
                <nuxt-link class="button is-primary" to="/signup">
                  <strong>Sign up</strong>
                </nuxt-link>
                <nuxt-link id="login" class="button is-light" to="/login">
                  Log in
                </nuxt-link>
              </div>
            </div>
          </div>
        </div>
      </nav>
    </header>
    <main class="ch-main">
      <nuxt />
    </main>
    <footer class="ch-footer is-hidden-mobile">
      <div class="level">
        <div class="level-left">
          <div class="level-item">
            <a href="#">
              <span class="icon">
                <fa :icon="faFacebookSquare" />
              </span>
            </a>
            <a href="#">
              <span class="icon">
                <fa :icon="faTwitterSquare" />
              </span>
            </a>
            <a href="#">
              <span class="icon">
                <fa :icon="faRedditSquare" />
              </span>
            </a>
          </div>
        </div>
        <div class="level-right">
          <div class="level-item">
            &copy;ch, All Rights Reserved
          </div>
          <div class="level-item">
            <nuxt-link to="/contact">Contact</nuxt-link>
          </div>
          <div class="level-item">
            <nuxt-link to="/terms-of-service">Terms</nuxt-link>
          </div>
          <div class="level-item">
            <nuxt-link to="/privacy-policy">Privacy</nuxt-link>
          </div>
        </div>
      </div>
    </footer>
  </div>
</template>

<script>
import {
  faFacebookSquare,
  faTwitterSquare,
  faRedditSquare,
} from '@fortawesome/free-brands-svg-icons'

import { faMoon, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'

export default {
  computed: {
    faFacebookSquare() {
      return faFacebookSquare
    },
    faTwitterSquare() {
      return faTwitterSquare
    },
    faRedditSquare() {
      return faRedditSquare
    },
    faMoon() {
      return faMoon
    },
    faExclamationCircle() {
      return faExclamationCircle
    },
  },

  mounted() {
    // Get all "navbar-burger" elements
    const $navbarBurgers = Array.prototype.slice.call(
      document.querySelectorAll('.navbar-burger'),
      0
    )

    // Check if there are any navbar burgers
    if ($navbarBurgers.length > 0) {
      // Add a click event on each of them
      $navbarBurgers.forEach((el) => {
        el.addEventListener('click', () => {
          // Get the target from the "data-target" attribute
          const target = el.dataset.target
          const $target = document.getElementById(target)

          // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
          el.classList.toggle('is-active')
          $target.classList.toggle('is-active')
        })
      })
    }
  },
}
</script>

<style></style>

Here is a GIF illustrating the problem

enter image description here

How to close the dropdown after you move to a different page?


Solution

  • Ok, the whole thing makes it complicated, because the hover is triggered by css and therefore the dropdown can always be seen when the mouse is over it. You have to overwrite this state and solve it with vue events. We also have to put a watcher on the route to reset the state.

    CodeSandbox - Example

    <template>
      <div class="container">
        <nav class="navbar" role="navigation" aria-label="main navigation">
          <div class="navbar-brand">
            <!-- <a class="navbar-item" href="https://bulma.io"> -->
            <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
    
            <a
              role="button"
              class="navbar-burger burger"
              aria-label="menu"
              aria-expanded="false"
              data-target="navbarBasicExample"
            >
              <span aria-hidden="true"></span>
              <span aria-hidden="true"></span>
              <span aria-hidden="true"></span>
            </a>
          </div>
    
          <div id="navbarBasicExample" class="navbar-menu">
            <div class="navbar-start">
              <a class="navbar-item">Home</a>
    
              <a class="navbar-item">Documentation</a>
    
              <div
                @mouseover="toggleDropdown(true)"
                @mouseleave="toggleDropdown(false)"
                class="navbar-item has-dropdown is-hoverable"
              >
                <a class="navbar-link">More</a>
    
                <div class="navbar-dropdown" :style="{display: showDropdown ? 'block' : 'none' }">
                  <nuxt-link class="navbar-item" to="/about">About</nuxt-link>
                  <nuxt-link class="navbar-item" to="/jobs">Jobs</nuxt-link>
                </div>
              </div>
            </div>
    
            <div class="navbar-end">
              <div class="navbar-item">
                <div class="buttons">
                  <a class="button is-primary">
                    <strong>Sign up</strong>
                  </a>
                  <a class="button is-light">Log in</a>
                </div>
              </div>
            </div>
          </div>
        </nav>
        <Nuxt/>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          routeChange: false,
          showDropdown: false
        };
      },
    
      watch: {
        $route() {
          this.routeChange = true;
          this.showDropdown = false;
        }
      },
    
      methods: {
        toggleDropdown(payload) {
          if (this.showDropdown !== payload) this.routeChange = false;
          if (!this.routeChange) {
            this.showDropdown = payload;
          }
        }
      }
    };
    </script>