@absreim/react-bootstrap-data-grid 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Grid.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface GridProps {
16
16
  editModel?: EditModel;
17
17
  caption?: string;
18
18
  styleModel?: StyleModel;
19
+ useToolbar?: boolean;
19
20
  }
20
21
  declare const Grid: FC<GridProps>;
21
22
  export default Grid;
package/Grid.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useMemo, useState } from "react";
3
+ import { useId, useMemo, useState } from "react";
4
4
  import ColHeaderCell from "./ColHeaderCell";
5
5
  import useFilter from "./pipeline/useFilter";
6
6
  import ToggleButton from "./ToggleButton";
@@ -17,18 +17,51 @@ import inputStrsToRowData from "./editing/inputStrsToRowData";
17
17
  import { unwrapAdditionalComponentsStyleModel, unwrapTableStyleModel, } from "./styling/styleModelUnwrappers";
18
18
  import useCurrentPageRows from "./pipeline/useCurrentPageRows";
19
19
  import isSubset from "./util/isSubset";
20
+ import useFilterStateStore from "./pipeline/useFilterStateStore";
21
+ import useInterfaces from "./toolbar/useInterfaces";
22
+ import ToolbarContainer from "./toolbar/ToolbarContainer";
23
+ import useExportFn from "./export/useExportFn";
20
24
  var Grid = function (_a) {
21
25
  var _b;
22
- var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel, selectModel = _a.selectModel, editModel = _a.editModel, caption = _a.caption, styleModel = _a.styleModel;
23
- var editableFilterState = (filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || null;
24
- var filterState = useFilterStateFromEditable(cols, editableFilterState);
26
+ var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel, selectModel = _a.selectModel, editModel = _a.editModel, caption = _a.caption, styleModel = _a.styleModel, useToolbar = _a.useToolbar;
27
+ var normalizedTableFilterModel = useFilterStateStore(filterModel, cols);
28
+ var editableFilterState = (normalizedTableFilterModel === null || normalizedTableFilterModel === void 0 ? void 0 : normalizedTableFilterModel.tableFilterState) || null;
25
29
  var filteredRows = useFilter(rows, editableFilterState);
26
- var sortedRows = useSortedRows(filteredRows, cols, sortModel);
27
- var currentPageRows = useCurrentPageRows(sortedRows, pagination);
30
+ var filterState = useFilterStateFromEditable(cols, editableFilterState);
31
+ var _c = useSortedRows(filteredRows, cols, sortModel), sortedRows = _c.sortedRows, sortingEnabled = _c.sortingEnabled, sortColDef = _c.sortColDef, setSortColDef = _c.setSortColDef;
32
+ var _d = useCurrentPageRows(sortedRows, pagination), paginatedRows = _d.paginatedRows, normalizedModel = _d.normalizedModel;
28
33
  var showSelectCol = selectModel && selectModel.mode !== "row";
29
34
  var ariaColIndexOffset = showSelectCol ? 1 : 0;
30
- var displayRows = useDisplayRows(currentPageRows, cols, ariaColIndexOffset);
31
- var _c = useState(false), filterOptionsVisible = _c[0], setFilterOptionsVisible = _c[1];
35
+ var displayRows = useDisplayRows(paginatedRows, cols, ariaColIndexOffset);
36
+ var _e = useState(false), filterOptionsVisible = _e[0], setFilterOptionsVisible = _e[1];
37
+ var exportFnInfo = useExportFn({
38
+ rows: rows,
39
+ cols: cols,
40
+ filteredRows: filterModel && filteredRows,
41
+ currentPageRows: pagination && paginatedRows,
42
+ });
43
+ var toolbarInterfaceParams = useMemo(function () { return ({
44
+ filtering: useToolbar && filterState && filterModel && normalizedTableFilterModel
45
+ ? {
46
+ filterState: filterState,
47
+ setFilterState: normalizedTableFilterModel.setTableFilterState,
48
+ caption: filterModel.filterTableCaption,
49
+ styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputTableStyleModel,
50
+ }
51
+ : undefined,
52
+ exporting: useToolbar
53
+ ? { exportFnInfo: exportFnInfo, styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.exportFormStyleModel }
54
+ : undefined,
55
+ }); }, [
56
+ exportFnInfo,
57
+ filterModel,
58
+ filterState,
59
+ normalizedTableFilterModel,
60
+ styleModel === null || styleModel === void 0 ? void 0 : styleModel.exportFormStyleModel,
61
+ styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputTableStyleModel,
62
+ useToolbar,
63
+ ]);
64
+ var toolbarInterfaces = useInterfaces(toolbarInterfaceParams);
32
65
  var handleToggleFilterOptions = function () {
33
66
  setFilterOptionsVisible(!filterOptionsVisible);
34
67
  };
@@ -79,11 +112,12 @@ var Grid = function (_a) {
79
112
  selectModel.setSelected(selectModel.selected.filter(function (num) { return num !== index; }));
80
113
  }; };
81
114
  // used to group radio buttons for selection
115
+ var gridId = useId();
82
116
  var getSelectInputModel = function (id, selectModel) {
83
117
  if (selectModel.type === "single") {
84
118
  return {
85
119
  type: "radio",
86
- name: selectModel.groupName,
120
+ name: selectModel.groupName || gridId,
87
121
  };
88
122
  }
89
123
  return {
@@ -113,9 +147,9 @@ var Grid = function (_a) {
113
147
  }
114
148
  var getMultiExistingSelection = function (selectionExists, rows) {
115
149
  var rowIndices = rows.map(function (_, index) { return index; });
116
- var isFullSelection = isSubset(rowIndices, selectModel.selected);
117
- // Note that isFullSelection is true if there are no rows at all. In that case, the existing selection value
150
+ // Note that isFullSelection is true if there are no rows at all. In that case, the return value of this function
118
151
  // should be "none", not "full".
152
+ var isFullSelection = isSubset(rowIndices, selectModel.selected);
119
153
  if (!selectionExists) {
120
154
  return "none";
121
155
  }
@@ -157,18 +191,16 @@ var Grid = function (_a) {
157
191
  var unwrappedAdditionalStyleModel = useMemo(function () {
158
192
  return unwrapAdditionalComponentsStyleModel(styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel);
159
193
  }, [styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel]);
160
- return (_jsxs("div", { "data-testid": "rbdg-top-level-div", className: classNames(unwrappedAdditionalStyleModel.topLevelDiv), children: [filterState && filterModel && (_jsxs("div", { "data-testid": "rbdg-filter-inputs-div", className: classNames(unwrappedAdditionalStyleModel.filterInputsDiv), children: [_jsx(ToggleButton, { isActive: filterOptionsVisible, label: "".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options"), onClick: handleToggleFilterOptions, classes: (_b = styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel) === null || _b === void 0 ? void 0 : _b.filterUiToggleButton }), filterOptionsVisible && (_jsx(FilterOptionsTable, { caption: filterModel.filterTableCaption, filterState: filterState, setFilterState: filterModel.setTableFilterState, styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputTableStyleModel }))] })), _jsxs("div", { "data-testid": "rbdg-table-and-pagination-div", className: classNames(unwrappedAdditionalStyleModel.tableAndPaginationDiv), children: [_jsxs("table", { className: classNames("table", {
194
+ return (_jsxs("div", { "data-testid": "rbdg-top-level-div", className: classNames(unwrappedAdditionalStyleModel.topLevelDiv), children: [normalizedTableFilterModel && !useToolbar && (_jsxs("div", { "data-testid": "rbdg-filter-inputs-div", className: classNames(unwrappedAdditionalStyleModel.filterInputsDiv), children: [_jsx(ToggleButton, { isActive: filterOptionsVisible, label: "".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options"), onClick: handleToggleFilterOptions, classes: (_b = styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel) === null || _b === void 0 ? void 0 : _b.filterUiToggleButton }), filterOptionsVisible && (_jsx(FilterOptionsTable, { caption: filterModel.filterTableCaption, filterState: filterState, setFilterState: normalizedTableFilterModel.setTableFilterState, styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputTableStyleModel }))] })), useToolbar && (_jsx(ToolbarContainer, { interfaces: toolbarInterfaces, styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.toolbarStyleModel })), _jsxs("div", { "data-testid": "rbdg-table-and-pagination-div", className: classNames(unwrappedAdditionalStyleModel.tableAndPaginationDiv), children: [_jsxs("table", { className: classNames("table", {
161
195
  "table-hover": rowsAreSelectable,
162
196
  }, unwrappedTableModel.table), "aria-rowcount": filteredRows.length + 1, children: [caption !== undefined && (_jsx("caption", { className: classNames(unwrappedTableModel.caption), children: caption })), _jsx("thead", { className: classNames(unwrappedTableModel.thead), children: _jsxs("tr", { "aria-rowindex": 1, className: classNames(unwrappedTableModel.theadTr), children: [showSelectCol && (_jsx(SelectAllHeaderCell, { selectionInfo: selectionInfo, onClick: selectAllOnClick, totalRows: rows.length, additionalClasses: unwrappedTableModel.rowSelectColTh })), cols.map(function (_a, index) {
163
- var _b;
164
197
  var name = _a.name, label = _a.label, sortable = _a.sortable;
165
- var colSortModel = sortModel && sortable
198
+ var colSortModel = sortingEnabled && sortable
166
199
  ? {
167
- sortOrder: ((_b = sortModel.sortColDef) === null || _b === void 0 ? void 0 : _b.name) === name
168
- ? sortModel.sortColDef.order
169
- : null,
200
+ sortOrder: (sortColDef === null || sortColDef === void 0 ? void 0 : sortColDef.name) === name ? sortColDef.order : null,
170
201
  setSortOrder: function (order) {
171
- sortModel.setSortColDef(order && { name: name, order: order });
202
+ setSortColDef &&
203
+ setSortColDef(order && { name: name, order: order });
172
204
  },
173
205
  }
174
206
  : undefined;
@@ -183,6 +215,6 @@ var Grid = function (_a) {
183
215
  }, dataCellInputClasses: function (colIndex) {
184
216
  return unwrappedTableModel.tbodyTdInput(row.id, index, colIndex);
185
217
  }, editCellClasses: unwrappedTableModel.editColTd(row.id, index), saveButtonClasses: unwrappedTableModel.editSaveButton(row.id, index), deleteButtonClasses: unwrappedTableModel.editDeleteButton(row.id, index), startButtonClasses: unwrappedTableModel.editStartButton(row.id, index), cancelButtonClasses: unwrappedTableModel.editCancelButton(row.id, index), children: showSelectCol && (_jsx("td", { className: classNames(unwrappedTableModel.rowSelectColTd(row.id, index)), "aria-colindex": 1, children: _jsx(SelectionInput, { selected: selectedSet.has(row.id), selectionInputModel: getSelectInputModel(row.id, selectModel), selectCallback: getSelectHandler(row.id), additionalClasses: unwrappedTableModel.rowSelectInput(row.id, index) }) })) }, row.id));
186
- }) })] }), pagination && (_jsx(Pagination, { componentSize: pagination.componentSize || "medium", pageSizeOptions: pagination.pageSizeOptions, pageSizeIndex: pagination.pageSizeIndex, handleSetPageSizeIndex: pagination.setPageSizeIndex, handleSetPageNum: pagination.setCurrentPage, prePagingNumRows: sortedRows.length, maxPageButtons: pagination.maxPageButtons, currentPage: pagination.currentPage, pageSelectorJustifyContent: pagination.pageSelectorJustifyContent, pageSelectorAriaLabel: pagination.pageSelectorAriaLabel }))] })] }));
218
+ }) })] }), normalizedModel && (_jsx(Pagination, { normalizedModel: normalizedModel, prePagingNumRows: sortedRows.length, containerDivClasses: unwrappedAdditionalStyleModel.paginationUiDiv }))] })] }));
187
219
  };
