cloudflare docsのレスポンシブデザイン、Next.jsでざっくり再現に挑戦

### レスポンシブデザインの実装を知る機会に
先日cloudflareのQueueについて、調べものをするためにdocsを眺めていました。
ふと、文字サイズが小さい？と感じて倍率を上げると、画面の構造が変わったので、
（あ、レスポンシブ対応だ！）

https://n-works.link/blog/webdesign/responsive-web-design

倍率を変更したり、developerツールで画面幅を狭くしてみたり、してみました。PC・タブレット・スマホでそれぞれレイアウトが異なる形になっていることを確認しました。

:::note 

Queueに関する学習に進もうと思いましたが、この発見を機に、レスポンシブデザインを実現するための実装を理解してみよう！となりました。
最近の開発では割と必要な要素という話も聞いていたので、やってみてプラスになることが多い気もしました。
:::
### cloudflare docs
今回、レスポンシブデザインを確認していたcloudflare docsです。

https://developers.cloudflare.com/queues/get-started/

ページにアクセスして、倍率を変えたり、画面幅を変えてみるとわかりますが、
レイアウトは、以下のようになっていました。
ここで確認したレスポンシブデザインを実現できるように実装を進めていきます。

##### 倍率100%で表示したとき（画面幅で言えば、PC）
- 画面左側：他ページの内容を含めた目次を表示
- 画面中央：本ページの内容を表示
- 画面右側：本ページに関するアウトライン？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/3a8cd13d-41c6-721b-000d-d22829e68acc.png)

##### 倍率150%にしたとき（画面幅で言えば、タブレット）
- 画面左側：他ページの内容を含めた目次を表示（変わらず）
- 画面中央：本ページの内容を表示（変わらず）
- 画面右側：非表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/992de26f-c7c6-52ab-f87a-1445931b8aa4.png)
##### 倍率200%にしたとき（画面幅で言えば、スマホ）
- 画面左側：非表示となり、画面上部に、ヘッダーのようなものが表示。画面右端にある3本線のアイコンをクリックすると、サイドバーが表示される「ハンバーガーメニュー」と呼ばれるものですね。（水色線の枠内）
- 画面中央：本ページの内容を表示（ピンク色線の枠内）
- 画面右側：非表示
画面上部に表示にされた部分は少し浮いたようなデザインになっています。パンくずリストなどを表示しているピンク線の枠内より色が少しだけ明るくなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/7e8054be-bc00-8136-89f2-1947c88612fc.png)
アイコンクリックすると、サイドバーを表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/09032e50-110c-fe49-d4a9-bbd06c0cc2bb.png)
##### サイドバーの機能
Toggle機能があります。
1枚目（左側）は、`Guide`ページを開き、`Examples`の左にある矢印アイコンにマウスカーソルがある状態です。クリックすると、2枚目（右側）の画像の状態になります。
2枚目の状態から`Learning`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/11cb182d-2d67-c3cc-7f6e-bf1e945e7e59.png)
3枚目(左側)の状態ですが、ページを開いたとき、トグルがある項目であれば、展開した状態で表示されます。
3枚目の状態から`Example`ページに移動すると、4枚目(右側)の状態です。先ほどまで開いていた`Learning`のToggleは閉じた状態に戻ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/8bdc7608-6597-e1fe-7b60-6b9ea41c398c.png)


# 実装開始
`Nextjs`と`tailwindcss`で構築しました。
`Nextjs`はちょこちょこ使ってきましたが、`tailwindcss`は基礎はわかってきましたが、本格的な実装が（今回のレスポンシブ対応含めて）あるか？と聞かれるとと怪しい部分もあったので、ちょうどいい機会となりました。
終わってから思ったことを一つここで記載しますが、デザインに関する細かい部分まで対応するのはそれなりに大変だなあと感じました。

### 土台作りをchatGPTで
:::note 

0から作り上げることも重要ですが、
せっかくなので、ある程度スタートがきりやすい形にしたいと思いました。そこで最初はchatGPTに要件を伝えてみて、その回答をベースに実装を始めてみることにしました。

:::

```
reactとtailwindcssを用いて、レスポンシブ対応の画面を作成したいです。
実装を教えてください。
・画面幅が大きい場合（PC）：
画面左側にメニューを表示、
画面中央にページコンテンツ、
画面右側に画面中央に表示しているページコンテンツのアウトラインを表示

・画面幅が少し狭くなったとき（タブレット）：
画面左側にメニューを表示、
画面中央にページコンテンツ、
画面右側に表示していた、画面中央に表示しているページコンテンツのアウトラインは非表示にする

・画面幅がさらに狭くなったとき（スマホ）：
画面左側にメニューを表示していた、メニューを非表示にして、メニューの内容はハンバーガーメニューの形で表示できるようにレイアウトを修正する
画面中央にページコンテンツ、
画面右側の内容は引き続き非表示とする。
```
**動くのかわからないですけども**、それっぽいは回答はきました。
要件を伝えるだけで、chatGPTはある程度完成された実装を提供してくれるから、本当に助かりますね。これまでは公式ドキュメントとかを見ながら格闘していたと思うので・・・

