1. 記事一覧 >
  2. ブログ記事
category logo

MUI X Data GridのセルにReactカラーピッカー(react-color)で色付け

(更新) (公開)

はじめに

Material UI(MUI)v5 のデータグリッド(x-data-grid)のセルにカラーピッカー(react-color)で選択した色を付けるというのを実装しました。

データグリッドのセルにカラーピッカー(react-color)で選択した色を付ける 動画

地味にハマったところがありましたので、記事にしました。


完成したソースコードは、この記事の一番下にあります。


データグリッド(x-data-grid)については、主題ではありませんので、簡素な実装で進めます。

【検証環境】

Windows 10 Pro x64

 Visual Studio Code 1.69.0

Raspberry Pi Desktop OS(Linux raspberry 4.19.0-13-amd64)

 node 14.16.0

 npm 6.14.11

  react 18.2.0

  mui/material 5.8.7

  mui/mui/x-data-grid 5.13.0

  react-color 2.19.3


MUI(Material-UI)v5

環境により、状況が異なる可能性があるため、大前提として、別記事
Material UI(MUI) Recharts WebSocket FastAPI でリアルタイムグラフ描画
の続きとします。


MUI(Material-UI)v5 を導入して、ダッシュボードが実装されているものとします。なお、ダッシュボードは、ただ単に既存のテンプレートを導入しただけです。

MUI(Material-UI)v5 ダッシュボード


x-data-grid 導入

$ npm install @mui/x-data-grid

<GridColorPick /> コンポーネントを作成します。シンプルなグリッドを表示するものとします。

src/GridColorPick.tsx
import React from "react";
import {
  DataGrid,
  GridColDef,
  GridToolbarContainer,
  GridToolbarExport,
  jaJP,
} from "@mui/x-data-grid";

const columns: GridColDef[] = [
  { field: "id", headerName: "ID", width: 70 },
  { field: "firstName", headerName: "First name", width: 130 },
  { field: "lastName", headerName: "Last name", width: 130 },
  {
    field: "age",
    headerName: "Age",
    type: "number",
    width: 90,
  },
];

