Styling
Methods
There are three primary ways of applying custom styles to the child components of the react-bootstrap-data-grid table:
Bootstrap Variable Overrides
Because react-bootstrap-data-grid makes heavy use of Bootstrap CSS classes and variables, it will also obey
customization of Bootstrap via variable overrides. For example, setting a new primary theme color with an SCSS
variable will cause buttons of the primary variant in react-bootstrap-data-grid to be the new color as well.
Additionally, setting the $table-cell-vertical-align variable can be used to adjust the vertical alignment of table
cells from the default of top.
Specific Members of Props in react-bootstrap-data-grid
A small number of options that are set via properties of component props are implemented via CSS classes or HTML style attributes.
Pagination Component
When pagination is enabled for the grid, the pageSelectorJustifyContent and componentSize properties of the PaginationModel interface set the component size and flexbox justify-content settings of the Bootstrap pagination component, respectively.
Column Widths
Column widths specified in the ColDef interface are implemented via HTML style attributes.
Table Display Mode
The displayMode property of TableProps sets the CSS
display property on the main table
element via Boostrap utility classes.
Injection of Custom Classes via the StyleModel Prop
The StyleModel prop contains a fairly extensive of list of properties that correspond to HTML elements in various parts of react-bootstrap-data-grid. These properties either contain arrays of strings that are CSS classes or functions that return an array of such strings.
Examples
Colors and Alignment Example
Code
"use client";
import Table, {
ColDef,
RowDef,
StyleModel,
TableStyleModel,
} from "@absreim/react-bootstrap-data-grid/table";
import { FC, useMemo } from "react";
const cols: ColDef[] = [
{
type: "string",
name: "variant",
label: "Variant",
},
{
type: "string",
name: "color",
label: "Color",
},
{
type: "string",
name: "alignment",
label: "Alignment",
},
{
type: "string",
name: "placeholderText",
label: "Placeholder Text",
},
];
type PartialRow = Record<"variant" | "alignment", string>;
const partialRows: PartialRow[] = [
{
variant: "primary",
alignment: "baseline",
},
{
variant: "secondary",
alignment: "top",
},
{
variant: "success",
alignment: "middle",
},
{
variant: "danger",
alignment: "bottom",
},
{
variant: "warning",
alignment: "text-top",
},
{
variant: "info",
alignment: "text-bottom",
},
{
variant: "light",
alignment: "baseline",
},
{
variant: "dark",
alignment: "top",
},
];
const placeholderText =
"This is placeholder text intended to take up space in order to demonstrate " +
"the effects of vertical alignment. If cell being vertically aligned is the " +
"tallest column for a row, then vertical alignment will have no effect. " +
"There should be enough text here to demonstrate the effect even at the " +
"largest breakpoint.";
const getComputedTableColors: (variants: string[]) => string[] = (variants) => {
const table: HTMLTableElement = document.createElement("table");
table.className = "table";
table.style.position = "absolute";
table.style.visibility = "hidden";
const tbody = table.createTBody();
variants.forEach((variant) => {
const tr: HTMLTableRowElement = document.createElement("tr");
tr.className = `table-${variant}`;
const td: HTMLTableCellElement = document.createElement("td");
tr.appendChild(td);
tbody.appendChild(tr);
});
document.body.appendChild(table);
const colors = Array.from(tbody.children).map((tr) => {
const td = tr.firstElementChild!;
return getComputedStyle(td).backgroundColor;
});
table.remove();
return colors;
};
interface Data {
variant: string;
color: string;
alignment: string;
placeholderText: string;
}
const getRows: (
partialRows: PartialRow[],
colors: string[],
) => RowDef<Data>[] = (partialRows, colors) =>
partialRows.map((_, index) => ({
id: partialRows[index].variant,
data: {
variant: partialRows[index].variant,
color: colors[index],
alignment: partialRows[index].alignment,
placeholderText: placeholderText,
},
}));
const variantToAlignment = new Map(
partialRows.map(({ variant, alignment }) => [variant, alignment]),
);
const tableStyleModel: TableStyleModel = {
caption: ["caption-top"],
table: ["table", "table-bordered", "border-primary"],
tbodyTr: (id) => [`table-${id}`],
tbodyTd: (id, _, colIndex) =>
colIndex === 2 ? [`align-${variantToAlignment.get(id as string)}`] : null,
};
const styleModel: StyleModel = {
mainTableStyleModel: tableStyleModel,
};
const caption: string =
"Table demonstrating various ways of customizing " +
"styling with classes built into Bootstrap";
const SampleStyledTable: FC = () => {
const rows = useMemo(() => {
const variants = partialRows.map(({ variant }) => variant);
const colors = getComputedTableColors(variants);
return getRows(partialRows, colors);
}, []);
return (
<Table rows={rows} cols={cols} styleModel={styleModel} caption={caption} />
);
};
export default SampleStyledTable;Live Demo
Inputs and Display Order Example
Code
"use client";
import Table, {
ColDef,
EditableFilterState,
EditModel,
FilterModel,
RowDef,
RowId,
StyleModel,
UpdateCallbackGenerator,
} from "@absreim/react-bootstrap-data-grid/table";
import { FC, MouseEventHandler, useCallback, useMemo, useState } from "react";
const cols: ColDef[] = [
{
type: "string",
name: "name",
label: "Name",
},
{
type: "number",
name: "timesAttended",
label: "Times Attended",
},
{
type: "date",
name: "recruitmentDate",
label: "Recruitment Date",
},
];
interface Data {
name: string;
timesAttended: number;
recruitmentDate: Date;
}
const initRows: RowDef<Data>[] = [
{
id: 0,
data: {
name: "Commander Shepard",
timesAttended: 100,
recruitmentDate: new Date("2183-01-01"),
},
},
{
id: 1,
data: {
name: "Kaidan Alenko",
timesAttended: 25,
recruitmentDate: new Date("2183-01-02"),
},
},
{
id: 2,
data: {
name: "Ashley Williams",
timesAttended: 20,
recruitmentDate: new Date("2183-01-10"),
},
},
{
id: 3,
data: {
name: "Garrus Vakarian",
timesAttended: 15,
recruitmentDate: new Date("2183-02-01"),
},
},
{
id: 4,
data: {
name: "Tali'Zorah nar Rayya",
timesAttended: 12,
recruitmentDate: new Date("2183-03-15"),
},
},
{
id: 5,
data: {
name: "Urdnot Wrex",
timesAttended: 10,
recruitmentDate: new Date("2183-03-20"),
},
},
{
id: 6,
data: {
name: "Liara T'soni",
timesAttended: 8,
recruitmentDate: new Date("2183-06-21"),
},
},
];
const caption = "Normandy SR-1 Insiders";
const styleModel: StyleModel = {
mainTableStyleModel: {
table: ["table table-striped"],
tbodyTdInput: () => ["form-control form-control-sm"],
editDeleteButton: () => ["btn btn-danger"],
editSaveButton: () => ["btn btn-success"],
editCancelButton: () => ["btn btn-warning"],
},
additionalComponentsStyleModel: {
topLevelDiv: ["vstack flex-column-reverse border"],
tableAndPaginationDiv: ["vstack flex-column-reverse border m-2 p-2"],
},
filterInputTableStyleModel: {
table: ["table table-striped-columns"],
startDateInput: () => ["form-control form-control-sm"],
endDateInput: () => ["form-control form-control-sm"],
submitButton: ["btn btn-success"],
form: ["table-responsive", "w-100"],
},
};
const SampleStyledInputsTable: FC = () => {
const [rows, setRows] = useState<RowDef[]>(initRows);
const [tableFilterState, setTableFilterState] = useState<EditableFilterState>(
{
name: {
type: "string",
scheme: "contains",
searchString: "",
enabled: false,
},
timesAttended: {
type: "number",
scheme: "greaterOrEqual",
numValue: null,
enabled: false,
},
recruitmentDate: {
type: "date",
scheme: "between",
startDate: null,
endDate: null,
enabled: false,
},
},
);
const filterModel: FilterModel = useMemo(
() => ({
tableFilterState,
setTableFilterState,
}),
[tableFilterState],
);
const getUpdateCallback: UpdateCallbackGenerator = useCallback(
(id) => (rowData) => {
const newRows = rows.slice();
const index = rows.findIndex((row) => row.id === id);
if (index === undefined) {
return;
}
newRows[index] = {
id,
data: rowData,
};
setRows(newRows);
},
[rows],
);
const getDeleteCallback: (id: RowId) => () => void = useCallback(
(id) => () => {
const index = rows.findIndex((row) => row.id === id);
if (index === undefined) {
return;
}
setRows(rows.toSpliced(index, 1));
},
[rows],
);
const editModel: EditModel = { getUpdateCallback, getDeleteCallback };
const handleReset: MouseEventHandler<HTMLButtonElement> = () => {
setRows(initRows);
};
return (
<div className="vstack gap-2 align-items-start border p-2">
<Table
rows={rows}
cols={cols}
caption={caption}
filterModel={filterModel}
editModel={editModel}
styleModel={styleModel}
/>
<button className="btn btn-secondary btn-sm" onClick={handleReset}>
Reset Rows
</button>
</div>
);
};
export default SampleStyledInputsTable;Live Demo
| Name | Times Attended | Recruitment Date | Edit Controls |
|---|---|---|---|
| Commander Shepard | 100 | 2183-01-01 | |
| Kaidan Alenko | 25 | 2183-01-02 | |
| Ashley Williams | 20 | 2183-01-10 | |
| Garrus Vakarian | 15 | 2183-02-01 | |
| Tali'Zorah nar Rayya | 12 | 2183-03-15 | |
| Urdnot Wrex | 10 | 2183-03-20 | |
| Liara T'soni | 8 | 2183-06-21 |