Reactで行列を固定した表をgridレイアウトで作ろう

## はじめに
Reactでフロントエンドを開発していると、カレンダーなどテーブル表記を行いたい場面はたくさんあるかなと思います。
Excelのように、行列の一部を固定して、中身だけスクロールさせたいなんていうデザイン要望にぶち当たることはままあるのではないでしょうか？
今回、コード例を載せながら、scssでどんなstyleを当てれば、それが実装できるのか？を備忘録として残しておきたいと思います。

完成形はこんな感じです。

:::note warn
Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください
:::

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="xbxqRvG" data-pen-title="gridLayout-sample5" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/xbxqRvG">
  gridLayout-sample5</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

## スタイルなしの状態を作成
今回のサンプルは適当にボタンやテキスト、チェックボックスなどが存在する表を作成してみようと思います。
まずは、単純に要素を並べたものがこちらです。
(便宜上一部のクラス名だけつけちゃってます。)

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="VYwpjWz" data-pen-title="gridLayout-sample" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/VYwpjWz">
  gridLayout-sample</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

## ヘッダー部分を作る
まずは、ヘッダー部分にスタイルを当てていこうと思います。

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="mydWEjK" data-pen-title="gridLayout-sample2" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/mydWEjK">
  gridLayout-sample2</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

表示領域内でx, y方向にスクロールさせたいので、親要素に`overflow-x`, `overflow-y`に`auto`を入れています。
また、ヘッダーを`sticky`にして固定したいので`position: relative;`と`position: sticky;`を使用しています。ヘッダーの背景は、`width`を指定しないとスクロールの範囲外には色がつかなくなってしまうので計算した値を入れています。
(コード詳細は、codepenの中身を参照してください)

## コンテンツ内のレイアウトを調整する
ヘッダーの配置は決まったので次はコンテンツのそれぞれの配置を調整します。

:::note warn
Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください
:::

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="yyLMJWe" data-pen-title="gridLayout-sample3" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/yyLMJWe">
  gridLayout-sample3</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

以下のように、`contentRow`クラスに対してスタイルを当てていきます。

```diff_scss
.modal {
  display: flex;
  flex-direction: column;
  background-color: lightgray;
  width: 800px;
  height: 600px;
  margin: auto;
  overflow-x: auto;
  overflow-y: auto;
  position: relative;

  .header {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    text-align: center;
    position: sticky;
    top: 0;
    left: 0;
    width: 1120px;
    background-color: #d1d7e0;
  }

+ .contentRow {
+   display: grid;
+   grid-template-columns: 120px 200px repeat(5, 160px);
+   align-items: center;
+   border-bottom: 1px solid black;
+   width: 1120px;
+ }
}
```

checkboxのところにスクロールしてみてみると、縦の位置がしっかり揃っていることがわかると思います。

## 左の２列も固定化する
ヘッダー部分は`sticky`の状態にすることができていますが、ボタンと名前の列も固定化してチェックボックスがある部分だけをスクロールするように変更したいと思います。

これを実装するには、実装側のclassNameを追加する必要があります。


```diff_tsx
const DEFAULT_USER_NAME = [...Array(30)].map(
  (_, index) => `sample name${index + 1}`
);

const ChildComponent = () => {
  return (
    <div className="modal">
      <div className="gridLayoutWrapper">
        <div className="header">
+         <div className={`stickyColumn deleteRow`}></div>
+         <div className={`stickyColumn name`}>名前</div>
          {[...Array(5)].map((_, index) => (
            <div>{`someColumn${index + 1}`}</div>
          ))}
        </div>

        {DEFAULT_USER_NAME.map((name, index) => (
          <div className="contentRow" key={`row-${index + 1}`}>
            <button
+             className={`stickyColumn deleteRow`}
              key={`deleteRow-${index + 1}`}
            >
              deleteRow
            </button>
+           <div className={`stickyColumn name`} key={`tableContents-${index}`}>
              {name}
            </div>
            {[...Array(5)].map((_, index) => (
              <input type="checkbox" key={`column${index + 1}`} />
            ))}
          </div>
        ))}
      </div>
    </div>
  );
};
```

`stickyColumn`, `deleteRow`, `name`のクラスを追加しました。

scssの方は、
```diff_scss
.modal {
  display: flex;
  flex-direction: column;
  background-color: lightgray;
  width: 800px;
  height: 600px;
  margin: auto;
  overflow-x: auto;
  overflow-y: auto;
  position: relative;

  .header {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    text-align: center;
    position: sticky;
    top: 0;
    left: 0;
    width: 1120px;
+   background-color: #d1d7e0;
+   z-index: 30;

+   .stickyColumn {
+     position: sticky;
+     z-index: 40;
+     background-color: #d1d7e0;
+   }

+   .deleteRow {
+     left: 0;
+     width: 120px;
+   }

+   .name {
+     left: 120px;
+     width: 200px;
+   }
  }

  .contentRow {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    align-items: center;
    border-bottom: 1px solid black;
    width: 1120px;

+   .stickyColumn {
+     position: sticky;
+     background-color: #d1d7e0;
+     z-index: 20;
+   }

+   .deleteRow {
+     left: 0;
+     width: 120px;
+   }

+   .name {
+     left: 120px;
+     width: 200px;
+   }
  }
}
```
これらを追加し、左のに列を固定化するのと同時に、
`z-index`と`background-color`を追加することで裏に入り込むような挙動になるように調整しました。

ここまでのコードの動きはこんな感じです。

:::note warn
Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください
:::


<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="VYwpKwd" data-pen-title="gridLayout-sample4" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/VYwpKwd">
  gridLayout-sample4</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>


動きは完成しましたが、もう少しだけ調整をしたいと思います。

## 見た目微調整
全体的に行がピッチピチなので、大人の余裕の如く、隙間を設けてあげようと思います。
あとは好みの色にしたり、遊び心で線を足してみたりしてます。
この辺りはお遊びなのでCodePenのStyle参照でお願いします。

完成形は以下の通りです(再掲)。

:::note warn
Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください
:::

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="xbxqRvG" data-pen-title="gridLayout-sample5" data-user="Yoshi2885" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yoshi2885/pen/xbxqRvG">
  gridLayout-sample5</a> by Yoshi2885 (<a href="https://codepen.io/Yoshi2885">@Yoshi2885</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

## 最後に
`display: flex;`はよく使いますが、
`display: grid;`をちゃんと使ってみたのは初めてでした。
CodePenの環境ではありますが、いろいろ試してみてこんなレイアウトを結構簡単に整えられるんだな〜というのを知れていい学びでした。
Headerを固定化するのはよくやったことがありますが、
列を固定化するのに`className`をそれぞれに当てて…とやることで実現できるんですね💡

もっと賢い記述方法もきっとあると思いますが、
それはおいおい勉強することにしようと思います。