Spanned cells in ReactGrid
You can specify the number of rows or columns a cell spans by using the rowSpan
and colSpan
properties when creating cells for ReactGrid.
Live example
Code
const cellStyles = { header: { backgroundColor: "#55bc71", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: "bold", }, }; const categoryArr = [ { id: 1, range: "1-5", categoryName: "cat1", percentage: 0.5, records: 10 }, { id: 2, range: "6-10", categoryName: "cat2", percentage: 0.1, records: 20 }, { id: 3, range: "11-15", categoryName: "cat2", percentage: 0.1, records: 30 }, { id: 4, range: "16-20", categoryName: "cat3", percentage: 0.4, records: 40 }, { id: 5, range: "21-25", categoryName: "cat3", percentage: 0.4, records: 50 }, { id: 6, range: "26-30", categoryName: "cat3", percentage: 0.4, records: 60 }, ]; const getRows = (people: Category[]): Row[] => [ // header row { rowIndex: 0, height: 40, }, // data rows ...people.map((_, i) => ({ rowIndex: i + 1, height: 40, })), ]; const getColumns = (): Column[] => [ { colIndex: 0, width: 220 }, { colIndex: 1, width: 220 }, { colIndex: 2, width: 220 }, { colIndex: 3, width: 220 }, ]; type UpdateCategoryFn = <T>(id: number, key: string, newValue: T) => void; const generateCells = (categories: Category[], updateCategories: UpdateCategoryFn): Cell[] => { const generateHeaderCells = () => { const titles = ["Range", "Category", "Category %", "Records"]; return titles.map((title, colIndex) => ({ rowIndex: 0, colIndex, Template: NonEditableCell, props: { value: title, style: cellStyles.header, }, })); }; const spannedCellsIdx = [ { rowIndex: 2, colIndex: 1, rowSpan: 2 }, { rowIndex: 2, colIndex: 2, rowSpan: 2 }, { rowIndex: 4, colIndex: 1, rowSpan: 3 }, { rowIndex: 4, colIndex: 2, rowSpan: 3 }, ]; const isCellOverlappingSpan = (rowIndex: number, colIndex: number): boolean => { return spannedCellsIdx.some(({ rowIndex: spanRow, colIndex: spanCol, rowSpan }) => { return colIndex === spanCol && rowIndex > spanRow && rowIndex < spanRow + rowSpan; }); }; const getSpan = (rowIndex: number, colIndex: number) => { const spannedCell = spannedCellsIdx.find((cell) => cell.rowIndex === rowIndex && cell.colIndex === colIndex); if(!spannedCell) return null; return { rowSpan: spannedCell.rowSpan }; }; const generateRowCells = (rowIndex: number, category: Category): Cell[] => { const { id, range, categoryName, percentage, records } = category; return [ { rowIndex, colIndex: 0, Template: TextCell, props: { text: range, onTextChanged: (newText: string) => updateCategories(id, "range", newText), }, }, { rowIndex, colIndex: 1, Template: TextCell, props: { text: categoryName, onTextChanged: (newValue: string) => updateCategories(id, "categoryName", newValue), }, ...getSpan(rowIndex, 1), // Check if this is a spanned cell }, { rowIndex, colIndex: 2, Template: NumberCell, props: { value: percentage, onValueChanged: (newValue: number) => updateCategories(id, "percentage", newValue), format: new Intl.NumberFormat("en-US", { style: "percent", minimumFractionDigits: 0 }), }, ...getSpan(rowIndex, 2), // Check if this is a spanned cell }, { rowIndex, colIndex: 3, Template: NumberCell, props: { value: records, onValueChanged: (newValue: number) => updateCategories(id, "records", newValue), }, }, ].filter((cell) => !isCellOverlappingSpan(cell.rowIndex, cell.colIndex)); // Filter out only overlapping cells }; const headerCells = generateHeaderCells(); const rowCells = categories.flatMap((category, idx) => generateRowCells(idx + 1, category)); return [...headerCells, ...rowCells]; }; const ReactGridExample = () => { const [categories, setCategories] = useState<Category[]>(categoryArr); const updateCategories = (id: number, key: string, newValue) => { setCategories((prevData) => prevData.map((category) => (category.id !== id ? category : { ...category, [key]: newValue })) ); }; const rows = getRows(categories); const columns = getColumns(); const cells = generateCells(categories, updateCategories); return ( <ReactGrid columns={columns} rows={rows} stickyTopRows={1} cells={cells} /> ); }; render(<ReactGridExample />, document.getElementById("root"));
Preview