Micromodal.jsをカスタムHookで仕立てる
JSON色付け係のhanetsukiです。
今回はMicromodal.jsをカスタムHookで汎用的に使えるようにしようという試みで記事を書きました。
今回の実装はリポジトリを公開してありますので、参考になれば幸いです。
Micromodal.jsとは
- バニラjsで構成されたモーダルライブラリ
- アクセシビリティが考慮されている
- 1.9kbと軽量
要点をまとめると上記のような特徴を持っています。
近年様々なフレームワークを元に実装して行く最中、どのフレームワークに対してもアプローチ可能で、軽量!
そして、一番実装で苦戦してしまいそうなa11yが考慮されているのは非常にありがたいです。
事前準備
インストール
micromodalをプロジェクトに追加します
yarn add micromodal
useMicromodalを作成する
今回はuse-micromodal
フォルダを作成し、その中に4つのファイルで振り分けたいと思います。
- index.tsx
useMicromodal
のエントリーポイントとなるファイル - modal.view.tsx
Micromodal.jsの描画を司るファイル - modal.hook.ts
Micromodal.jsの機能を司るファイル - modal.scss
modal.view.tsxの装飾を司るファイル
では、早速作成していきましょう!
modal.view.tsx
公式のUsageに従ってマークアップしていきます。
import styles from './modal.module.scss'
import { createPortal } from 'react-dom'
type Props = {
id: string
}
export const Modal: React.FC<Props> = ({ children, id }) => {
return createPortal(
<div id={id} aria-hidden="true" className={styles.wrap}>
<div className={styles.overlay} tabIndex={-1} data-micromodal-close>
<div role="dialog" className={styles.dialog} aria-modal="true">
{children}
</div>
</div>
</div>,
document.body
)
}
この時ポイントなのは、createPortal
を用いることです。
モーダルコンポーネントを通常のコンポーネントと同じように作成してしまうと、z-indexなどのDOMの階層による表示バグの原因となります。
createPortal を用いることで、<body>
タグの閉じタグ直前にappendする形でモーダルコンポーネントを描画することが可能になります。
modal.scss
modal.view.tsx
の装飾をしていきます。
.wrap {
display: none;
&.is-open {
display: block;
}
}
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.dialog {
overflow-y: auto;
max-height: 100vh;
}
ポイントは&.is-open { display: block; }
の記述です。
Micromodal.jsでは、対象となるidのDOMにis-open
のクラスを付与することで描画をコントロールしています。
※ このクラスはオプションで変更可能です。
modal.hook.ts
モーダルの開閉とMicromodal.jsを初期化する処理を記述します。
import client from 'micromodal'
import { useCallback } from 'react'
export const useHook = (id: string) => {
const open = useCallback(() => {
client.show(id, {
disableScroll: true
})
}, [id])
const close = useCallback(() => {
client.close(id)
}, [id])
return { open, close }
}
client.show
の第二引数にmicromodalのオプションを渡すことができます。
client.show({
disableScroll: true
})
index.tsx
最後にエントリーポイントです。
今まで作成したviewとhookを組み合わせます。
import { useHook } from './modal.hook'
import { Modal as ModalComponent } from './modal.view'
import { useCallback } from 'react'
export type UseMicromodal = (id: string) => {
Modal: React.FC<{ children: React.ReactNode }>
open: () => void
close: () => void
}
export const useMicromodal: UseMicromodal = (id: string) => {
const { open, close } = useHook(id)
const Modal = useCallback(
({ children }) => {
return <ModalComponent id={id}>{children}</ModalComponent>
},
[id]
)
return {
Modal,
open,
close
}
}
ポイントはカスタムHooksの内部でコンポーネントを生成し、返却する点です。
Provider形式にするか迷いましたが、今回はこの形で作成してみました。
このファイルを起点としてuseMicromodal
を呼ぶことが可能になりました。
利用方法
import { useMicromodal } from './hooks/use-micromodal'
function App() {
const { Modal, open, close } = useMicromodal('sample-modal')
return (
<div className="App">
<button onClick={open}>open</button>
<Modal>
<p>Opend modal</p>
<button onClick={close}>close</button>
</Modal>
</div>
)
}
利用したい場所でuseMicromodal
を呼び出し、引数にmodalのIDとなる文字列を渡して実行します。
戻り値にメソッドとコンポーネントが返るので、それを利用してあとはよしなにコーディングするだけです。
カスタムHookを用いて利用時にスッキリと利用できるコンポーネントが出来上がりました!!
最後に
アクセシビリティなど、webアプリケーションを作成する上でなかなか行き届かないところをライブラリを使い開発するのが主流になってきました。
こういったラップして使うことが多くなって行くんだろうなというのが最近思ったことです。
うまくライブラリとは付き合っていきたいですね!
至らぬ点などあったとは思いますが、最後まで読んでいただき有難うございました。
Discussion