💧 Tailwind CSS+Reactを使った領域外クリックで閉じるドロップダウンメニュー

2020-11-07 #Development

CSS Only のフレームワークを使う時もドロップダウンメニューを作る場合には領域外をクリックしたら閉じる機能を入れておきたいのですが、気を抜いたら横着しそうなので簡単に実装できるようにやり方をメモ。

コンポーネント側

  • useEffectでリスナーをつけたり外したりする
  • マウスダウンした時にuseRefを使って領域内かどうかを判定
  • useStateを使って状態を切り替える
import React, { useRef, useState, useEffect } from "react";

const Dropdown = (props) => {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef();

  useEffect(() => {
    document.addEventListener("mousedown", handleOutsideClick);
    return () => {
      document.removeEventListener("mousedown", handleOutsideClick);
    };
  }, []);

  const handleOutsideClick = (e) => {
    if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
      setIsOpen(false);
    }
  };

  return (
    <>
      <div ref={dropdownRef} className="relative inline-block text-left">
        <span className="rounded-md shadow-sm">
          <button onClick={() => setIsOpen(!isOpen)} type="button" className="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" id="options-menu" aria-haspopup="true" aria-expanded={isOpen}>
            {props.title}
            <svg className="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
              <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
            </svg>
          </button>
        </span>

        {isOpen && (
          <>
            <div className="origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg z-50">
              <div className="rounded-md bg-white shadow-xs">
                <div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
                  {props.children}
                </div>
              </div>
            </div>
          </>
        )}
      </div>
    </>
  );
};

export default Dropdown;

使う側

  • ドロップダウンの中身は props.children で外から渡す
<Dropdown label="Options">
  <a href="#" className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
    Item0
  </a>
  <a href="#" className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
    Item1
  </a>
  <a href="#" className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
    Item2
  </a>
</Dropdown>

React はまだ慣れないんだけど Vue と違って children の中身をボタンのとことコンテンツのところを分けて渡すような事はできないのかな?(Vue だとslot:labelとslot:contentみたいなタグをつけて渡す事ができる)

ボタンのラベルの部分をアイコンにしたい時もあるのでできればラベル部分も html タグごとコンポーネントの外から渡したいけど、その時はまた一手間かかりそう。

所感

Tailwind CSS を初めて使ってみたんだけどとてもよさそうですね。 tailwind.config.js にちょろっと記述を入れるだけで purgeCSS が使えるところも素敵。

CSS フレームワーク、いくつか使ってみた結果やっぱり Bootstrap が良いと思っていたんだけど、Tailwind CSS は仲良くやっていけそうです。

しかしここまでスタイルを html に書きまくるのだったらいっそ Vanilla CSS をタグに直書きするのと変わらないのではと思ったけど、そうか、コード量が減るメリットがあったりするのかな。だとしたら CSS は普通に自分で書いた方が余分なタグも減って綺麗でシンプルに書けるよなとも思いつつ、いやしかし CSS に一切触れずに開発を進められるのはプロトタイプの段階では重要だよなと思うし、なかなか答えが出てきません…

comments powered by Disqus
Profile
😛

石原 悠 / Yu Ishihara

デザインとプログラミングとヨーグルト作りが好きです。