- 記事一覧 >
- ブログ記事
MUI X Data GridのセルにReactカラーピッカー(react-color)で色付け
はじめに
Material UI(MUI)v5 のデータグリッド(x-data-grid)のセルにカラーピッカー(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 を導入して、ダッシュボードが実装されているものとします。なお、ダッシュボードは、ただ単に既存のテンプレートを導入しただけです。
x-data-grid 導入
$ npm install @mui/x-data-grid
<GridColorPick />
コンポーネントを作成します。シンプルなグリッドを表示するものとします。
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
が突然出てきましたが、別記事を見てください。
・・・
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);
で showPicker
が true
になることにより、カラーピッカーを表示するというシンプルな実装です。(一度表示したら、カラーピッカーは、消せません。)
カラーピッカーの種類ですが、以下のようにいろいろありますが、Sketch を選択します。Sketch を選択したから、<SketchPicker />
コンポーネントです。
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);
};
・・・
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 されたと判定しているので)カラーピッカー閉じる(この時点では、元々表示されていない。)
↓
グリッドのクリックイベントでカラーピッカー開く
↓
開いたまま!
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;
開く位置調整
開く閉じるはできたのですが、カラーピッカーを開く位置が固定になっています。クリックしたセルのすぐそばにカラーピッカーを表示させたいです。
onCellClick
で getBoundingClientRect
により、クリック位置を特定 →div のスタイルに top,left を設定
としました。
・・・
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
)
としています。
完成!
今回の全ソースコードです。
↓
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;
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。