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

Normandy SR-1 Insiders
NameTimes AttendedRecruitment DateEdit Controls
Commander Shepard1002183-01-01
Kaidan Alenko252183-01-02
Ashley Williams202183-01-10
Garrus Vakarian152183-02-01
Tali'Zorah nar Rayya122183-03-15
Urdnot Wrex102183-03-20
Liara T'soni82183-06-21