188
220
  export default Grid;
@@ -0,0 +1,9 @@
1
+ import { ExportFnInfo } from "./useExportFn";
2
+ import { FC } from "react";
3
+ import { ExportFormStyleModel } from "../styling/types";
4
+ export interface ExportFormProps {
5
+ exportFnInfo: ExportFnInfo;
6
+ styleModel?: ExportFormStyleModel;
7
+ }
8
+ declare const ExportForm: FC<ExportFormProps>;
9
+ export default ExportForm;
@@ -0,0 +1,97 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { useId, useState } from "react";
14
+ import classNames from "classnames";
15
+ var ExportForm = function (_a) {
16
+ var _b = _a.exportFnInfo, exportFn = _b.exportFn, formattersExist = _b.formattersExist, paginationEnabled = _b.paginationEnabled, filteringEnabled = _b.filteringEnabled, rowCounts = _b.rowCounts, styleModel = _a.styleModel;
17
+ var formId = useId();
18
+ var _c = useState({
19
+ stage: "original",
20
+ formatted: false,
21
+ fileType: "json",
22
+ }), formState = _c[0], setFormState = _c[1];
23
+ var getChangeHandler = function (field, value) {
24
+ return function () {
25
+ setFormState(function (prev) {
26
+ var _a;
27
+ return (__assign(__assign({}, prev), (_a = {}, _a[field] = value, _a)));
28
+ });
29
+ };
30
+ };
31
+ var getRowCountLabel = function (featureType, count) {
32
+ if (count === undefined) {
33
+ return "".concat(featureType, " disabled");
34
+ }
35
+ return "".concat(count, " ").concat(count === 1 ? "row" : "rows");
36
+ };
37
+ var stageOptions = [
38
+ {
39
+ value: "original",
40
+ label: "Original rows (total ".concat(rowCounts.total, " ").concat(rowCounts.total === 1 ? "row" : "rows", ")"),
41
+ disabled: false,
42
+ },
43
+ {
44
+ value: "filtered",
45
+ label: "After filters applied (".concat(getRowCountLabel("filtering", rowCounts.filtered), ")"),
46
+ disabled: !filteringEnabled,
47
+ },
48
+ {
49
+ value: "paged",
50
+ label: "Current page only (".concat(getRowCountLabel("pagination", rowCounts.currentPage), ")"),
51
+ disabled: !paginationEnabled,
52
+ },
53
+ ];
54
+ var formatOptions = [
55
+ {
56
+ formatted: false,
57
+ label: "Use original data",
58
+ disabled: false,
59
+ },
60
+ {
61
+ formatted: true,
62
+ label: "Apply formatters to data".concat(formattersExist ? "" : " (no formatters defined)"),
63
+ disabled: !formattersExist,
64
+ },
65
+ ];
66
+ var fileTypeOptions = [
67
+ {
68
+ fileType: "json",
69
+ label: "JSON",
70
+ },
71
+ {
72
+ fileType: "csv",
73
+ label: "CSV",
74
+ },
75
+ ];
76
+ var handleSubmit = function (event) {
77
+ event.preventDefault();
78
+ var stage = formState.stage, fileType = formState.fileType, formatted = formState.formatted;
79
+ exportFn(stage, fileType, formatted);
80
+ };
81
+ var legendClasses = classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.legend) || []);
82
+ var radioContainerClasses = classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.radioContainer) || ["form-check"]);
83
+ var radioInputClasses = classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.radioInput) || ["form-check-input"]);
84
+ var radioLabelClasses = classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.radioLabel) || ["form-check-label"]);
85
+ var submitButtonClasses = classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.submitButton) || ["btn", "btn-secondary"]);
86
+ return (_jsxs("form", { onSubmit: handleSubmit, children: [_jsxs("fieldset", { children: [_jsx("legend", { className: legendClasses, children: "Choose data to export" }), stageOptions.map(function (_a) {
87
+ var value = _a.value, label = _a.label, disabled = _a.disabled;
88
+ return (_jsxs("div", { className: radioContainerClasses, children: [_jsx("input", { className: radioInputClasses, type: "radio", id: "".concat(formId, "-").concat(value), value: value, checked: formState.stage === value, onChange: getChangeHandler("stage", value), disabled: disabled }), _jsx("label", { className: radioLabelClasses, htmlFor: "".concat(formId, "-").concat(value), children: label })] }, value));
89
+ })] }), _jsxs("fieldset", { children: [_jsx("legend", { className: legendClasses, children: "Choose whether to apply formatters" }), formatOptions.map(function (_a) {
90
+ var formatted = _a.formatted, label = _a.label, disabled = _a.disabled;
91
+ return (_jsxs("div", { className: radioContainerClasses, children: [_jsx("input", { className: radioInputClasses, type: "radio", id: "".concat(formId, "-").concat(formatted), value: String(formatted), checked: formState.formatted === formatted, onChange: getChangeHandler("formatted", formatted), disabled: disabled }), _jsx("label", { className: radioLabelClasses, htmlFor: "".concat(formId, "-").concat(formatted), children: label })] }, String(formatted)));
92
+ })] }), _jsxs("fieldset", { children: [_jsx("legend", { className: legendClasses, children: "Choose the file type" }), fileTypeOptions.map(function (_a) {
93
+ var fileType = _a.fileType, label = _a.label;
94
+ return (_jsxs("div", { className: radioContainerClasses, children: [_jsx("input", { className: radioInputClasses, type: "radio", id: "".concat(formId, "-").concat(fileType), value: fileType, checked: formState.fileType === fileType, onChange: getChangeHandler("fileType", fileType) }), _jsx("label", { className: radioLabelClasses, htmlFor: "".concat(formId, "-").concat(fileType), children: label })] }, fileType));
95
+ })] }), _jsx("button", { type: "submit", className: submitButtonClasses, children: "Submit" })] }));
96
+ };
97
+ export default ExportForm;
@@ -0,0 +1,5 @@
1
+ import { RowId } from "../types";
2
+ export type FormattedExportRow = {
3
+ id: RowId;
4
+ data: Record<string, string | number>;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { ColDef, RowDef } from "../types";
2
+ export interface UseExportFnsParams {
3
+ rows: RowDef[];
4
+ cols: ColDef[];
5
+ filteredRows?: RowDef[];
6
+ currentPageRows?: RowDef[];
7
+ }
8
+ export type Stage = "original" | "filtered" | "paged";
9
+ export type FileType = "csv" | "json";
10
+ export type ExportFn = (stage: Stage, fileType: FileType, formatted: boolean) => void;
11
+ export interface ExportFnInfo {
12
+ exportFn: ExportFn;
13
+ formattersExist: boolean;
14
+ filteringEnabled: boolean;
15
+ paginationEnabled: boolean;
16
+ rowCounts: {
17
+ total: number;
18
+ filtered?: number;
19
+ currentPage?: number;
20
+ };
21
+ }
22
+ declare const useExportFn: (params: UseExportFnsParams) => ExportFnInfo;
23
+ export default useExportFn;
@@ -0,0 +1,115 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { dateToDatetimeInputStr, dateToInputStr } from "../util/datetime";
13
+ import Papa from "papaparse";
14
+ import { useCallback, useMemo } from "react";
15
+ var downloadFile = function (data, filename, type) {
16
+ var blob = new Blob([data], { type: type });
17
+ var url = URL.createObjectURL(blob);
18
+ var a = document.createElement("a");
19
+ a.href = url;
20
+ a.download = filename;
21
+ a.click();
22
+ a.remove();
23
+ setTimeout(function () { return URL.revokeObjectURL(url); }, 0);
24
+ };
25
+ var getDefaultFormatter = function (type) {
26
+ switch (type) {
27
+ case "date":
28
+ return dateToInputStr;
29
+ case "datetime":
30
+ return dateToDatetimeInputStr;
31
+ default:
32
+ return function (a) { return a; };
33
+ }
34
+ };
35
+ var applyFormatters = function (rows, cols, defaultOnly) {
36
+ var colToFormatter = cols.reduce(function (map, _a) {
37
+ var name = _a.name, formatter = _a.formatter, type = _a.type;
38
+ var normalizedFormatter = (!defaultOnly && formatter) || getDefaultFormatter(type);
39
+ map.set(name, normalizedFormatter);
40
+ return map;
41
+ }, new Map());
42
+ return rows.map(function (_a) {
43
+ var id = _a.id, data = _a.data;
44
+ return ({
45
+ id: id,
46
+ data: Object.keys(data).reduce(function (newData, name) {
47
+ var formatter = colToFormatter.get(name);
48
+ newData[name] = formatter(data[name]);
49
+ return newData;
50
+ }, {}),
51
+ });
52
+ });
53
+ };
54
+ var flattenExportRows = function (rows) {
55
+ return rows.map(function (_a) {
56
+ var id = _a.id, data = _a.data;
57
+ return (__assign({ id: id }, data));
58
+ });
59
+ };
60
+ var exportJson = function (rows, cols, useDefaultFormatters) {
61
+ var formattedRows = applyFormatters(rows, cols, useDefaultFormatters);
62
+ var json = JSON.stringify(formattedRows, null, 2);
63
+ downloadFile(json, "export.json", "application/json");
64
+ };
65
+ var exportCsv = function (rows, cols, useDefaultFormatters) {
66
+ var formattedRows = applyFormatters(rows, cols, useDefaultFormatters);
67
+ var flattenedRows = flattenExportRows(formattedRows);
68
+ var csv = Papa.unparse(flattenedRows, { header: true });
69
+ downloadFile(csv, "export.csv", "text/csv");
70
+ };
71
+ var useExportFn = function (_a) {
72
+ var rows = _a.rows, cols = _a.cols, filteredRows = _a.filteredRows, currentPageRows = _a.currentPageRows;
73
+ var formattersExist = useMemo(function () { return cols.reduce(function (prev, _a) {
74
+ var formatter = _a.formatter;
75
+ return prev || !!formatter;
76
+ }, false); }, [cols]);
77
+ var exportFn = useCallback(function (stage, fileType, formatted) {
78
+ if (stage === "filtered" && !filteredRows) {
79
+ throw Error("Cannot export filtered rows because filtering is not enabled for this grid");
80
+ }
81
+ if (stage === "paged" && !currentPageRows) {
82
+ throw Error("Cannot export current page of rows because paging is not enabled for this grid");
83
+ }
84
+ if (formatted && !formattersExist) {
85
+ throw Error("Cannot export formatted rows because formatters are not defined for this grid");
86
+ }
87
+ var exportRows = (function () {
88
+ switch (stage) {
89
+ case "filtered":
90
+ return filteredRows;
91
+ case "paged":
92
+ return currentPageRows;
93
+ default:
94
+ return rows;
95
+ }
96
+ })();
97
+ if (fileType === "csv") {
98
+ exportCsv(exportRows, cols, !formatted);
99
+ return;
100
+ }
101
+ exportJson(exportRows, cols, !formatted);
102
+ }, [cols, currentPageRows, filteredRows, formattersExist, rows]);
103
+ return useMemo(function () { return ({
104
+ exportFn: exportFn,
105
+ formattersExist: formattersExist,
106
+ paginationEnabled: !!currentPageRows,
107
+ filteringEnabled: !!filteredRows,
108
+ rowCounts: {
109
+ total: rows.length,
110
+ filtered: filteredRows === null || filteredRows === void 0 ? void 0 : filteredRows.length,
111
+ currentPage: currentPageRows === null || currentPageRows === void 0 ? void 0 : currentPageRows.length,
112
+ }
113
+ }); }, [currentPageRows, exportFn, filteredRows, formattersExist, rows.length]);
114
+ };
115
+ export default useExportFn;
@@ -10,9 +10,8 @@ var __assign = (this && this.__assign) || function () {
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { useMemo } from "react";
13
+ import { useId } from "react";
14
14
  import { dateFilterSchemeNames, } from "./types";
15
- import { nanoid } from "nanoid/non-secure";
16
15
  import FilterRow from "./FilterRow";
17
16
  import classNames from "classnames";
18
17
  var DateFilterRow = function (_a) {
@@ -35,7 +34,7 @@ var DateFilterRow = function (_a) {
35
34
  };
36
35
  var enabled = filterState.enabled, scheme = filterState.scheme, startDate = filterState.startDate, endDate = filterState.endDate;
37
36
  var inputType = includeTime ? "datetime-local" : "date";
38
- var inputId = useMemo(function () { return nanoid(); }, []);
37
+ var inputId = useId();
39
38
  var startDateInputId = "$startDate-".concat(inputId);
40
39
  var endDateInputId = "$endDate-".concat(inputId);
41
40
  var startDateInputLabel = "".concat(columnLabel, " Column Filter Start Date");
@@ -1,7 +1,7 @@
1
1
  import { FC } from "react";
2
2
  import { EditableTableFilterState, TableFilterState } from "./types";
3
3
  import { FilterInputTableStyleModel } from "../styling/types";
4
- interface FilterOptionsTableProps {
4
+ export interface FilterOptionsTableProps {
5
5
  filterState: TableFilterState;
6
6
  setFilterState: (filterState: EditableTableFilterState) => void;
7
7
  caption?: string;
@@ -112,7 +112,9 @@ var FilterOptionsTable = function (_a) {
112
112
  var editableTableFilterState = convertFilterFormStateToEditableState(formState);
113
113
  setFilterState(editableTableFilterState);
114
114
  };
115
- // TODO: consider using an accordion to show and hide this component
115
+ // TODO: consider using an accordion to show and hide this component.
116
+ // Will eventually be a moot point due to the toolbar being implemented, but
117
+ // still worth considering for backwards compatibility.
116
118
  return (_jsxs("form", { onSubmit: onSubmit, children: [_jsxs("table", { className: classNames.apply(void 0, __spreadArray(["table"], unwrappedStyleModel.table, false)), children: [caption && (_jsx("caption", { className: classNames(unwrappedStyleModel.caption), children: caption })), _jsx("thead", { className: classNames.apply(void 0, unwrappedStyleModel.thead), children: _jsx("tr", { className: classNames.apply(void 0, unwrappedStyleModel.theadTr), children: ["Enabled", "Column", "Type", "Operator", "Value"].map(function (colName, index) { return (_jsx("th", { className: classNames.apply(void 0, unwrappedStyleModel.theadTh(index)), children: colName }, index)); }) }) }), _jsx("tbody", { className: classNames.apply(void 0, unwrappedStyleModel.tbody), children: getRows() })] }), _jsx("button", { className: classNames("btn", { "btn-secondary": unwrappedStyleModel.submitButton.length === 0 }, unwrappedStyleModel.submitButton), type: "submit", children: "Submit" })] }));
117
119
  };
118
120
  export default FilterOptionsTable;
@@ -45,16 +45,22 @@ export interface BetweenDatesFilterState extends AbstractDateFilterState {
45
45
  }
46
46
  export type DateFilterState = StartDateFilterState | EndDateFilterState | BetweenDatesFilterState;
47
47
  export type FilterState = StringFilterState | NumberFilterState | DateFilterState;
48
- export interface FilterModel {
48
+ export interface ControlledFilterModel {
49
+ type?: "controlled";
49
50
  tableFilterState: EditableTableFilterState;
50
51
  setTableFilterState: (state: EditableTableFilterState) => void;
51
52
  filterTableCaption?: string;
52
53
  }
54
+ export type UncontrolledFilterModel = Partial<Pick<ControlledFilterModel, "tableFilterState" | "filterTableCaption">> & {
55
+ type: "uncontrolled";
56
+ };
57
+ export type NormalizedTableFilterModel = Pick<ControlledFilterModel, "tableFilterState" | "setTableFilterState">;
53
58
  export interface NumberFormFilterState extends AbstractFilterState {
54
59
  type: "number";
55
60
  scheme: NumberFilterScheme;
56
61
  inputValue: string;
57
62
  }
63
+ export type FilterModel = ControlledFilterModel | UncontrolledFilterModel;
58
64
  export interface DateFormFilterState extends AbstractDateFilterState {
59
65
  scheme: DateFilterScheme;
60
66
  startDate: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@absreim/react-bootstrap-data-grid",
3
3
  "description": "Data grid UI component for use with web apps using React and Bootstrap",
4
- "version": "2.0.0",
4
+ "version": "2.2.0",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -16,7 +16,8 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "classnames": "^2.5.1",
19
- "dayjs": "^1.11.13"
19
+ "dayjs": "^1.11.13",
20
+ "papaparse": "^5.5.3"
20
21
  },
21
22
  "peerDependencies": {
22
23
  "react": "^19",
@@ -1,16 +1,9 @@
1
1
  import { FC } from "react";
2
- import { JustifyContentSetting, Size } from "../types";
2
+ import { NormalizedPaginationModel } from "./types";
3
3
  export interface PaginationProps {
4
- componentSize: Size;
5
- pageSizeOptions: number[];
6
- pageSizeIndex: number;
7
- handleSetPageSizeIndex: (index: number) => void;
8
- handleSetPageNum: (index: number) => void;
4
+ normalizedModel: NormalizedPaginationModel;
9
5
  prePagingNumRows: number;
10
- maxPageButtons: number;
11
- currentPage: number;
12
- pageSelectorAriaLabel?: string;
13
- pageSelectorJustifyContent?: JustifyContentSetting;
6
+ containerDivClasses: string[];
14
7
  }
15
8
  declare const Pagination: FC<PaginationProps>;
16
9
  export default Pagination;
@@ -1,20 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import PageSizeSelector from "./PageSizeSelector";
3
3
  import PageSelector from "./PageSelector";
4
+ import classNames from "classnames";
4
5
  var Pagination = function (_a) {
5
- var componentSize = _a.componentSize, pageSizeOptions = _a.pageSizeOptions, pageSizeIndex = _a.pageSizeIndex, handleSetPageSizeIndex = _a.handleSetPageSizeIndex, handleSetPageNum = _a.handleSetPageNum, prePagingNumRows = _a.prePagingNumRows, maxPageButtons = _a.maxPageButtons, currentPage = _a.currentPage, pageSelectorAriaLabel = _a.pageSelectorAriaLabel, pageSelectorJustifyContent = _a.pageSelectorJustifyContent;
6
+ var normalizedModel = _a.normalizedModel, prePagingNumRows = _a.prePagingNumRows, containerDivClasses = _a.containerDivClasses;
7
+ var pageSizeOptions = normalizedModel.pageSizeOptions, pageSizeIndex = normalizedModel.pageSizeIndex, currentPage = normalizedModel.currentPage, setCurrentPage = normalizedModel.setCurrentPage, setPageSizeIndex = normalizedModel.setPageSizeIndex, componentSize = normalizedModel.componentSize, maxPageButtons = normalizedModel.maxPageButtons, pageSelectorAriaLabel = normalizedModel.pageSelectorAriaLabel, pageSelectorJustifyContent = normalizedModel.pageSelectorJustifyContent;
6
8
  var numPages = Math.ceil(prePagingNumRows / pageSizeOptions[pageSizeIndex]);
7
9
  var pageIndexAwarePageSizeSetter = function (newPageSizeIndex) {
8
10
  var newPageSize = pageSizeOptions[newPageSizeIndex];
9
11
  var maxPages = Math.ceil(prePagingNumRows / newPageSize);
10
- handleSetPageSizeIndex(newPageSizeIndex);
12
+ setPageSizeIndex(newPageSizeIndex);
11
13
  // The new page size can cause the current page number to be out of bounds.
12
14
  // In that case, set the page num to the maximum possible with new page
13
15
  // size.
14
16
  if (currentPage > maxPages) {
15
- handleSetPageNum(maxPages);
17
+ setCurrentPage(maxPages);
16
18
  }
17
19
  };
18
- return (_jsxs("div", { className: "d-flex justify-content-end gap-2", children: [_jsx(PageSizeSelector, { componentSize: componentSize, pageSizeOptions: pageSizeOptions, pageSizeIndex: pageSizeIndex, handleSetPageSize: pageIndexAwarePageSizeSetter }), _jsx(PageSelector, { numPages: numPages, pageNum: currentPage, numButtons: maxPageButtons, setPageNum: handleSetPageNum, size: componentSize, ariaLabel: pageSelectorAriaLabel, alignment: pageSelectorJustifyContent })] }));
20
+ return (_jsxs("div", { "data-testid": "pagination ui container div", className: classNames(containerDivClasses.length > 0
21
+ ? containerDivClasses
22
+ : ["d-flex", "justify-content-end", "gap-2", "px-2"]), children: [_jsx(PageSizeSelector, { componentSize: componentSize, pageSizeOptions: pageSizeOptions, pageSizeIndex: pageSizeIndex, handleSetPageSize: pageIndexAwarePageSizeSetter }), _jsx(PageSelector, { numPages: numPages, pageNum: currentPage, numButtons: maxPageButtons, setPageNum: setCurrentPage, size: componentSize, ariaLabel: pageSelectorAriaLabel, alignment: pageSelectorJustifyContent })] }));
19
23
  };
20
24
  export default Pagination;
@@ -1,12 +1,22 @@
1
1
  import { JustifyContentSetting, Size } from "../types";
2
- export interface GridPaginationState {
3
- pageSizeOptions: number[];
4
- pageSizeIndex: number;
5
- setPageSizeIndex: (pageSizeIndex: number) => void;
6
- currentPage: number;
7
- setCurrentPage: (pageNum: number) => void;
8
- maxPageButtons: number;
2
+ export interface PaginationOptions {
3
+ pageSizeOptions?: number[];
4
+ maxPageButtons?: number;
9
5
  componentSize?: Size;
10
6
  pageSelectorAriaLabel?: string;
11
7
  pageSelectorJustifyContent?: JustifyContentSetting;
12
8
  }
9
+ export type ControlledPaginationModel = PaginationOptions & {
10
+ type?: "controlled";
11
+ pageSizeIndex: number;
12
+ setPageSizeIndex: (pageSizeIndex: number) => void;
13
+ currentPage: number;
14
+ setCurrentPage: (pageNum: number) => void;
15
+ };
16
+ export type UncontrolledPaginationModel = PaginationOptions & {
17
+ type: "uncontrolled";
18
+ startingPageSizeIndex?: number;
19
+ startingCurrentPage?: number;
20
+ };
21
+ export type GridPaginationState = ControlledPaginationModel | UncontrolledPaginationModel;
22
+ export type NormalizedPaginationModel = Required<Omit<ControlledPaginationModel, "type" | "pageSelectorAriaLabel" | "pageSelectorJustifyContent">> & Pick<ControlledPaginationModel, "pageSelectorAriaLabel" | "pageSelectorJustifyContent">;
@@ -1,4 +1,7 @@
1
1
  import { RowDef } from "../types";
2
- import { GridPaginationState } from "../pagination/types";
3
- declare const useCurrentPageRows: (sortedRows: RowDef[], pagination: GridPaginationState | undefined) => RowDef[];
2
+ import { NormalizedPaginationModel, GridPaginationState } from "../pagination/types";
3
+ declare const useCurrentPageRows: (sortedRows: RowDef[], paginationModel: GridPaginationState | undefined) => {
4
+ paginatedRows: RowDef[];
5
+ normalizedModel: NormalizedPaginationModel | null;
6
+ };
4
7
  export default useCurrentPageRows;
@@ -1,14 +1,52 @@
1
- import { useMemo } from "react";
2
- var useCurrentPageRows = function (sortedRows, pagination) {
1
+ import { useMemo, useState } from "react";
2
+ var useCurrentPageRows = function (sortedRows, paginationModel) {
3
+ var componentSize = (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.componentSize) || "medium";
4
+ var isControlled = (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.type) !== "uncontrolled";
5
+ var _a = useState(isControlled ? 0 : paginationModel.startingPageSizeIndex || 0), internalPageSizeIndex = _a[0], setInternalPageSizeIndex = _a[1];
6
+ var pageSizeIndex = isControlled
7
+ ? (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.pageSizeIndex) || 0
8
+ : internalPageSizeIndex;
9
+ var setPageSizeIndex = isControlled
10
+ ? paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.setPageSizeIndex
11
+ : setInternalPageSizeIndex;
12
+ var _b = useState(isControlled ? 1 : paginationModel.startingCurrentPage || 1), internalPageNum = _b[0], setInternalPageNum = _b[1];
13
+ var currentPage = isControlled
14
+ ? (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.currentPage) || 1
15
+ : internalPageNum;
16
+ var setCurrentPage = isControlled
17
+ ? paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.setCurrentPage
18
+ : setInternalPageNum;
19
+ var maxPageButtons = (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.maxPageButtons) || 5;
3
20
  return useMemo(function () {
4
- if (pagination === undefined) {
5
- return sortedRows;
21
+ if (paginationModel === undefined) {
22
+ return { paginatedRows: sortedRows, normalizedModel: null };
6
23
  }
7
- var pageSizeOptions = pagination.pageSizeOptions, pageSizeIndex = pagination.pageSizeIndex, currentPage = pagination.currentPage;
24
+ var pageSizeOptions = (paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.pageSizeOptions) || [10, 25, 100];
25
+ var normalizedModel = {
26
+ pageSizeIndex: pageSizeIndex,
27
+ setPageSizeIndex: setPageSizeIndex,
28
+ currentPage: currentPage,
29
+ setCurrentPage: setCurrentPage,
30
+ pageSizeOptions: pageSizeOptions,
31
+ maxPageButtons: maxPageButtons,
32
+ componentSize: componentSize,
33
+ pageSelectorAriaLabel: paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.pageSelectorAriaLabel,
34
+ pageSelectorJustifyContent: paginationModel === null || paginationModel === void 0 ? void 0 : paginationModel.pageSelectorJustifyContent,
35
+ };
8
36
  var pageSize = pageSizeOptions[pageSizeIndex];
9
37
  var lowerIndex = pageSize * (currentPage - 1);
10
38
  var upperIndex = lowerIndex + pageSize;
11
- return sortedRows.slice(lowerIndex, upperIndex);
12
- }, [sortedRows, pagination]);
39
+ var paginatedRows = sortedRows.slice(lowerIndex, upperIndex);
40
+ return { paginatedRows: paginatedRows, normalizedModel: normalizedModel };
41
+ }, [
42
+ paginationModel,
43
+ pageSizeIndex,
44
+ setPageSizeIndex,
45
+ currentPage,
46
+ setCurrentPage,
47
+ maxPageButtons,
48
+ componentSize,
49
+ sortedRows,
50
+ ]);
13
51
  };
14
52
  export default useCurrentPageRows;
@@ -0,0 +1,4 @@
1
+ import { FilterModel, NormalizedTableFilterModel } from "../filtering/types";
2
+ import { ColDef } from "../types";
3
+ declare const useFilterStateStore: (filterModel: FilterModel | undefined, cols: ColDef[]) => NormalizedTableFilterModel | null;
4
+ export default useFilterStateStore;
@@ -0,0 +1,57 @@
1
+ import { useState } from "react";
2
+ var generateEmptyFilterState = function (cols) {
3
+ var filterState = {};
4
+ cols.forEach(function (_a) {
5
+ var type = _a.type, name = _a.name;
6
+ switch (type) {
7
+ case "string": {
8
+ filterState[name] = {
9
+ type: "string",
10
+ enabled: false,
11
+ scheme: "contains",
12
+ searchString: "",
13
+ };
14
+ break;
15
+ }
16
+ case "number": {
17
+ filterState[name] = {
18
+ type: "number",
19
+ enabled: false,
20
+ scheme: "greaterOrEqual",
21
+ numValue: null,
22
+ };
23
+ break;
24
+ }
25
+ default: {
26
+ filterState[name] = {
27
+ type: type,
28
+ enabled: false,
29
+ scheme: "startFrom",
30
+ startDate: null,
31
+ };
32
+ }
33
+ }
34
+ });
35
+ return filterState;
36
+ };
37
+ var useFilterStateStore = function (filterModel, cols) {
38
+ // Initial states being from prop values means that should uncontrolled
39
+ // FilterModel starting values change, the changes will not take effect
40
+ // unless the Grid is remounted. The documentation for this and other
41
+ // uncontrolled features should indicate this fact and recommend using
42
+ // controlled modes if this behavior is unacceptable.
43
+ var _a = useState((filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || generateEmptyFilterState(cols)), internalFilterState = _a[0], setInternalFilterState = _a[1];
44
+ if (!filterModel) {
45
+ return null;
46
+ }
47
+ return filterModel.type === "uncontrolled"
48
+ ? {
49
+ tableFilterState: internalFilterState,
50
+ setTableFilterState: setInternalFilterState,
51
+ }
52
+ : {
53
+ tableFilterState: filterModel.tableFilterState,
54
+ setTableFilterState: filterModel.setTableFilterState,
55
+ };
56
+ };
57
+ export default useFilterStateStore;
@@ -1,4 +1,9 @@
1
1
  import { ColDef, RowDef } from "../types";
2
- import { TableSortModel } from "../sorting/types";
3
- declare const useSortedRows: (rows: RowDef[], cols: ColDef[], sortModel: TableSortModel | undefined) => RowDef[];
2
+ import { SortColDef, TableSortModel } from "../sorting/types";
3
+ declare const useSortedRows: (rows: RowDef[], cols: ColDef[], sortModel: TableSortModel | undefined) => {
4
+ sortedRows: RowDef[];
5
+ sortingEnabled: boolean;
6
+ sortColDef: SortColDef | null | undefined;
7
+ setSortColDef: ((sortColDef: SortColDef | null) => void) | undefined;
8
+ };
4
9
  export default useSortedRows;
@@ -1,4 +1,4 @@
1
- import { useMemo } from "react";
1
+ import { useMemo, useState } from "react";
2
2
  var getTypeComparator = function (typeStr) {
3
3
  if (typeStr === "date" || typeStr === "datetime") {
4
4
  return function (a, b) { return a.valueOf() - b.valueOf(); };
@@ -20,12 +20,21 @@ var getRowComparator = function (comparator, fieldName) {
20
20
  return function (rowA, rowB) { return comparator(rowA.data[fieldName], rowB.data[fieldName]); };
21
21
  };
22
22
  var useSortedRows = function (rows, cols, sortModel) {
23
- return useMemo(function () {
24
- if (!sortModel || !sortModel.sortColDef) {
23
+ var _a = useState(((sortModel === null || sortModel === void 0 ? void 0 : sortModel.type) === "uncontrolled" && sortModel.initialSortColDef) ||
24
+ null), internalSortColDef = _a[0], setInternalSortColDef = _a[1];
25
+ var sortColDef = (sortModel === null || sortModel === void 0 ? void 0 : sortModel.type) === "uncontrolled"
26
+ ? internalSortColDef
27
+ : (sortModel === null || sortModel === void 0 ? void 0 : sortModel.sortColDef) || undefined;
28
+ var setSortColDef = (sortModel === null || sortModel === void 0 ? void 0 : sortModel.type) === "uncontrolled"
29
+ ? setInternalSortColDef
30
+ : (sortModel === null || sortModel === void 0 ? void 0 : sortModel.setSortColDef) || undefined;
31
+ var sortingEnabled = !!sortModel;
32
+ var sortedRows = useMemo(function () {
33
+ if (!sortColDef) {
25
34
  return rows;
26
35
  }
27
- var sortFieldName = sortModel.sortColDef.name;
28
- var sortOrder = sortModel.sortColDef.order;
36
+ var sortFieldName = sortColDef.name;
37
+ var sortOrder = sortColDef.order;
29
38
  var sortColIndex = cols.findIndex(function (_a) {
30
39
  var name = _a.name;
31
40
  return name === sortFieldName;
@@ -43,6 +52,7 @@ var useSortedRows = function (rows, cols, sortModel) {
43
52
  rowComparator = getRowComparator(descComparator, sortFieldName);
44
53
  }
45
54
  return rows.slice().sort(rowComparator);
46
- }, [rows, cols, sortModel]);
55
+ }, [sortColDef, cols, rows]);
56
+ return { sortedRows: sortedRows, sortingEnabled: sortingEnabled, sortColDef: sortColDef, setSortColDef: setSortColDef };
47
57
  };
48
58
  export default useSortedRows;
@@ -12,7 +12,7 @@ export interface SingleSelectModel {
12
12
  type: "single";
13
13
  selected: RowId | null;
14
14
  setSelected: (selected: RowId | null) => void;
15
- groupName: string;
15
+ groupName?: string;
16
16
  }
17
17
  export type SelectModel = SingleSelectModel | MultiSelectModel;
18
18
  export type MultiExistingSelection = "full" | "partial" | "none";
@@ -7,7 +7,13 @@ export interface ColSortModel {
7
7
  sortOrder: SortOrder | null;
8
8
  setSortOrder: (order: SortOrder | null) => void;
9
9
  }
10
- export interface TableSortModel {
10
+ export interface ControlledTableSortModel {
11
+ type?: "controlled";
11
12
  sortColDef: SortColDef | null;
12
13
  setSortColDef: (sortColDef: SortColDef | null) => void;
13
14
  }
15
+ export interface UncontrolledTableSortModel {
16
+ type: "uncontrolled";
17
+ initialSortColDef: SortColDef | null;
18
+ }
19
+ export type TableSortModel = ControlledTableSortModel | UncontrolledTableSortModel;
@@ -54,4 +54,5 @@ export var unwrapAdditionalComponentsStyleModel = function (styleModel) { return
54
54
  filterInputsDiv: (styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputsDiv) || [],
55
55
  tableAndPaginationDiv: (styleModel === null || styleModel === void 0 ? void 0 : styleModel.tableAndPaginationDiv) || [],
56
56
  filterUiToggleButton: (styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterUiToggleButton) || [],
57
+ paginationUiDiv: (styleModel === null || styleModel === void 0 ? void 0 : styleModel.paginationUiDiv) || [],
57
58
  }); };
@@ -37,9 +37,25 @@ export interface AdditionalComponentsStyleModel {
37
37
  filterInputsDiv?: string[];
38
38
  tableAndPaginationDiv?: string[];
39
39
  filterUiToggleButton?: string[];
40
+ paginationUiDiv?: string[];
41
+ }
42
+ export interface ToolbarStyleModel {
43
+ activeButton?: string[];
44
+ inactiveButton?: string[];
45
+ toolbar?: string[];
46
+ interfaceContainer?: string[];
47
+ }
48
+ export interface ExportFormStyleModel {
49
+ legend?: string[];
50
+ radioContainer?: string[];
51
+ radioInput?: string[];
52
+ radioLabel?: string[];
53
+ submitButton?: string[];
40
54
  }
41
55
  export interface StyleModel {
42
56
  mainTableStyleModel?: TableStyleModel;
43
57
  filterInputTableStyleModel?: FilterInputTableStyleModel;
44
58
  additionalComponentsStyleModel?: AdditionalComponentsStyleModel;
59
+ toolbarStyleModel?: ToolbarStyleModel;
60
+ exportFormStyleModel?: ExportFormStyleModel;
45
61
  }
@@ -0,0 +1,12 @@
1
+ import { ToolbarOption } from "./types";
2
+ import { FC } from "react";
3
+ export interface ToolbarProps {
4
+ enabledFeatures: Partial<Record<ToolbarOption, boolean>>;
5
+ option: ToolbarOption | null;
6
+ setOption: (option: ToolbarOption | null) => void;
7
+ toolbarClasses?: string[];
8
+ activeClasses?: string[];
9
+ inactiveClasses?: string[];
10
+ }
11
+ declare const Toolbar: FC<ToolbarProps>;
12
+ export default Toolbar;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ var buttonSpecs = {
4
+ filtering: {
5
+ label: "Filtering",
6
+ icon: (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: _jsx("path", { d: "M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5m-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5" }) })),
7
+ },
8
+ exporting: {
9
+ label: "Export",
10
+ icon: (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: [_jsx("path", { d: "M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" }), _jsx("path", { d: "M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" })] })),
11
+ },
12
+ };
13
+ // TODO: figure out tabindex and accessibility
14
+ var Toolbar = function (_a) {
15
+ var enabledFeatures = _a.enabledFeatures, option = _a.option, setOption = _a.setOption, toolbarClasses = _a.toolbarClasses, activeClasses = _a.activeClasses, inactiveClasses = _a.inactiveClasses;
16
+ return (_jsx("div", { className: classNames(toolbarClasses || ["hstack", "gap-2", "justify-content-start", "px-2"]), role: "toolbar", children: Object.keys(buttonSpecs)
17
+ .filter(function (toolbarOption) { return !!enabledFeatures[toolbarOption]; })
18
+ .map(function (toolbarOption) { return (_jsx("button", { "aria-label": buttonSpecs[toolbarOption].label, "aria-roledescription": "Grouped toggle button to show/hide ".concat(toolbarOption, " UI"), "aria-pressed": option === toolbarOption, className: classNames.apply(void 0, (option === toolbarOption
19
+ ? activeClasses || ["btn", "btn-outline-secondary", "active"]
20
+ : inactiveClasses || ["btn", "btn-outline-secondary"])), title: buttonSpecs[toolbarOption].label, onClick: function () {
21
+ setOption(option === toolbarOption
22
+ ? null
23
+ : toolbarOption);
24
+ }, children: buttonSpecs[toolbarOption].icon }, toolbarOption)); }) }));
25
+ };
26
+ export default Toolbar;
@@ -0,0 +1,9 @@
1
+ import { ToolbarInterfaces } from "./types";
2
+ import { FC } from "react";
3
+ import { ToolbarStyleModel } from "../styling/types";
4
+ interface ToolbarContainerProps {
5
+ interfaces: ToolbarInterfaces;
6
+ styleModel?: ToolbarStyleModel;
7
+ }
8
+ declare const ToolbarContainer: FC<ToolbarContainerProps>;
9
+ export default ToolbarContainer;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import Toolbar from "./Toolbar";
4
+ import classNames from "classnames";
5
+ var ToolbarContainer = function (_a) {
6
+ var interfaces = _a.interfaces, styleModel = _a.styleModel;
7
+ var _b = useState(null), option = _b[0], setOption = _b[1];
8
+ var enabledFeatures = Object.keys(interfaces).reduce(function (prev, toolbarOption) {
9
+ prev[toolbarOption] =
10
+ !!interfaces[toolbarOption];
11
+ return prev;
12
+ }, {});
13
+ // TODO: mention in documentation that Bootstrap 5.3 is required due to the
14
+ // use of the z-index utility
15
+ return (_jsxs("div", { className: "vstack", "data-testid": "toolbar container", children: [_jsx(Toolbar, { enabledFeatures: enabledFeatures, option: option, setOption: setOption, toolbarClasses: styleModel === null || styleModel === void 0 ? void 0 : styleModel.toolbar, activeClasses: styleModel === null || styleModel === void 0 ? void 0 : styleModel.activeButton, inactiveClasses: styleModel === null || styleModel === void 0 ? void 0 : styleModel.inactiveButton }), _jsx("div", { className: "position-relative", children: option !== null && (_jsx("div", { "data-testid": "toolbar feature interface content container", className: classNames((styleModel === null || styleModel === void 0 ? void 0 : styleModel.interfaceContainer) || [
16
+ "position-absolute",
17
+ "z-1",
18
+ "bg-body",
19
+ "border",
20
+ "shadow",
21
+ "p-2",
22
+ ]), children: interfaces[option] })) })] }));
23
+ };
24
+ export default ToolbarContainer;
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from "react";
2
+ export type ToolbarOption = "filtering" | "exporting";
3
+ export type ToolbarInterfaces = Partial<Record<ToolbarOption, ReactNode>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { FilterOptionsTableProps } from "../filtering/FilterOptionsTable";
2
+ import { ToolbarInterfaces } from "./types";
3
+ import { ExportFormProps } from "../export/ExportForm";
4
+ export interface InterfaceParams {
5
+ filtering?: FilterOptionsTableProps;
6
+ exporting?: ExportFormProps;
7
+ }
8
+ declare const useInterfaces: (params: InterfaceParams) => ToolbarInterfaces;
9
+ export default useInterfaces;
@@ -0,0 +1,23 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import FilterOptionsTable from "../filtering/FilterOptionsTable";
14
+ import { useMemo } from "react";
15
+ import ExportForm from "../export/ExportForm";
16
+ var useInterfaces = function (_a) {
17
+ var filtering = _a.filtering, exporting = _a.exporting;
18
+ return useMemo(function () { return ({
19
+ filtering: filtering ? _jsx(FilterOptionsTable, __assign({}, filtering)) : undefined,
20
+ exporting: exporting ? _jsx(ExportForm, __assign({}, exporting)) : undefined,
21
+ }); }, [exporting, filtering]);
22
+ };
23
+ export default useInterfaces;