react-modalを利用してハーフモーダルを実装する

# React-Modal を使ったハーフモーダルの実装方法

## 概要
この記事では、React のモーダルライブラリである `react-modal` を利用したハーフモーダルの実装方法を説明します。

### 実際の動作
![preview.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2256398/a90d4b16-cf2e-4f78-ae77-aa0a52f75183.gif)

## この記事の対象者
- React でハーフモーダルを実装したい人


## `react-modal` について
`react-modal` とは、その名の通り React でモーダルを実装するためのライブラリです。

- [GitHub リポジトリ](https://github.com/reactjs/react-modal)
- [ドキュメント](https://reactcommunity.org/react-modal/)

モーダルのロジックや UI を自作するのは意外と手間がかかりますが、このようなライブラリを使うことで、開発効率を向上させることができます。


## 実装

### `react-modal` のインストール
まずは `react-modal` をインストールします。

```sh
npm install react-modal
```

### `Modal` コンポーネントを利用した実装
続いて、`react-modal` の `Modal` コンポーネントを使用して、ハーフモーダルを実装します。

```tsx
import { useCallback, useState } from "react";
import Modal from "react-modal";

function App() {
  const [isOpen, setIsOpen] = useState(false);

  const handleOpenModal = useCallback(() => {
    setIsOpen(true);
  }, []);

  const handleCloseModal = useCallback(() => {
    setIsOpen(false);
  }, []);

  return (
    <div className="container">
      <button type="button" onClick={handleOpenModal} hidden={isOpen}>
        Open Modal
      </button>
      <Modal
        contentLabel="halfModal"
        isOpen={isOpen}
        onRequestClose={handleCloseModal}
        className={{
          base: "modal-base",
          afterOpen: "modal-after",
          beforeClose: "modal-before",
        }}
        overlayClassName={{
          base: "overlay-base",
          afterOpen: "overlay-after",
          beforeClose: "overlay-before",
        }}
        closeTimeoutMS={250}
      >
        <div className="modal-inner">
          <p>モーダルの中身</p>
          <button type="button" onClick={handleCloseModal}>
            Close Modal
          </button>
        </div>
      </Modal>
    </div>
  );
}

export default App;
```


## 各 `props` の説明
| プロパティ           | 説明 |
|-----------------|---------------------------|
| `isOpen`        | モーダルの開閉状態を管理する `state` |
| `onRequestClose` | モーダル外をクリックしたときに発火する関数 |
| `className`     | モーダルのクラス名 |
| `overlayClassName` | モーダル背景のクラス名 |
| `closeTimeoutMS` | モーダルの開閉時のアニメーション時間 |

## CSS の設定

```css
.modal-base {
  width: 100%;
  height: calc(100vh - 50px);
  background-color: green;
  overflow-y: auto;
  border-radius: 12px 12px 0 0;
  transition: all 250ms ease-in-out;
  transform: translateY(100%);
}

.modal-after {
  transform: translateY(0%);
}

.modal-before {
  transform: translateY(100%);
}

.modal-inner {
  color: #fff;
  text-align: center;
}

.overlay-base {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: end;
  transition: background-color 250ms ease-in-out;
}

.overlay-after {
  background-color: rgb(0 0 0 / 0.8);
}

.overlay-before {
  background-color: rgb(0 0 0 / 0);
}
```

## 実際のコードと挙動
[CodeSandbox でデモを見る](https://codesandbox.io/p/devbox/react-modal-76lysz?embed=1&file=%2Fsrc%2FApp.tsx)


## 実装の注意点

### 1. フォントが適用されない問題
`react-modal` の `Modal` コンポーネントは、DOM ツリーでは `<div id="root" />` の外側に挿入されます。
そのため、`#root` に対してフォントを指定している場合、モーダル内のコンテンツにはフォントが適用されないことがあります。

#### **解決策**
`.modal-base` などにフォントを明示的に指定することで適用できます。

```css
.modal-base {
  font-family: "Arial", sans-serif;
}
```

### 2. モーダル表示時の背景スクロール問題
`react-modal` は、モーダル表示時に背景スクロールを固定しません。そのため、モーダル内をスクロールしようとすると、背景もスクロールされる問題が発生します。

#### **解決策**
`useEffect` を利用して、モーダルが開いている間は `body` の `overflow` を `hidden` にすることで対応できます。

```tsx
import { useEffect } from "react";

useEffect(() => {
  document.body.style.overflow = isOpen ? "hidden" : "auto";
  return () => {
    document.body.style.overflow = "auto";
  };
}, [isOpen]);
```

## まとめ
本記事では、`react-modal` を利用したハーフモーダルの実装方法について説明しました。

- `react-modal` の基本的な使い方
- `className` や `overlayClassName` を利用したアニメーションの実装
- モーダル特有の注意点（フォント問題・スクロール制御）

ハーフモーダルは、スマホアプリのようなUIを作成する際に役立つので、ぜひ活用してください！