const rows = [
  { id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
  { id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
  { id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
  { id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
  { id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null },
];

const CustomToolbar = () => {
  return (
    <GridToolbarContainer>
      <GridToolbarExport />
    </GridToolbarContainer>
  );
};
const GridColorPick: React.FC = () => {
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
        <DataGrid
          onCellClick={(_params, event) => {}}
          rows={rows}
          columns={columns}
          pageSize={5}
          rowsPerPageOptions={[5]}
          components={{
            Toolbar: CustomToolbar,
          }}
          localeText={jaJP.components.MuiDataGrid.defaultProps.localeText}
        />
      </div>
    </>
  );
};

export default GridColorPick;

src/dashboard/Dashboard.tsx を大きく変えて、以下の内容とします。(ほとんど省略しています。 <Container>...</Container> 内を <GridColorPick /> だけにしています。import 削除も必要です。)

Dashboard.tsx が突然出てきましたが、別記事を見てください。

src/dashboard/Dashboard.tsx
・・・
import GridColorPick from "../GridColorPick";
・・・
          <Toolbar />
          <Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
            <GridColorPick />
          </Container>
        </Box>
・・・

確認します。

$ npm start

シンプルなグリッドを表示 確認


OK!


react-color 導入

$ npm install react-color
$ npm install --save-dev @types/react-color

まずは、グリッドのセルをクリックしたら、useState の setShowPicker(true);showPickertrue になることにより、カラーピッカーを表示するというシンプルな実装です。(一度表示したら、カラーピッカーは、消せません。)
カラーピッカーの種類ですが、以下のようにいろいろありますが、Sketch を選択します。Sketch を選択したから、<SketchPicker /> コンポーネントです。

カラーピッカー種類1

カラーピッカー種類2


カラーピッカー基本形 動画

src/GridColorPick.tsx
import React, { useState } from "react";
・・・
import SketchPicker from "react-color/lib/components/sketch/Sketch";
・・・
const GridColorPick: React.FC = () => {
  const [showPicker, setShowPicker] = useState(false);
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
        <DataGrid
          onCellClick={(_params, event) => {
            setShowPicker(true);
          }}
          rows={rows}
          columns={columns}
          pageSize={5}
          rowsPerPageOptions={[5]}
          components={{
            Toolbar: CustomToolbar,
          }}
          localeText={jaJP.components.MuiDataGrid.defaultProps.localeText}
        />
      </div>
      {showPicker && <SketchPicker />}
    </>
  );
};

export default GridColorPick;

彩度明度ブロックが動かない問題

いきなり問題が発生して、彩度明度ブロックが動かないことに気付きました。普通、マウスでドラッグすると、動きますが、最初の位置から動きません。

彩度明度ブロック:何て呼べばよいか分かりませんでした。以下の部分のことです。

彩度明度ブロック


https://github.com/casesandberg/react-color/issues/674 に答えがあるのですが、


<SketchPicker />onChange に以下を実装すると動きました。

const [color, setColor] = useState('#ffffff');
const handleChange = (value: ColorResult) => {
  setColor(value.hex);
};

彩度明度ブロック 動画

src/GridColorPick.tsx
・・・
import { ColorResult } from "react-color";
・・・
const GridColorPick: React.FC = () => {
  const [showPicker, setShowPicker] = useState(false);
  const [color, setColor] = useState("#ffffff");
  const handleChange = (value: ColorResult) => {
    setColor(value.hex);
  };
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
・・・
      </div>
      {showPicker && <SketchPicker color={color} onChange={handleChange} />}
    </>
  );
};

export default GridColorPick;

外側をクリックしたら閉じる

カラーピッカーの外側をクリックしたら閉じるようにします。
カラーピッカーを囲む div タグに useRef の参照を持っておいて、その div タグの外側がクリックされたら、カラーピッカーを閉じるようにしています。(div タグも一緒にアンマウント。)


最初このような実装をして、ハマりました。

const insideRef = useRef<HTMLDivElement>(null);
useEffect(() => {
  const hundleClickOutside = (e: MouseEvent) => {
    const element = insideRef.current;
    if (!showPicker || element?.contains(e.target as Node)) return;
    setShowPicker(false);
  };
  window.addEventListener('click', hundleClickOutside);
  return () => {
    window.removeEventListener('click', hundleClickOutside);
  };
}, [showPicker, insideRef]);

カラーピッカーを開くときにグリッドのセルをクリックしますが、その時に外側クリックの判定も発生して、以下のような動きになっていました。


グリッドのセルをクリック

同時に外側クリックの判定

グリッドのクリックイベントでカラーピッカー開く

window のクリックイベントで hundleClickOutside

(外側がクリックされたと判定しているので)カラーピッカー閉じる

開かない!


結局、
window.addEventListener("click", hundleClickOutside);
window.removeEventListener("click", hundleClickOutside);
とカラーピッカーを開くときと同じイベント click イベント で hundleClickOutside を呼び出しているのが原因でした。


window.addEventListener("mousedown", hundleClickOutside);
window.removeEventListener("mousedown", hundleClickOutside);
とすると、以下の順番になり、うまくいきました。


グリッドのセルをクリック

外側 mousedown の判定

hundleClickOutside

(外側が mousedown されたと判定しているので)カラーピッカー閉じる(この時点では、元々表示されていない。)

グリッドのクリックイベントでカラーピッカー開く

開いたまま!


カラーピッカー開く閉じる 動画

src/GridColorPick.tsx
import React, { useEffect, useRef, useState } from "react";
・・・
  const insideRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const hundleClickOutside = (e: MouseEvent) => {
      const element = insideRef.current;
      if (!showPicker || element?.contains(e.target as Node)) return;
      setShowPicker(false);
    };
    window.addEventListener("mousedown", hundleClickOutside);
    return () => {
      window.removeEventListener("mousedown", hundleClickOutside);
    };
  }, [showPicker, insideRef]);
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
・・・
      </div>
      {showPicker && (
        <div style={{ position: "absolute" }} ref={insideRef}>
          <SketchPicker color={color} onChange={handleChange} />
        </div>
      )}
    </>
  );
};

export default GridColorPick;

開く位置調整

開く閉じるはできたのですが、カラーピッカーを開く位置が固定になっています。クリックしたセルのすぐそばにカラーピッカーを表示させたいです。


