📝

TailwindCSSでのダイアログの実装方法を調べる

2025/01/22に公開

TailwindCSSでのダイアログの実装方法について調べる。

TailwindCSSでダイアログでの実装方法がわからなかったので、shadcn-railsで実装がされており参考にしながらダイアログをHTML/js実装をした。

調査

  • ドキュメント

https://shadcn.rails-components.com/docs/components/sheet

  • shadcn-railsのダイアログ関連の実装コード

https://github.com/aviflombaum/shadcn-rails/blob/main/app/views/components/ui/_sheet.html.erb
https://github.com/aviflombaum/shadcn-rails/blob/main/app/javascript/controllers/ui/sheet_controller.js
https://github.com/aviflombaum/shadcn-rails/blob/main/app/views/components/ui/_sheet.html.erb

基本HTML

shadcn-railsのダイアログ関連コードから基本HTMLを作成した。

<body>
    <div>
      <div>SheetTrigger</div>
      <div>Backdrop</div>
      <div
        role="dialog"
        data-state="open"
        class="data-[state=closed]:hidden data-[state=open]:block fixed z-50 gap-4 bg-backgroud p-6 shadow-lg
              transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300
              data-[state=closed]:slide-out-to-right
              data-[state=open]:slide-in-from-right"
        tabindex="-1"
        style="pointer-events: auto">
        <div>
          <h2 class="mt-10 scroll-m-20 border-b pb-2 text-3xl
          font-semibold tracking-tight transition-colors first:mt-0">
            The King's Plan
          </h2>
          <p class="leading-7 [&:not(:first-child)]:mt-6">
            The king thought long and hard, and finally came up with
            <a href="#" class="font-medium text-primary underline underline-offset-4">
              a brilliant plan
            </a>:
            he would tax the jokes in the kingdom.
          </p>
          <blockquote class="mt-6 border-l-2 pl-6 italic">
            "After all," he said, "everyone enjoys a good joke,
            so it's only fair that they should pay for the privilege."
          </blockquote>
        </div>
        <button
          type="button"
          class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
        >
          <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="h-4 w-4">
          <line
            x1="18"
            x2="6"
            y1="6"
            y2="18"></line>
          <line
            x1="6"
            x2="18"
            y1="6"
            y2="18"></line>
        </svg>
        <span class="sr-only">Close</span>
          </button>
        </div>
    </div>
</body>

backdropを追加する

ダイアログの後ろに表示するbackdropを追加する。

https://github.com/aviflombaum/shadcn-rails/blob/main/app/views/components/ui/_sheet.html.erb#L4

ダイアログでは開閉の状態管理には、data-state="closed"で扱う。

      <div>
        <div
        id="modal"
        data-ui--dialog-target="modal"
        data-ui--sheet-target="modal"
        data-state="closed"
        class="hidden fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
        style="pointer-events: auto"
        data-aria-hidden="true"
        aria-hidden="true"></div>
      </div>

TailwinCSS v3.1では、<dialog>タグを使えば、::backdrop要素を指定することが可能になっている。

https://tailwindcss.com/blog/tailwindcss-v3-1#style-native-dialog-backdrops

ダイアログの起動ボタンの配置とjs制御

ダイアログの起動ボタンを配置して、jsでダイアログを開く。

https://github.com/aviflombaum/shadcn-rails/blob/main/app/views/components/ui/_sheet.html.erb#L2

      <div>
        <button
        id="opensidebar"
        type="button"
        data-state="closed"
        >
          OpenSidebar
        </button>
      </div>

shadcn-railsではStimulusが利用されているため、data-targetを起点にして、要素を取得する。
jsでhiddenクラスを削除し、data-sate=openにする。

https://github.com/aviflombaum/shadcn-rails/blob/main/app/javascript/controllers/ui/sheet_controller.js#L11

// open sidebar
document.querySelector('#opensidebar').addEventListener('click', (event) => {
    const sheetTarget = document.querySelector('#sidebarsheet')
    const dialogTarget = sheetTarget.querySelector("[data-ui--sheet-target='dialog']")
    const modalTarget = sheetTarget.querySelector("[data-ui--sheet-target='modal']")
    const contentTarget = sheetTarget.querySelector("[data-ui--sheet-target='content']")

    document.body.classList.add("overflow-hidden");
    contentTarget.classList.add("overflow-y-scroll", "h-full");
    dialogTarget.classList.remove("hidden");
    dialogTarget.dataset.state = "open";
    modalTarget.classList.remove("hidden");
    modalTarget.dataset.state = "open";
});

overflow-hiddenではみだしたコンテンツを非表示にする。
body要素のコンテンツフローした分を非表示にするため、スクロールなしとなる。

https://tailwindcss.com/docs/overflow#hiding-content-that-overflows

overflow-y-scrollで、はみだしたコンテンツがある場合にスクロールする。
ダイアログコンテンツがフローした分のスクロールを制御する。

閉じるボタンの追加

閉じるボタン押下時のダイアログの非表示を行う。
jsでhiddenクラスを追加し、data-sate=closedにする。

https://github.com/aviflombaum/shadcn-rails/blob/main/app/javascript/controllers/ui/sheet_controller.js#L25

// close sidebar
document.querySelector('#closesidebar').addEventListener('click', (event) => {
    const sheetTarget = document.querySelector('#sidebarsheet')
    const dialogTarget = sheetTarget.querySelector("[data-ui--sheet-target='dialog']")
    const modalTarget = sheetTarget.querySelector("[data-ui--sheet-target='modal']")
    const contentTarget = sheetTarget.querySelector("[data-ui--sheet-target='content']")

    document.body.classList.remove("overflow-hidden");
    contentTarget.classList.remove("overflow-y-scroll", "h-full");
    dialogTarget.classList.add("hidden");
    dialogTarget.dataset.state = "closed";
    modalTarget.classList.add("hidden");
    modalTarget.dataset.state = "closed";    
});

// close sidebar modal
document.querySelector('#modal').addEventListener('click', (event) => {
    const sheetTarget = document.querySelector('#sidebarsheet')
    const dialogTarget = sheetTarget.querySelector("[data-ui--sheet-target='dialog']")
    const modalTarget = sheetTarget.querySelector("[data-ui--sheet-target='modal']")
    const contentTarget = sheetTarget.querySelector("[data-ui--sheet-target='content']")

    document.body.classList.remove("overflow-hidden");
    contentTarget.classList.remove("overflow-y-scroll", "h-full");
    dialogTarget.classList.add("hidden");
    dialogTarget.dataset.state = "closed";
    modalTarget.classList.add("hidden");
    modalTarget.dataset.state = "closed";    
});

animation

ダイアログのアニメーション対応は、tailwindcss-animateを利用しているため、インストールする。

https://github.com/jamiebuilds/tailwindcss-animate

ラップアップ

  • date-stateを利用してHTMLの状態を制御している。
  • data-*属性を使ってjsの要素を特定し制御している。
  • ダイアログのアニメーション表示は、tailwind-animateを利用している。

他に、shadcn/uiでは、sidebarまで実装されているためこちらも実装方法を確認したい。

https://ui.shadcn.com/docs/components/sidebar

Discussion