@absreim/react-bootstrap-data-grid 1.1.3 → 1.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/ColHeaderCell.jsx CHANGED
@@ -1,50 +1,12 @@
1
1
  "use client";
2
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
- if (ar || !(i in from)) {
5
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
- ar[i] = from[i];
7
- }
8
- }
9
- return to.concat(ar || Array.prototype.slice.call(from));
10
- };
11
- import { useState } from "react";
2
+ import downArrow from "./icons/downArrow";
3
+ import upArrow from "./icons/upArrow";
4
+ import arrowPlaceholder from "./icons/arrowPlaceholder";
12
5
  import classNames from "classnames";
13
- var getUpArrow = function (grayed) { return (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className={classNames(__spreadArray([
14
- "bi",
15
- "bi-arrow-up"
16
- ], (grayed ? ["text-body-secondary"] : []), true))} viewBox="0 0 16 16">
17
- {!grayed && (<>
18
- <title>(sorted ascending)</title>
19
- <desc>
20
- Up arrow indicating that the column is being sorted in an ascending
21
- manner
22
- </desc>
23
- </>)}
24
- <path fillRule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5"/>
25
- </svg>); };
26
- var downArrow = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-down" viewBox="0 0 16 16">
27
- <title>(sorted descending)</title>
28
- <desc>
29
- Down arrow indicating that the column is being sorted in an descending
30
- manner
31
- </desc>
32
- <path fillRule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1"/>
33
- </svg>);
34
- // Temporary solution to prevent column widths from changing when hovering over
35
- // columns with a mouse.
36
- // More ideal permanent solution would be to fix column widths based on preset
37
- // values.
38
- var placeholder = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-down" viewBox="0 0 16 16"></svg>);
6
+ import useControlledHover from "./hooks/useControlledHover";
39
7
  var ColHeaderCell = function (_a) {
40
8
  var label = _a.label, sortModel = _a.sortModel, ariaColIndex = _a.ariaColIndex;
41
- var _b = useState(false), isHovering = _b[0], setIsHovering = _b[1];
42
- var handleMouseOver = function () {
43
- return setIsHovering(true);
44
- };
45
- var handleMouseOut = function () {
46
- return setIsHovering(false);
47
- };
9
+ var _b = useControlledHover(), isHovering = _b.isHovering, handleMouseOver = _b.handleMouseOver, handleMouseOut = _b.handleMouseOut;
48
10
  var handleClick = function () {
49
11
  if (!sortModel) {
50
12
  return;
@@ -70,21 +32,24 @@ var ColHeaderCell = function (_a) {
70
32
  switch (sortModel.sortOrder) {
71
33
  case null: {
72
34
  if (isHovering) {
73
- return getUpArrow(true);
35
+ return upArrow(true);
74
36
  }
75
- return placeholder;
37
+ return arrowPlaceholder;
76
38
  }
77
39
  case "asc": {
78
- return getUpArrow(false);
40
+ return upArrow(false);
79
41
  }
80
42
  case "desc": {
81
43
  return downArrow;
82
44
  }
83
45
  }
84
46
  };
85
- return (<th onClick={sortModel && handleClick} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} aria-description={sortModel
47
+ return (<th className={classNames({
48
+ "cursor-pointer": sortModel,
49
+ "table-active": sortModel === null || sortModel === void 0 ? void 0 : sortModel.sortOrder,
50
+ })} onClick={sortModel && handleClick} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} aria-description={sortModel
86
51
  ? "Column header"
87
- : "Column header that can be clicked to change the sorting mode"} style={{ cursor: sortModel ? "pointer" : "default" }} aria-colindex={ariaColIndex}>
52
+ : "Column header that can be clicked to change the sorting mode"} aria-colindex={ariaColIndex}>
88
53
  {label}
89
54
  {getSortSymbol()}
90
55
  </th>);
package/Grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FC } from "react";
2
- import { ColDef, FilterModel, RowDef, Size, TableSortModel } from "./types";
2
+ import { ColDef, FilterModel, RowDef, SelectModel, Size, TableSortModel } from "./types";
3
3
  export interface GridPaginationState {
4
4
  pageSizeOptions: number[];
5
5
  pageSizeIndex: number;
@@ -15,6 +15,7 @@ export interface GridProps {
15
15
  pagination?: GridPaginationState;
16
16
  sortModel?: TableSortModel;
17
17
  filterModel?: FilterModel;
18
+ selectModel?: SelectModel;
18
19
  }
19
20
  declare const Grid: FC<GridProps>;
20
21
  export default Grid;
package/Grid.jsx CHANGED
@@ -1,7 +1,5 @@
1
1
  "use client";
2
2
  import { useMemo, useState } from "react";
3
- import Pagination from "./Pagination";
4
- import classNames from "classnames";
5
3
  import ColHeaderCell from "./ColHeaderCell";
6
4
  import useFilter from "./hooks/useFilter";
7
5
  import ToggleButton from "./ToggleButton";
@@ -10,8 +8,12 @@ import useFilterStateFromEditable from "./hooks/useFilterStateFromEditable";
10
8
  import useAugmentedRows from "./hooks/useAugmentedRows";
11
9
  import useSortedRows from "./hooks/useSortedRows";
12
10
  import useDisplayRows from "./hooks/useDisplayRows";
11
+ import SelectAllHeaderCell from "./selection/SelectAllHeaderCell";
12
+ import SelectionInput from "./selection/SelectionInput";
13
+ import Pagination from "./pagination/Pagination";
14
+ import classNames from "classnames";
13
15
  var Grid = function (_a) {
14
- var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel;
16
+ var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel, selectModel = _a.selectModel;
15
17
  var editableFilterState = (filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || null;
16
18
  var filterState = useFilterStateFromEditable(cols, editableFilterState);
17
19
  var augmentedRows = useAugmentedRows(rows);
@@ -29,20 +31,95 @@ var Grid = function (_a) {
29
31
  }, [sortedRows, pagination]);
30
32
  var displayRows = useDisplayRows(currentPageRows, cols);
31
33
  var _b = useState(false), filterOptionsVisible = _b[0], setFilterOptionsVisible = _b[1];
32
- var handleSetPageNum = function (pageNum) {
33
- if (pagination === undefined) {
34
+ var handleToggleFilterOptions = function () {
35
+ setFilterOptionsVisible(!filterOptionsVisible);
36
+ };
37
+ var getSelectionExists = function () {
38
+ if (!selectModel) {
39
+ return false;
40
+ }
41
+ if (selectModel.type === "single") {
42
+ return selectModel.selected !== null;
43
+ }
44
+ return selectModel.selected.length > 0;
45
+ };
46
+ var selectionExists = getSelectionExists();
47
+ var selectAllOnClick = function () {
48
+ if (!selectModel) {
49
+ return;
50
+ }
51
+ if (selectionExists && selectModel.type === "single") {
52
+ selectModel.setSelected(null);
34
53
  return;
35
54
  }
36
- pagination.setCurrentPage(pageNum);
55
+ if (selectionExists && selectModel.type === "multi") {
56
+ selectModel.setSelected([]);
57
+ return;
58
+ }
59
+ if (!selectionExists && selectModel.type === "multi") {
60
+ var allFilteredRowIndices = filteredRows.map(function (def) { return def.meta.origIndex; });
61
+ selectModel.setSelected(allFilteredRowIndices);
62
+ }
63
+ // Button should be disabled in the case of selectionExists &&
64
+ // selectModel.type === "single", so function execution should never get
65
+ // to this point.
37
66
  };
38
- var handleSetPageSize = function (event) {
39
- if (pagination === undefined) {
67
+ var getSelectHandler = function (index) { return function () {
68
+ if (!selectModel) {
69
+ return;
70
+ }
71
+ if (selectModel.type === "single") {
72
+ selectModel.setSelected(index);
40
73
  return;
41
74
  }
42
- pagination.setPageSizeIndex(Number(event.target.value));
75
+ selectModel.setSelected(selectModel.selected.concat(index));
76
+ }; };
77
+ var getDeselectHandler = function (index) { return function () {
78
+ if (!selectModel || selectModel.type === "single") {
79
+ return;
80
+ }
81
+ selectModel.setSelected(selectModel.selected.filter(function (num) { return num !== index; }));
82
+ }; };
83
+ // used to group radio buttons for selection
84
+ var getSelectInputModel = function (index, selectModel) {
85
+ if (selectModel.type === "single") {
86
+ return {
87
+ type: "radio",
88
+ name: selectModel.groupName,
89
+ };
90
+ }
91
+ return {
92
+ type: "checkbox",
93
+ deselectCallback: getDeselectHandler(index),
94
+ };
43
95
  };
44
- var handleToggleFilterOptions = function () {
45
- setFilterOptionsVisible(!filterOptionsVisible);
96
+ var showSelectCol = selectModel && selectModel.mode !== "row";
97
+ var selectedSet = new Set();
98
+ if (selectModel && selectModel.type === "multi") {
99
+ selectModel.selected.forEach(function (value) { return selectedSet.add(value); });
100
+ }
101
+ if (selectModel &&
102
+ selectModel.type === "single" &&
103
+ selectModel.selected !== null) {
104
+ selectedSet.add(selectModel.selected);
105
+ }
106
+ var rowsAreSelectable = !!(selectModel && selectModel.mode !== "column");
107
+ var getRowClickHandler = function (index) { return function (event) {
108
+ event.preventDefault();
109
+ if (!rowsAreSelectable) {
110
+ return;
111
+ }
112
+ if (selectedSet.has(index)) {
113
+ getDeselectHandler(index)();
114
+ return;
115
+ }
116
+ getSelectHandler(index)();
117
+ }; };
118
+ var getAriaSelectedValue = function (index) {
119
+ if (!selectModel) {
120
+ return undefined;
121
+ }
122
+ return String(selectedSet.has(index));
46
123
  };
47
124
  // Once this component implements selection state, and if such interactivity is enabled, (conditionally) change the
48
125
  // aria role to "grid".
@@ -52,9 +129,12 @@ var Grid = function (_a) {
52
129
  <ToggleButton isActive={filterOptionsVisible} label={"".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options")} onClick={handleToggleFilterOptions}/>
53
130
  {filterOptionsVisible && (<FilterOptionsTable filterState={filterState} setFilterState={filterModel.setTableFilterState}/>)}
54
131
  </div>)}
55
- <table className="table" aria-rowcount={filteredRows.length + 1}>
132
+ <table className={classNames("table", {
133
+ "table-hover": rowsAreSelectable,
134
+ })} aria-rowcount={filteredRows.length + 1}>
56
135
  <thead>
57
136
  <tr aria-rowindex={1}>
137
+ {showSelectCol && (<SelectAllHeaderCell selectType={selectModel.type} onClick={selectAllOnClick} selectionExists={selectionExists}/>)}
58
138
  {cols.map(function (_a, index) {
59
139
  var _b;
60
140
  var name = _a.name, label = _a.label, sortable = _a.sortable;
@@ -68,33 +148,24 @@ var Grid = function (_a) {
68
148
  },
69
149
  }
70
150
  : undefined;
71
- return (<ColHeaderCell key={name} label={label} sortModel={colSortModel} ariaColIndex={index + 1}/>);
151
+ return (<ColHeaderCell key={name} label={label} sortModel={colSortModel} ariaColIndex={index + 1 + (showSelectCol ? 1 : 0)}/>);
72
152
  })}
73
153
  </tr>
74
154
  </thead>
75
155
  <tbody>
76
- {displayRows.map(function (row, index) { return (<tr key={row.origIndex} aria-rowindex={index + 2}>
156
+ {displayRows.map(function (row, index) { return (<tr onClick={getRowClickHandler(row.origIndex)} className={classNames({
157
+ "table-active": selectedSet.has(row.origIndex),
158
+ })} key={row.origIndex} aria-rowindex={index + 2} data-rowindex={row.origIndex} aria-selected={getAriaSelectedValue(row.origIndex)}>
159
+ {showSelectCol && (<td>
160
+ <SelectionInput selected={selectedSet.has(row.origIndex)} selectionInputModel={getSelectInputModel(row.origIndex, selectModel)} selectCallback={getSelectHandler(row.origIndex)}/>
161
+ </td>)}
77
162
  {row.contents.map(function (value, index) { return (<td key={index} aria-colindex={index + 1}>
78
163
  {value}
79
164
  </td>); })}
80
165
  </tr>); })}
81
166
  </tbody>
82
167
  </table>
83
- {pagination && (<div className="d-flex justify-content-end gap-2">
84
- <div>
85
- <select className={classNames({
86
- "form-select": true,
87
- "form-select-lg": pagination.componentSize === "large",
88
- "form-select-sm": pagination.componentSize === "small",
89
- })} value={pagination.pageSizeIndex} aria-label="Number of Rows per Page" onChange={handleSetPageSize}>
90
- {pagination.pageSizeOptions.map(function (numRows, index) { return (<option key={index} value={index}>
91
- {numRows}
92
- </option>); })}
93
- </select>
94
- </div>
95
- <Pagination numPages={Math.ceil(rows.length /
96
- pagination.pageSizeOptions[pagination.pageSizeIndex])} pageNum={pagination.currentPage} numButtons={pagination.maxPageButtons} setPageNum={handleSetPageNum} size={pagination.componentSize || "medium"}/>
97
- </div>)}
168
+ {pagination && (<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}/>)}
98
169
  </div>);
99
170
  };
100
171
  export default Grid;
@@ -0,0 +1,9 @@
1
+ import { MouseEventHandler } from "react";
2
+ export interface UseControlledHoverHook<T> {
3
+ isHovering: boolean;
4
+ setIsHovering: (isHovering: boolean) => void;
5
+ handleMouseOver: MouseEventHandler<T>;
6
+ handleMouseOut: MouseEventHandler<T>;
7
+ }
8
+ declare const useControlledHover: <T>() => UseControlledHoverHook<T>;
9
+ export default useControlledHover;
@@ -0,0 +1,17 @@
1
+ import { useState } from "react";
2
+ var useControlledHover = function () {
3
+ var _a = useState(false), isHovering = _a[0], setIsHovering = _a[1];
4
+ var handleMouseOver = function () {
5
+ return setIsHovering(true);
6
+ };
7
+ var handleMouseOut = function () {
8
+ return setIsHovering(false);
9
+ };
10
+ return {
11
+ isHovering: isHovering,
12
+ setIsHovering: setIsHovering,
13
+ handleMouseOver: handleMouseOver,
14
+ handleMouseOut: handleMouseOut
15
+ };
16
+ };
17
+ export default useControlledHover;
@@ -0,0 +1,2 @@
1
+ declare const arrowPlaceholder: import("react").JSX.Element;
2
+ export default arrowPlaceholder;
@@ -0,0 +1,10 @@
1
+ // Temporary solution to prevent column widths from changing when hovering over
2
+ // columns with a mouse.
3
+ // More ideal permanent solution would be to fix column widths based on preset
4
+ // values.
5
+ var arrowPlaceholder = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
6
+ <desc>
7
+ Empty transparent square for styling purposes
8
+ </desc>
9
+ </svg>);
10
+ export default arrowPlaceholder;
@@ -0,0 +1,2 @@
1
+ declare const deselectAll: import("react").JSX.Element;
2
+ export default deselectAll;
@@ -0,0 +1,5 @@
1
+ var deselectAll = (<svg className="svg-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
2
+ <desc>Minus sign inside a square</desc>
3
+ <rect className="svg-icon-foreground" x="3.5" y="7.5" width="9" height="1"/>
4
+ </svg>);
5
+ export default deselectAll;
@@ -0,0 +1,2 @@
1
+ declare const downArrow: import("react").JSX.Element;
2
+ export default downArrow;
@@ -0,0 +1,9 @@
1
+ var downArrow = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
2
+ <title>(sorted descending)</title>
3
+ <desc>
4
+ Down arrow indicating that the column is being sorted in an descending
5
+ manner
6
+ </desc>
7
+ <path fillRule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1"/>
8
+ </svg>);
9
+ export default downArrow;
@@ -0,0 +1,2 @@
1
+ declare const selectAll: import("react").JSX.Element;
2
+ export default selectAll;
@@ -0,0 +1,6 @@
1
+ var selectAll = (<svg className="svg-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
2
+ <desc>Plus sign inside a square</desc>
3
+ <rect className="svg-icon-foreground" x="3.5" y="7.5" width="9" height="1"/>
4
+ <rect className="svg-icon-foreground" x="3.5" y="7.5" width="9" height="1" transform="translate(0 16) rotate(-90)"/>
5
+ </svg>);
6
+ export default selectAll;
@@ -0,0 +1,2 @@
1
+ declare const upArrow: (grayed: boolean) => import("react").JSX.Element;
2
+ export default upArrow;
@@ -0,0 +1,21 @@
1
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
2
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
3
+ if (ar || !(i in from)) {
4
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
5
+ ar[i] = from[i];
6
+ }
7
+ }
8
+ return to.concat(ar || Array.prototype.slice.call(from));
9
+ };
10
+ import classNames from "classnames";
11
+ var upArrow = function (grayed) { return (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className={classNames(__spreadArray([], (grayed ? ["text-body-secondary"] : []), true))} viewBox="0 0 16 16">
12
+ {!grayed && (<>
13
+ <title>(sorted ascending)</title>
14
+ <desc>
15
+ Up arrow indicating that the column is being sorted in an ascending
16
+ manner
17
+ </desc>
18
+ </>)}
19
+ <path fillRule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5"/>
20
+ </svg>); };
21
+ export default upArrow;
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./types";
2
2
  export * from "./Grid";
3
- export * from "./Pagination";
3
+ export * from "./pagination/PageSelector";
4
4
  export { default } from "./Grid";
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./types";
2
2
  export * from "./Grid";
3
- export * from "./Pagination";
3
+ export * from "./pagination/PageSelector";
4
4
  export { default } from "./Grid";
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": "1.1.3",
4
+ "version": "1.2.0",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -1,4 +1,4 @@
1
- import { JustifyContentSetting, Size } from "./types";
1
+ import { JustifyContentSetting, Size } from "../types";
2
2
  import { FC } from "react";
3
3
  export interface PaginationProps {
4
4
  numPages: number;
@@ -20,5 +20,5 @@ export interface PaginationProps {
20
20
  * @param alignment - Flexbox justify-content setting on the <ul> element
21
21
  * @param size - Size variant of the <ul> element
22
22
  */
23
- declare const Pagination: FC<PaginationProps>;
24
- export default Pagination;
23
+ declare const PageSelector: FC<PaginationProps>;
24
+ export default PageSelector;
@@ -12,7 +12,7 @@ import classNames from "classnames";
12
12
  * @param alignment - Flexbox justify-content setting on the <ul> element
13
13
  * @param size - Size variant of the <ul> element
14
14
  */
15
- var Pagination = function (_a) {
15
+ var PageSelector = function (_a) {
16
16
  var numPages = _a.numPages, numButtons = _a.numButtons, pageNum = _a.pageNum, setPageNum = _a.setPageNum, ariaLabel = _a.ariaLabel, alignment = _a.alignment, size = _a.size;
17
17
  var ulClasses = ["pagination"];
18
18
  if (size === "small") {
@@ -116,4 +116,4 @@ var Pagination = function (_a) {
116
116
  </ul>
117
117
  </nav>);
118
118
  };
119
- export default Pagination;
119
+ export default PageSelector;
@@ -0,0 +1,10 @@
1
+ import { FC } from "react";
2
+ import { Size } from "../types";
3
+ export interface PageSizeSelectorProps {
4
+ componentSize: Size;
5
+ pageSizeOptions: number[];
6
+ pageSizeIndex: number;
7
+ handleSetPageSize: (index: number) => void;
8
+ }
9
+ declare const PageSizeSelector: FC<PageSizeSelectorProps>;
10
+ export default PageSizeSelector;
@@ -0,0 +1,20 @@
1
+ import classNames from "classnames";
2
+ var PageSizeSelector = function (_a) {
3
+ var componentSize = _a.componentSize, pageSizeOptions = _a.pageSizeOptions, pageSizeIndex = _a.pageSizeIndex, handleSetPageSize = _a.handleSetPageSize;
4
+ var onChange = function (event) {
5
+ var pageSizeIndex = Number(event.target.value);
6
+ handleSetPageSize(pageSizeIndex);
7
+ };
8
+ return (<div>
9
+ <select className={classNames({
10
+ "form-select": true,
11
+ "form-select-lg": componentSize === "large",
12
+ "form-select-sm": componentSize === "small",
13
+ })} value={pageSizeIndex} aria-label="Number of Rows per Page" onChange={onChange}>
14
+ {pageSizeOptions.map(function (numRows, index) { return (<option key={index} value={index}>
15
+ {numRows}
16
+ </option>); })}
17
+ </select>
18
+ </div>);
19
+ };
20
+ export default PageSizeSelector;
@@ -0,0 +1,14 @@
1
+ import { FC } from "react";
2
+ import { Size } from "../types";
3
+ export interface PaginationProps {
4
+ componentSize: Size;
5
+ pageSizeOptions: number[];
6
+ pageSizeIndex: number;
7
+ handleSetPageSizeIndex: (index: number) => void;
8
+ handleSetPageNum: (index: number) => void;
9
+ prePagingNumRows: number;
10
+ maxPageButtons: number;
11
+ currentPage: number;
12
+ }
13
+ declare const Pagination: FC<PaginationProps>;
14
+ export default Pagination;
@@ -0,0 +1,22 @@
1
+ import PageSizeSelector from "./PageSizeSelector";
2
+ import PageSelector from "./PageSelector";
3
+ var Pagination = function (_a) {
4
+ 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;
5
+ var numPages = Math.ceil(prePagingNumRows / pageSizeOptions[pageSizeIndex]);
6
+ var pageIndexAwarePageSizeSetter = function (newPageSizeIndex) {
7
+ var newPageSize = pageSizeOptions[newPageSizeIndex];
8
+ var maxPages = Math.ceil(prePagingNumRows / newPageSize);
9
+ handleSetPageSizeIndex(newPageSizeIndex);
10
+ // The new page size can cause the current page number to be out of bounds.
11
+ // In that case, set the page num to the maximum possible with new page
12
+ // size.
13
+ if (currentPage > maxPages) {
14
+ handleSetPageNum(maxPages);
15
+ }
16
+ };
17
+ return (<div className="d-flex justify-content-end gap-2">
18
+ <PageSizeSelector componentSize={componentSize} pageSizeOptions={pageSizeOptions} pageSizeIndex={pageSizeIndex} handleSetPageSize={pageIndexAwarePageSizeSetter}/>
19
+ <PageSelector numPages={numPages} pageNum={currentPage} numButtons={maxPageButtons} setPageNum={handleSetPageNum} size={componentSize}/>
20
+ </div>);
21
+ };
22
+ export default Pagination;
@@ -0,0 +1,9 @@
1
+ import { SelectType } from "../types";
2
+ import { FC } from "react";
3
+ interface SelectAllHeaderCellProps {
4
+ onClick: () => void;
5
+ selectType: SelectType;
6
+ selectionExists: boolean;
7
+ }
8
+ declare const SelectAllHeaderCell: FC<SelectAllHeaderCellProps>;
9
+ export default SelectAllHeaderCell;
@@ -0,0 +1,42 @@
1
+ import deselectAll from "../icons/deselectAll";
2
+ import selectAll from "../icons/selectAll";
3
+ import arrowPlaceholder from "../icons/arrowPlaceholder";
4
+ import classNames from "classnames";
5
+ // It seems like React does not support setting indeterminate states on
6
+ // checkboxes in a controlled manner, which caused me to not want to refactor
7
+ // this feature to use a checkbox input instead of SVG icons. I am not sure how
8
+ // much of an issue using an uncontrolled input would be, but because at time of
9
+ // this writing I had already implemented a solution with SVG, on balance I felt
10
+ // like it was not worth going out of my way to change things up to use an
11
+ // uncontrolled checkbox input.
12
+ var getSelectIcon = function (selectMode, existingSelection) {
13
+ if (existingSelection) {
14
+ return deselectAll;
15
+ }
16
+ if (selectMode === "multi" && !existingSelection) {
17
+ return selectAll;
18
+ }
19
+ // Single select mode and none selected means that the button is disabled
20
+ return arrowPlaceholder;
21
+ };
22
+ var getCellAriaDescription = function (selectMode, existingSelection) {
23
+ if (existingSelection) {
24
+ return "Deselect all rows";
25
+ }
26
+ if (selectMode === "multi" && !existingSelection) {
27
+ return "Select all rows";
28
+ }
29
+ // Single select mode and none selected means that the button is disabled
30
+ return "Selection input header cell";
31
+ };
32
+ var SelectAllHeaderCell = function (_a) {
33
+ var onClick = _a.onClick, selectType = _a.selectType, selectionExists = _a.selectionExists;
34
+ var disabled = selectType === "single" && !selectionExists;
35
+ var description = getCellAriaDescription(selectType, selectionExists);
36
+ return (<th aria-colindex={1} title={description} aria-description={description} className={classNames("select-header-cell", "btn-primary", {
37
+ "cursor-pointer": !disabled,
38
+ })} onClick={onClick}>
39
+ {getSelectIcon(selectType, selectionExists)}
40
+ </th>);
41
+ };
42
+ export default SelectAllHeaderCell;
@@ -0,0 +1,18 @@
1
+ import { FC } from "react";
2
+ export interface RadioSelectionInputModel {
3
+ type: "radio";
4
+ name: string;
5
+ }
6
+ export interface CheckboxSelectionInputModel {
7
+ type: "checkbox";
8
+ deselectCallback: () => void;
9
+ name?: string;
10
+ }
11
+ export type SelectionInputModel = RadioSelectionInputModel | CheckboxSelectionInputModel;
12
+ export interface SelectionInputProps {
13
+ selected: boolean;
14
+ selectionInputModel: SelectionInputModel;
15
+ selectCallback: () => void;
16
+ }
17
+ declare const SelectionInput: FC<SelectionInputProps>;
18
+ export default SelectionInput;
@@ -0,0 +1,21 @@
1
+ var SelectionInput = function (_a) {
2
+ var selectionInputModel = _a.selectionInputModel, selected = _a.selected, selectCallback = _a.selectCallback;
3
+ var type = selectionInputModel.type;
4
+ var onChange = function (_a) {
5
+ var checked = _a.target.checked;
6
+ // theoretically, a radio button shouldn't become unchecked so the below
7
+ // check of the "type" variable is not needed except for narrowing the
8
+ // type of the "selectionInputModel"
9
+ if (!checked && type === "checkbox") {
10
+ selectionInputModel.deselectCallback();
11
+ return;
12
+ }
13
+ if (checked) {
14
+ selectCallback();
15
+ }
16
+ };
17
+ return (<input onClick={function (event) {
18
+ event.stopPropagation();
19
+ }} type={type} checked={selected} onChange={onChange} name={selectionInputModel.name}/>);
20
+ };
21
+ export default SelectionInput;
package/types.d.ts CHANGED
@@ -85,3 +85,19 @@ export interface FilterModel {
85
85
  tableFilterState: EditableTableFilterState;
86
86
  setTableFilterState: (state: EditableTableFilterState) => void;
87
87
  }
88
+ export type SelectMode = "column" | "row" | "both";
89
+ export type SelectType = "single" | "multi";
90
+ export interface MultiSelectModel {
91
+ mode: SelectMode;
92
+ type: "multi";
93
+ selected: number[];
94
+ setSelected: (selected: number[]) => void;
95
+ }
96
+ export interface SingleSelectModel {
97
+ mode: SelectMode;
98
+ type: "single";
99
+ selected: number | null;
100
+ setSelected: (selected: number | null) => void;
101
+ groupName: string;
102
+ }
103
+ export type SelectModel = SingleSelectModel | MultiSelectModel;
@@ -1,2 +0,0 @@
1
- import { ColDataTypeStrings } from "../types";
2
- export declare const colTypeDescs: Record<ColDataTypeStrings, string>;
@@ -1,6 +0,0 @@
1
- export var colTypeDescs = {
2
- string: "String",
3
- number: "Number",
4
- date: "Date",
5
- datetime: "DateTime",
6
- };