onCellClickgetBoundingClientRect により、クリック位置を特定 →div のスタイルに top,left を設定
としました。

src/GridColorPick.tsx
・・・
  const [pickerPos, setPickerPos] = useState({});
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
        <DataGrid
          onCellClick={(_params, event) => {
            const rect = event.currentTarget.getBoundingClientRect();
            setPickerPos({
              position: "absolute",
              top: rect.top + rect.height + 5,
              left: rect.left,
            });
            setShowPicker(true);
          }}
・・・
      </div>
      {showPicker && (
        <div style={pickerPos} ref={insideRef}>
          <SketchPicker color={color} onChange={handleChange} />
        </div>
      )}
    </>
  );
};

export default GridColorPick;

カラーピッカー クリックした位置に開く 動画


セルに色を塗る

現時点では、色を選択しても何も起きません。
グリッドのセルに色を塗るようにします。


グリッドのセルクリック

クリックされたセルの HTMLElement を useState の 変数 cell に格納(setCell(el)

クリックされたセルの色をカラーピッカーが開いたときの初期値とする(setColor(event.currentTarget.style.backgroundColor)

カラーピッカーを開く

カラーピッカーの色を選択

handleChangeComplete

クリックされたセルの色をセット(cell.style.backgroundColor = value.hex


としています。

データグリッドのセルにカラーピッカー(react-color)で選択した色を付ける 動画


完成!


今回の全ソースコードです。

src/GridColorPick.tsx
import React, { useEffect, useRef, useState } from "react";
import {
  DataGrid,
  GridColDef,
  GridToolbarContainer,
  GridToolbarExport,
  jaJP,
} from "@mui/x-data-grid";
import SketchPicker from "react-color/lib/components/sketch/Sketch";
import { ColorResult } from "react-color";

const columns: GridColDef[] = [
  { field: "id", headerName: "ID", width: 70 },
  { field: "firstName", headerName: "First name", width: 130 },
  { field: "lastName", headerName: "Last name", width: 130 },
  {
    field: "age",
    headerName: "Age",
    type: "number",
    width: 90,
  },
];

const rows = [
  { id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
  { id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
  { id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
  { id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
  { id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null },
];

const CustomToolbar = () => {
  return (
    <GridToolbarContainer>
      <GridToolbarExport />
    </GridToolbarContainer>
  );
};
const GridColorPick: React.FC = () => {
  const [showPicker, setShowPicker] = useState(false);
  const [color, setColor] = useState("#ffffff");
  const handleChange = (value: ColorResult) => {
    setColor(value.hex);
  };

  const insideRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const hundleClickOutside = (e: MouseEvent) => {
      const element = insideRef.current;
      if (!showPicker || element?.contains(e.target as Node)) return;
      setShowPicker(false);
    };
    window.addEventListener("mousedown", hundleClickOutside);
    return () => {
      window.removeEventListener("mousedown", hundleClickOutside);
    };
  }, [showPicker, insideRef]);

  const [pickerPos, setPickerPos] = useState({});

  const [cell, setCell] = useState<HTMLElement | null>(null);
  const handleChangeComplete = (value: ColorResult) => {
    setColor(value.hex);
    if (cell !== null) cell.style.backgroundColor = value.hex;
  };
  return (
    <>
      <div style={{ height: 420, width: 600, margin: 30 }}>
        <DataGrid
          onCellClick={(_params, event) => {
            const rect = event.currentTarget.getBoundingClientRect();
            setPickerPos({
              position: "absolute",
              top: rect.top + rect.height + 5,
              left: rect.left,
            });
            const el = event.currentTarget;
            setCell(el);
            setColor(el.style.backgroundColor);
            setShowPicker(true);
          }}
          rows={rows}
          columns={columns}
          pageSize={5}
          rowsPerPageOptions={[5]}
          components={{
            Toolbar: CustomToolbar,
          }}
          localeText={jaJP.components.MuiDataGrid.defaultProps.localeText}
        />
      </div>
      {showPicker && (
        <div style={pickerPos} ref={insideRef}>
          <SketchPicker
            color={color}
            onChange={handleChange}
            onChangeComplete={handleChangeComplete}
          />
        </div>
      )}
    </>
  );
};

export default GridColorPick;
loading...