```react
import React from 'react';

function App() {
  const [showMenu, setShowMenu] = React.useState(false);

  // メニューの表示/非表示を切り替えるハンドラー
  const toggleMenu = () => {
    setShowMenu(!showMenu);
  };

  return (
    <div className="flex">
      {/* 画面幅が大きい場合のメニュー */}
      <div className="w-1/6 bg-gray-200">
        <div className="p-4">メニュー</div>
      </div>

      {/* 画面中央のコンテンツ */}
      <div className="w-4/6 bg-gray-100">
        {/* 画面幅が大きい場合とタブレットの場合はアウトラインを表示 */}
        {(window.innerWidth > 768 || window.innerWidth < 1024) && (
          <div className="p-4">ページコンテンツのアウトライン</div>
        )}
      </div>

      {/* 画面幅が大きい場合のアウトライン */}
      {window.innerWidth > 768 && (
        <div className="w-1/6 bg-gray-200">
          <div className="p-4">ページコンテンツのアウトライン</div>
        </div>
      )}

      {/* 画面幅が狭い場合のハンバーガーメニュー */}
      {window.innerWidth <= 768 && (
        <div className="fixed top-0 right-0 p-4">
          <button
            className="text-gray-600 hover:text-gray-900"
            onClick={toggleMenu}
          >
            <svg
              className="w-6 h-6"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M4 6h16M4 12h16M4 18h16"
              />
            </svg>
          </button>
          {/* メニューの表示 */}
          {showMenu && (
            <div className="absolute top-0 right-0 bg-gray-200 w-1/2 h-screen">
              <div className="p-4">メニュー</div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

export default App;
```

### chatGPTに質問
各機能実装のために必要なコードを教えてもらうために、適宜chatGPTに質問を投げました。
- レスポンシブ対応で画面幅がsmの場合に、画面最上部に、画面幅いっぱいにヘッダー要素を表示する方法
- 画面幅に応じて、特定のコンテンツを非表示にし、他の表示コンテンツの幅に充てる方法
- サイドバーの表示制御の実装
- 画面幅を検知する方法
- svg要素に対するスタイル適用方法
- トグルの開閉に関する実装
などなど

### 完成したレスポンシブデザイン
**メインの目的だった部分が完成して何よりでした！実際に作成したページの画像を見ていただこうと思います。**
Vercel上にデプロイしているので、実際の挙動を確かめたい方は、以下リンクから確かめてみてください。

https://cloudflaredocs-layout-challenge.vercel.app/

※githubのリポジトリを最後に添付しているので、詳細はそちらで見ていただきたいと思います。

:::note warn

まずは動くものを、という気持ちで実装したので、保守性▲のコードもある認識です。こんな風に実装すると機能するんだね～～くらいの感覚でお願いします！
:::

**倍率100%のとき**
レイアウトが画面左側、中央、右側と3部構成になってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/facbae75-2fc9-6041-ecf2-0f54a6d87c38.png)

**倍率150%のとき**
レイアウトが画面左側、中央、と2部構成に代わり、中央のコンテンツ幅が広くなってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/d007e8b5-4bd2-e315-6762-8f4054e83892.png)

**倍率200%のとき**
レイアウトが中央、だけになって、画面上部にヘッダーとハンバーガーメニューのアイコンが表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/255cffd7-a82c-df22-2c4b-b68ea81efea0.png)
ハンバーガーメニューをクリックすると、以下のように`倍率100%、150%のとき`に表示されていた、`overview`以下の内容が表示されます。
※アイコンが入れ替わるとき、cloudflareのdocsでは、アニメーションが入りますが、現段階ではそこまではできてないです！（今後やるところ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/fe2ac835-9c80-16b6-2cae-c9da13e3f688.png)
##### サイドバーの表示制御に関して
- 初回
画面幅が一定より狭くなったら、再度が表示できるためのアイコンを表示
初回はまだサイドバーを表示しない状態
- 2回目以降
画面幅が一定より狭くなったら、直前のサイドバーを開いた状態か判定
開いていた：サイドバー表示してアイコンは×
開いてなかった：サイドバー非表示してアイコンは三本線
実装では、画面幅が変わる度に、画面幅を検知するような構造にしています。
一定の幅より小さくなった場合は、表示形式を変更する仕組みになっています。

実装について
**`window.innerWidth >= 640`といった書き方はよくないと思うので、設定ファイルなどでの定義に改善ができそうです。**
```react
    useEffect(() => {
        const handleResize = () => {
            // 640pxはTailwind CSSのsmブレークポイントに対応
            if (window.innerWidth >= 640) {
                setShowSidebar(false);
            } else {
                if (previousShowSidebarState) {
                    setShowSidebar(true);
                }
            }
            // アイコン表示制御
            if (window.innerWidth >= 200) {
                setShowIcon(true)
            } else {
                setShowIcon(false)
            }
        };
        window.addEventListener('resize', handleResize);

        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, [previousShowSidebarState])
```

