🦔

Radix UI の Dialog コンポーネントTips

2024/03/10に公開

RadixUIはアクセシビリティに焦点を当てた低レベルのUIコンポーネントライブラリで、非常に柔軟性が高く、スタイリングが制限されていない点が特徴です。ダイアログのようなUIはアクセシビリティ等をしっかり実装しようとすると考慮する点が意外に多く、実装コストがかかるのでRadixを活用していきたいところです。
この記事では、RadixのDialogコンポーネントを使用するにあたってのTipsを共有していきます。またshadcn/uiに対しても、中身はRadixが使用されているため基本的に応用が可能です。
Dialogコンポーネントの基本的な使い方はドキュメントを参照してください。

https://www.radix-ui.com/primitives/docs/components/dialog

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

イベント時の自動制御

Dialogコンポーネントには以下のようにイベント時の自動処理が発生します。

  • オープン、クローズ時に自動的にフォーカスが発生する
  • Escキー押下時、ダイアログ外への操作時にダイアログがクローズする

これらを無効・制御するため、ContentコンポーネントにイベントハンドラのPropsが用意されています。

オープン、クローズ時のフォーカス制御

デフォルトではオープン時には自動でダイアログコンテンツ内の要素にフォーカスし、クローズ時は開く前の要素にフォーカスが自動で戻ります。これを制御するにはonOpenAutoFocusonCloseAutoFocusを利用します。
まず、無効にしたい場合は一般的なイベント同様にpreventDefault()が利用できます。

<Dialog.Content
  onOpenAutoFocus={(e) => {
    e.preventDefault();
  }}>
  ...
</Dialog.Content>

フォーカスする要素を制御したい場合は、対象の要素のfocus()メソッドをコールバック関数内で呼び出す形で実現します。ここでは、useRefを用いて対象の要素への参照がある例を記載します。

<Dialog.Content
  onOpenAutoFocus={(e) => {
    ref.current.focus();
  }}>
  ...
</Dialog.Content>

Escキー押下時、ダイアログ外への操作時のクローズ制御

デフォルトではEscキーを押下したり、ダイアログのオーバーレイ部分をクリックしたりするとダイアログがクローズします。
これらを制御するために、先ほど同様にEscキーの制御であればonEscapeKeyDown、ダイアログ外操作に対する制御であればonPointerDownOutsideonInteractOutsideが用意されています。
例えば、Escキーを押下した場合はダイアログを閉じるのではなく、別のUIを動作させたい場合などに利用できます。

<Dialog.Content
  onEscapeKeyDown={(e) => {
  e.preventDefault()
    // 別のUIを表示させる任意の処理
  }}>
  ...
</Dialog.Content>
`onPointerDownOutside`と`onInteractOutside`の違い

どちらもダイアログ外の操作に対して(オーバーレイ部分をクリックした場合など)イベントが発火しますが、onPointerDownOutsideはポインタイベントが発生した場合に限定されます。onInteractOutsideはその他のインタラクションも含むので基本的にはonInteractOutsideを利用するのが良いかと思います。

オープン、クローズ時の処理を一括制御

上記で触れたように、Dialogコンポーネントがクローズする契機は複数あります。(Escキー、オーバーレイ、クローズボタン押下など)。また、オープンの契機も複数作り込むことが可能です。
それぞれイベントハンドラを設定することも可能ですが、共通で行いたい処理を設定するにはRootコンポーネントのonOpenChangeが利用できます。onOpenChangeは開閉時にbooleanでステートが渡されるのでこちらで制御します。

<Dialog.Content
  onOpenChange={(open) => {
  // クローズの処理
   if (!open.valueOf()) {
      // 任意の処理
     }
  }}
>
  ...
</Dialog.Content>

クローズのアニメーション

クローズのアニメーションにはdata-state属性を利用します。OverlayContentコンポーネントはクローズ時にこの属性がdata-state=falseとなるので、これをCSSセレクタに利用しアニメーションを設定します。

<Dialog.Root>
  <Dialog.Portal>
    <Dialog.Overlay className="DialogOverlay" />
    <Dialog.Content className="DialogContent">
  ...
  </Dialog.Content>
 </Dialog.Portal>
</Dialog.Root>
 
.DialogOverlay[data-state='closed'] {
  animation: overlayClose 1000ms cubic-bezier(0.16, 1, 0.3, 1);
}

.DialogContent[data-state='closed'] {
  animation: contentClose 1000ms cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes overlayClose {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes contentClose {
  from {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  to {
    opacity: 0;
    transform: translate(-50%, -48%) scale(0.96);
  }
}

Discussion