👻

TailwindUIのJavascriptが提供されてない件

2023/11/12に公開

概要

ずっとSASSとBootstrapでやってましたが遅ればせながらTailwindを触ってみました。コンセプトはなかなか気に入ったのですが、公式のTailwindUIは、ReactかVue以外で使う時はJavascriptは自分で書かないとダメみたいです。

TailwindUIのDropdwonのJSを書いてみたので置いておきます。

コード

document.addEventListener('DOMContentLoaded', () => {
  const toShowClasses = ['opacity-100', 'scale-100'];
  const toHideClasses = ['opacity-0', 'scale-95'];

  const dropdowns = document.querySelectorAll('.dropdown');

  dropdowns.forEach(dropdown => {
    const trigger = dropdown.querySelector('.dropdown-trigger');
    const menu = dropdown.querySelector('.dropdown-menu');

    if (trigger === null || menu === null) return;

    menu.classList.add(...toHideClasses, 'transition', 'duration-100', 'ease-out');

    menu.addEventListener('transitionend', () => {
      if (toHideClasses.every(c => menu.classList.contains(c))) {
        menu.classList.add('hidden');
      }
    });

    trigger.addEventListener('click', () => {
      if (menu.classList.contains('hidden')) {
        menu.classList.remove('hidden');
        setTimeout(() => {
          menu.classList.remove(...toHideClasses);
          menu.classList.add(...toShowClasses);
        }, 0);
      } else {
        menu.classList.remove(...toShowClasses);
        menu.classList.add(...toHideClasses);
      }
    });
  });

  document.addEventListener('click', event => {
    if (event.target) {
      const target = event.target as HTMLElement;
      if (target.closest('.dropdown-menu')) {
        return;
      }
    }

    dropdowns.forEach(dropdown => {
      const menu = dropdown.querySelector('.dropdown-menu');
      if (menu === null) return;

      menu.classList.remove(...toShowClasses);
      menu.classList.add(...toHideClasses);
    });
  });
});
<div class="dropdown relative inline-block text-left">
  <div>
    <button
      type="button"
      class="dropdown-trigger inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
      id="menu-button"
      aria-expanded="true"
      aria-haspopup="true"
    >
      Options
      <svg class="-mr-1 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
        <path
          fill-rule="evenodd"
          d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
          clip-rule="evenodd"
        />
      </svg>
    </button>
  </div>

  <div
    class="dropdown-menu absolute right-0 z-10 mt-2 hidden w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
    role="menu"
    aria-orientation="vertical"
    aria-labelledby="menu-button"
    tabindex="-1"
  >
    <div class="py-1" role="none">
      <!-- Active: "bg-gray-100 text-gray-900", Not Active: "text-gray-700" -->
      <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="menu-item-0"
        >Account settings</a
      >
      <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="menu-item-1"
        >Support</a
      >
      <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="menu-item-2"
        >License</a
      >
      <form method="POST" action="#" role="none">
        <button
          type="submit"
          class="block w-full px-4 py-2 text-left text-sm text-gray-700"
          role="menuitem"
          tabindex="-1"
          id="menu-item-3"
        >
          Sign out
        </button>
      </form>
    </div>
  </div>
</div>

解説

Javascriptのトリガーとしてラッパーとボタンとメニューにそれぞれdropdowndropdown-triggerdropdown-menuのクラスを付与しました。

ドキュメントを読むとHTMLに付与するクラスのヒントが書いてあります。

  <!--
    Dropdown menu, show/hide based on menu state.

    Entering: "transition ease-out duration-100"
      From: "transform opacity-0 scale-95"
      To: "transform opacity-100 scale-100"
    Leaving: "transition ease-in duration-75"
      From: "transform opacity-100 scale-100"
      To: "transform opacity-0 scale-95"
  -->

これをonClickでつけたり外したりしてるだけですね。メニューはdisplay:noneにしないと他の要素のクリックを阻害すると思うのでhiddenもつけたり外したりしてますが、hiddenを外してすぐクラスを付与してもtransitionのアニメーションが動かないようです。なのでsetTimeoutで処理を後ろに回してます。

ページロード時にメニューが表示されないようにHTML側にもhiddenをつけてます。これをつけないと一瞬見えます。

ドキュメントクリックで閉じる挙動が一般的だと思うのでそうしました。

このドロップダウンは左寄せ(ボタンと右側のツラが揃った状態)で表示されますが、right-0 origin-top-rightを、それぞれleftに変えてやると右寄せに変わります。

注意点

2023/11/17に追記

Tailwind慣れてる人には当たり前かもしれないけど、ハマったので追記。tailwindは使用してないCSSは読み込みません。使用してるかどうか検索するファイルの指定はtailwind.config.jscontentに指定します。contentに指定を忘れて、JSやTSにクラス名を記載してもしそれがHTML無いと読み込まれず動きません。動いていたも(何故動いてたのか謎だが)のがいつも間にか動かなくなってハマりました。

/** @type {import('tailwindcss').Config} */

module.exports = {
  content: ['app/views/**/*.html.erb', 'app/frontend/**/*.js', , 'app/frontend/**/*.ts'],
  plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
};

感想

299ドルも取ってJSは自分で書けって、それは無いだろ、って気はしますね。まあ、JSが必要なのはDropdownとModalくらいらしいですが。しかも、関係ないけど299ドルって今45,000円を超えるんですね。クレジットカードだと少し高めに来るので5万近いのかな?円安どうにかしろ!政府。

他のComponentライブラリも触ってみましたがボタンにインタラクションがなかったり、チェックボックスのテキストがなぜか左側だったり、デザインが好みじゃなかったり・・・Sailboat UIがいい感じでライセンスもMITですが、Alpinejsを使ってるのがあまり好きじゃ無いのとComponentの数も頼りない感じです。

あと、ピュアなtailwindではなくbtnとかpanelみたいに独自クラスにまとまってると依存度が高そうで微妙な感じもします。

もう一つ無料でおすすめはこれですかね。ただ、こちらもピュアなHTMLの場合JSは自分で実装しないとダメそうですね(たぶん)。

https://www.tailwind-kit.com/

細かい説明は端折りますがいくつか貼っときますね。

https://flowbite.com/
https://daisyui.com/
https://preline.co/index.html

おすすめのComponentライブラリがあったらコメントで教えていただけると嬉しいです。

Discussion