DEV Community

Discussion on: Tailwind Enter/Leave Transition Effects with Stimulus.js

Collapse
 
andypeters profile image
Andy Peters

This is really great, thanks for the share! I echo exactly what Andrew Mason said too.

One thing was missing for me and that was clicking away on the window to close the dropdown. In AlpineJS it is handled with @click.away=. I wish I knew more about javascript and events, but I did find adding an event listener when the button is clicked and removing it upon close to work well. Supports clicking in the dropdown, the original button and outside (somewhere on the window).

Here's my updated dropdown_controller.js for you or anyone else listening:

import {Controller} from "stimulus"
import {enter, leave} from 'el-transition';

export default class extends Controller {
  static targets = ["menu", "button"]

  toggleMenu(event) {
    event.stopPropagation();
    if (this.menuTarget.classList.contains('hidden')) {
      document.body.addEventListener('click', event => this.closeMenu(event));
      enter(this.menuTarget)
    } else {
      this.closeMenu(event);
    }
  }

  closeMenu(event){
    document.body.removeEventListener('click', event => this.closeMenu(event));
    leave(this.menuTarget)
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mmccall10 profile image
Mike McCall • Edited

For sure! That's a really cool solution! I agree that handling clicks outside is important. I didn't go down that road because it strayed from the purpose of the article and some UI's don't implement it. ie. npmjs.com.

FWIW I am/was doing something like this, a pattern I see in React.

import {Controller} from "stimulus"
import {leave, toggle} from 'el-transition';

export default class extends Controller {
    static targets = ["menu", "button"]

    handleClickOutside(event) {
        const menuClicked = this.menuTarget.contains(event.target)
        const buttonClicked = this.buttonTarget.contains(event.target)
        const hidden = this.menuTarget.classList.contains('hidden')

        if (!menuClicked && !buttonClicked && !hidden ) {
            leave(this.menuTarget)
        }
    }

    connect() {
        document.addEventListener('click', this.handleClickOutside.bind(this));
    }

    disconnect() {
        document.removeEventListener('click', this.handleClickOutside.bind(this));
    }

    toggleMenu() {
        toggle(this.menuTarget)
    }
}

Collapse
 
andypeters profile image
Andy Peters

Heck yah, that works great! Thanks for following up.

Collapse
 
damel profile image
Dmitrii Amelchenko • Edited

First, thanks for a good post. And, i believe, you can doing something like

data-action="click->dropdown#toggleMenu click@window->dropdown#hideMenu"
Enter fullscreen mode Exit fullscreen mode
hideMenu(event) {
    const buttonClicked = this.buttonTarget.contains(event.target)

    if (!buttonClicked) {
      leave(this.menuTarget)
    }
  }
Enter fullscreen mode Exit fullscreen mode