Modal
モーダルダイアログ。画面を覆って、応答するまで先へ進ませない
素の dialog に乗っています
showModal() を呼ぶと、ブラウザが次の全部を持ってくれます。
- 最前面の層に出る(
z-indexもoverflowも関係しない) - フォーカスが中に閉じ込められる
- 後ろが操作できなくなる
Escapeで閉じる- 閉じたとき、開く前にフォーカスがあった場所へ戻る
自前で書くと、この5つを全部持ち直すことになります。
属性だけで出すと、この層に入りません。 入らないとフォーカスの閉じ込めも後ろの遮断も
効きませんが、見た目は出るので気づけません。テストが :modal で測っています。
開いているかどうかは利用側が持ちます
const modal = useModal();
<Button onClick={modal.show}>消す</Button>
<Modal open={modal.open} onClose={modal.hide} title="確認">
本当に消しますか
</Modal>これは利用側が知りたい値です——開いている間だけ読み込む、 閉じたら次の案内を出す、など。コンポーネントの中に隠すと「開いた」ことしか分かりません。
Escape で閉じたときも onClose が呼ばれます。返さないと、
利用側は開いていると思ったままになり、次に押しても開きません。
後ろの巻き取りは自分で止めています
showModal() は後ろの操作は止めますが、巻き取り(スクロール)は止めません。
止めないと、中を読んでいるつもりで後ろが動きます。
背景を押して閉じるのは既定ではありません
dialog は背景の部分も自分の領域なので、押した場所が中か外かを座標で見分ける
ことになります。取り違えると、中を押したのに閉じます。
消える操作の前では代償が大きいので、closeOnBackdrop を渡したときだけにしています。
表示と消去にアニメーションが付きます
ダイアログと背景が一緒に薄れて現れ、薄れて消えます。
閉じるときが難しいところです。 閉じる操作は表示を即座に消すので、
何もしないと消えてから動くことになり、動きが見えません。
display と overlay を allow-discrete で遷移させて、
動き終わるまで最前面の層に留まるようにしています。
動きを減らす設定(prefers-reduced-motion)では動きません。 尊重するのは利用側の責務ではないので、
トークン層で止めています。
幅は決めていません
このシステムには幅の段がありません。 素の dialog は中身に合わせて広がり、
画面幅で止まります。幅を決めたいときは className か style で渡してください。
3つの状態
通常・空・エッジケースの3つは、どのコンポーネントにも必ずあります。
通常default.tsx
通常。開いているかどうかは `useModal` が持つ。
空empty.tsx
空。中身も操作も無い、見出しだけ。
エッジケースedge.tsx
エッジケース。中身が長く、入力があり、覆いを押すと閉じる。
props
Prop
Type
型と説明はコンポーネントのソースから抽出しています。既定値は出していません——cva の defaultVariants は 署名に現れないので、表に出すと「引数のデフォルトが書かれているものだけ」になり、嘘になります。