### Toggleやパンくずリストの実装
タイトルの通り`レスポンシブデザイン`をメインに実装を始めましたが、それだけにはとどまらないことにしました。
Toggleの実装やパンくずリストの実装も進めてみました。Toggleの状態管理となると、`useState`は登場するものですよね。
メモみたいな書き方になってますが、**実装しているとき最中のメモ書きでして、リアリティがあっていいかも！ということでそのまま記載してます。**

##### Toggleの機能
cloudflareの公式サイトの挙動の部分でも記載した内容と重なりますが、以下の挙動になるようにします。
このあたりは、useStateを最小限にできたような気がします。
- あるページを選択したとき、他のページのトグルが開いている場合は、すべて閉じた状態にする
- トグルがある項目にアクセスしたときは、その項目のトグルを開いた状態にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/046c4bc3-ca4e-4b3a-b5b1-8eefb1759c80.png)

実装について
`useEffect`でコンポーネントを呼び出したときや、`toggleOpen`でボタンクリックしたときに、`useState`で定義した変数の制御を行います。
**現在の実装ではパスが追加されるたびに、if文を増やすことになるので、改善したいポイントです。**
```react
interface IProps {
    pathname: string
}

export default function Toggle(props: IProps) {
    const [isOpenManage, setIsOpenManage] = useState({ overview: false, examples: false, learning: false });
    const [currentOpenPage, setCurrentOpenPage] = useState<string>(props.pathname);

    // Path更新時
    useEffect(() => {
        if (props.pathname === '/queues') {
            setIsOpenManage({ ...isOpenManage, overview: !isOpenManage.overview })
        }
        if (props.pathname === '/queues/examples') {
            setIsOpenManage({ ...isOpenManage, examples: !isOpenManage.examples })
        }
        if (props.pathname === '/queues/learning') {
            setIsOpenManage({ ...isOpenManage, learning: !isOpenManage.learning })
        }
    }, [])

    // 矢印アイコンクリック
    const toggleOpen = (value: string) => {
        if (value === '/queues/examples') {
            setIsOpenManage({ ...isOpenManage, examples: !isOpenManage.examples })
        }
        if (value === '/queues/learning') {
            setIsOpenManage({ ...isOpenManage, learning: !isOpenManage.learning })
        }
    };
}
```

##### パンくずリスト
パスの階層によって表示内容が変わるので、そのあたりをどう実装しようか、というのが悩んだところでした。
以下のようなデザインにしてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/6fd218dc-7400-16ee-7b4c-1ebb7c1e0470.png)


##### cloudflare docsやQueueのアイコン情報
今回作成したものに、公式のdocsでも使用されているアイコンをお借りしました。
cloudflare docsをDeveloperツールで開いて、該当する要素のコードをコピーしする形で実現しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/5b763291-808f-211d-0ecd-8303eaea0777.png)


### 実装後
##### Repositoryを見つけた？
実装している途中で、実際のdocsのコードかな？と思われるリポジトリを見つけました。
readMeの手順でエラーがでてしまったので、最後まで確認はできませんでした・・・
コードを見た限り、vue.jsを使っていたり、各ページのコンテンツとしては、markdownを使用してたり、といった形になってそう、という状況です。

https://github.com/cloudflare/cloudflare-docs

###### 参考資料
レイアウト関係

https://saruwakakun.com/html-css/basic/display

https://zenn.dev/junki555/articles/45614f64695b77a2ae78

複数のstate管理

https://www.kamo-it.org/blog/26/

表示しているページのPath取得

https://nextjs.org/docs/app/api-reference/functions/use-pathname

---


### リポジトリ情報

https://github.com/eno-conan/cloudflaredocs-layout-challenge-sample

### この先
今回一気に作成をしてみて、ある程度モノができた段階で、この記事を作成しました。
一気にやったので、気になることもその分まとめて出てきました。以下がそのうちのいくつかです。
別の機会にはなりますが、それぞれ検証するタイミングは設けられたらと思います。

**仮に規模が拡大したら？**
途中でも記載しましたが、今のコードでは満足いくまのではないです。
例えばですが、以下の対応をする場合に、修正漏れやリグレッション（デグレ？）が発生しにくいような構造、また実装量を少なくできる形を目指したいです。
- 既存のPathを変えるとき
- 新規でPathを追加するとき

**細かいデザインの理解**
今回はレスポンシブデザインをメインでやっていましたが、色々付け加える中で思うことはありました。
公式サイトはいい感じになっているけど、俺の実装した画面なんかダサくない？
もっと滑らかな感じ？（ヌルヌルっぽい動き）とかどうやって実現するんだ？など疑問がいくつもありました。

**MarkDownとReactの組み合わせ**
公式ドキュメントを見ると、画面表示にMarkdownファイルを使ってそうな感じがしています。
また実際にMarkdownとReactを用いた記事も上がっているので、取り組んでみようかなと思います。

https://zenn.dev/hayato94087/articles/649e8d817165d8