カスタムレンダリング
セル / ヘッダー / トップ・ボトムバー / エディタ / コンテキストメニューを React で自由に描画する
グリッドの見た目と挙動は、通常の React コンポーネントで差し替えられます。すべて「関数を渡すとコンテキストが届く」スタイルで、状態管理はグリッド側が持ちます。
カスタムセル(renderCell)
column.renderCell にレンダラを渡すと、セル内容を自由に描画できます。コンテキストには value / row のほか、そのセルへ直接書き込める setValue が入っているため、ボタンやトグルなどのインタラクティブなセルも作れます。
{
key: 'status', title: 'ステータス', width: 120,
renderCell: ({ value }) => (
<span className={badgeClass(value)}>{String(value)}</span>
),
},
{
key: 'stock', title: '在庫', width: 160,
renderCell: ({ value, setValue }) => (
<>
<button onClick={() => setValue(Number(value) - 1)}>−</button>
<span>{Number(value)}</span>
<button onClick={() => setValue(Number(value) + 1)}>+</button>
</>
),
}- コンテキストの全フィールドは
CellRenderContext<T>(row/rowIndex/sourceRowIndex/rowKey/colIndex/value/column/isActive/isSelected/isEditing/readOnly/setValue)です。 renderCellは表示の差し替えです。編集(ダブルクリック時のエディタ)は従来どおりeditorが担当し、共存できます(デモのステータス列 = バッジ表示 + select エディタ)。- 表示文字列を変えるだけなら、より軽い
valueFormatterを使ってください。条件付きの色付けだけならcellClassNameが最軽量です。
セルのメモ(cellNote)
column.cellNote が文字列を返したセルの右上に印(三角)を出し、マウスを乗せるとその文字列をツールチップで表示します。FarPoint SPREAD のセルメモ / Excel のメモに当たる機能で、renderCell を使わずに「このセルには補足がある」ことを示せます。プレイグラウンドの cellNote トグルで試せます。
{
key: 'partNo', title: '品番', width: 140,
cellNote: ({ row }) =>
[row.isNew && '今回の取込で追加された構成です。', row.otherUses > 0 && `他 ${row.otherUses} 箇所でも使用`]
.filter(Boolean)
.join('\n'), // \n で改行。空文字なら印は出ない
}- 印の色はトークン
--ssg-note-indicatorで変えられます。入力エラー(validate)と同じセルでは、エラーの小さい赤い三角がメモの三角の上に重なって両方見え、ツールチップは「エラー → 改行 → メモ」の順になります。 - 背景色などは従来どおり
cellClassNameで付けます(メモは印とツールチップだけ)。 - CSV / コピー / エクスポートには含まれません。詳細は API リファレンス を参照してください。
カスタムヘッダー(renderHeader)
column.renderHeader で列ヘッダーのラベル部分を差し替えます(上のデモの「📋 タスク」列)。ソート・メニュー・リサイズなどのヘッダー機能はそのまま維持されます。
{
key: 'name', title: 'タスク', width: 180,
renderHeader: ({ column }) => (
<span>📋 {column.title}</span>
),
}トップバー / ボトムバー(renderTopBar / renderBottomBar)
renderTopBar / renderBottomBar でバーを丸ごと React で差し替えられます。渡ってくる SpreadsheetGridSlotContext に、グローバルフィルターの読み書き(globalFilterText / setGlobalFilterText)、rows / filteredRows、選択状態、そして整形済みの派生 summary である derivedSummary が入っているため、ヘルパーを import せずに実用的なバーが書けます。
<SpreadsheetGrid
renderTopBar={(ctx) => (
<div className="my-toolbar">
<input
value={ctx.globalFilterText}
onChange={(e) => ctx.setGlobalFilterText(e.target.value)}
placeholder="検索"
/>
<span>{ctx.filteredRows.length} / {ctx.rows.length} 件</span>
</div>
)}
renderBottomBar={(ctx) => (
<div className="my-statusbar">
<span>{ctx.derivedSummary.rowSummaryText}</span>
<span>{ctx.derivedSummary.selectionStatsText}</span>
</div>
)}
...
/>derivedSummaryには行/列/フィルター/ソートの summary 文字列、選択セルの件数などが入っています(SpreadsheetGridDerivedSummary)。- 2 セル以上を選んでいるときは
selectionAggregatesに選択範囲の集計(平均 / データの個数 / 合計 / 最小 / 最大)が入っています(GridSelectionAggregates)。既定ボトムバーはこのうち平均 / データの個数 / 合計を右側(Active / Selection の隣の空き)に出します(showSelectionAggregates={false}で非表示。詳細は API リファレンス)。 ctxやrows/filteredRowsを子コンポーネントの props にそのまま渡す(<MyBar ctx={ctx} />/<MyBar rows={ctx.filteredRows} />)のは避け、使う値だけを渡してください(件数ならctx.filteredRows.lengthやderivedSummary)。React 19.2 以降の開発ビルドは props の差分を取るために大きな配列を走査するため、100 万行規模ではソート 1 回に数十秒かかることがあります(本番ビルドには影響しません)。上の例のようにバーの中で直接ctxを読む分には問題ありません。- 丸ごと差し替えずに調整したい場合は表示制御 props を使います:
showTopBar/showBottomBar(マスタースイッチ)、既定バーの部品単位ではshowTopBarSummary/showTopBarFilter/showFilterChipBarなど。一覧は API リファレンス を参照してください。
カスタムエディタ(type: 'custom')
組み込みエディタ(text / number / select / date / checkbox)で足りないときは、編集 UI 自体を描画できます。ctx.commit(value) で確定、ctx.cancel() でキャンセルです。非 string を commit するとパーサをバイパスしてドメイン値がそのまま書き込まれます。
{
key: 'rating', title: '評価', width: 180, editable: true,
renderCell: ({ value }) => <Stars n={Number(value)} />,
editor: {
type: 'custom',
render: (ctx) => (
<StarPicker
value={Number(ctx.value)}
onPick={(n) => ctx.commit(n)} // 数値のまま書き込み
onCancel={() => ctx.cancel()}
/>
),
},
}- フォーカス管理とキーバインドは利用側の責務です(
ctxにはrow/initialText(印字キー開始時の 1 文字)なども入ります)。
コンテキストメニュー(右クリック)
enableContextMenu + getContextMenuItems で、セル / 行番号ガターの右クリックメニューを定義できます。ライブラリは既定項目を一切持たない完全カスタム設計で、空配列を返すとブラウザ標準メニューにフォールスルーします。
セルまたは行番号を右クリックしてください
<SpreadsheetGrid
enableContextMenu
getContextMenuItems={({ target, isTargetSelected }) => {
if (target.type !== 'cell' && target.type !== 'rowHeader') return [];
return [
{ kind: 'label', label: '行操作' },
{ label: '行を複製', onSelect: () => duplicate(target.row) },
{ kind: 'separator' },
{ label: '行を削除', danger: true, onSelect: () => remove(target.row) },
];
}}
...
/>項目は判別共用体(action / label / separator / custom)で、custom は自由描画のエスケープハッチです。詳細は API リファレンス を参照してください。
どの手段を選ぶか
| やりたいこと | 手段 |
|---|---|
| 表示文字列の整形(3 桁区切りなど) | valueFormatter |
| 条件付きの色・背景 | cellClassName + CSS |
| セルに補足のメモ(右上の印 + ツールチップ) | cellNote |
| セルに独自 UI(バッジ / バー / ボタン) | renderCell |
| 編集 UI そのものを差し替え | editor: { type: 'custom' } |
| ヘッダーラベルの装飾 | renderHeader |
| バーの部品の表示切替 | showTopBar* / showBottomBar* 系 props |
| バーを丸ごと自作 | renderTopBar / renderBottomBar |
| 右クリックメニュー | getContextMenuItems |
| 色・寸法トークンの一括調整 | テーマとスタイリング |