@absreim/react-bootstrap-data-grid 0.1.2 → 0.1.4

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.
@@ -0,0 +1,8 @@
1
+ import { FC } from "react";
2
+ import { ColSortModel } from "./types";
3
+ interface ColHeaderCellProps {
4
+ label: string;
5
+ sortModel?: ColSortModel;
6
+ }
7
+ declare const ColHeaderCell: FC<ColHeaderCellProps>;
8
+ export default ColHeaderCell;
@@ -0,0 +1,75 @@
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";
12
+ import classNames from "classnames";
13
+ var getUpArrow = function (addlClasses) { return (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className={classNames(__spreadArray(["bi", "bi-arrow-up"], (addlClasses || []), true))} viewBox="0 0 16 16">
14
+ <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"/>
15
+ </svg>); };
16
+ 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">
17
+ <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"/>
18
+ </svg>);
19
+ // Temporary solution to prevent column widths from changing when hovering over
20
+ // columns with a mouse.
21
+ // More ideal permanent solution would be to fix column widths based on preset
22
+ // values.
23
+ 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>);
24
+ var ColHeaderCell = function (_a) {
25
+ var label = _a.label, sortModel = _a.sortModel;
26
+ var _b = useState(false), isHovering = _b[0], setIsHovering = _b[1];
27
+ var handleMouseOver = function () {
28
+ return setIsHovering(true);
29
+ };
30
+ var handleMouseOut = function () {
31
+ return setIsHovering(false);
32
+ };
33
+ var handleClick = function () {
34
+ if (!sortModel) {
35
+ return;
36
+ }
37
+ switch (sortModel.sortOrder) {
38
+ case null: {
39
+ sortModel.setSortOrder("asc");
40
+ return;
41
+ }
42
+ case "asc": {
43
+ sortModel.setSortOrder("desc");
44
+ return;
45
+ }
46
+ case "desc": {
47
+ sortModel.setSortOrder(null);
48
+ }
49
+ }
50
+ };
51
+ var getSortSymbol = function () {
52
+ if (!sortModel) {
53
+ return null;
54
+ }
55
+ switch (sortModel.sortOrder) {
56
+ case null: {
57
+ if (isHovering) {
58
+ return getUpArrow(["text-body-secondary"]);
59
+ }
60
+ return placeholder;
61
+ }
62
+ case "asc": {
63
+ return getUpArrow();
64
+ }
65
+ case "desc": {
66
+ return downArrow;
67
+ }
68
+ }
69
+ };
70
+ return (<th onClick={sortModel && handleClick} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} aria-description="Column header that can be clicked to change the sorting mode" style={{ cursor: "pointer " }}>
71
+ {label}
72
+ {getSortSymbol()}
73
+ </th>);
74
+ };
75
+ export default ColHeaderCell;
package/Grid.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { FC } from "react";
2
+ import { ColDef, RowDef, Size, TableSortModel } from "./types";
3
+ export interface GridPaginationState {
4
+ pageSizeOptions: number[];
5
+ pageSizeIndex: number;
6
+ setPageSizeIndex: (pageSizeIndex: number) => void;
7
+ currentPage: number;
8
+ setCurrentPage: (pageNum: number) => void;
9
+ maxPageButtons: number;
10
+ componentSize?: Size;
11
+ }
12
+ export interface GridProps {
13
+ rows: RowDef[];
14
+ cols: ColDef[];
15
+ pagination?: GridPaginationState;
16
+ sortModel?: TableSortModel;
17
+ }
18
+ declare const Grid: FC<GridProps>;
19
+ export default Grid;
package/Grid.jsx ADDED
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ import Pagination from "./Pagination";
4
+ import classNames from "classnames";
5
+ import ColHeaderCell from "./ColHeaderCell";
6
+ var getTypeComparator = function (typeStr) {
7
+ if (typeStr === "date" || typeStr === "datetime") {
8
+ return function (a, b) { return a.valueOf() - b.valueOf(); };
9
+ }
10
+ if (typeStr === "number") {
11
+ return function (a, b) { return a - b; };
12
+ }
13
+ return function (a, b) {
14
+ if (a < b) {
15
+ return -1;
16
+ }
17
+ if (a > b) {
18
+ return 1;
19
+ }
20
+ return 0;
21
+ };
22
+ };
23
+ var getRowComparator = function (comparator, fieldName) {
24
+ return function (rowA, rowB) { return comparator(rowA[fieldName], rowB[fieldName]); };
25
+ };
26
+ var Grid = function (_a) {
27
+ var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel;
28
+ var sortedRows = useMemo(function () {
29
+ if (!sortModel || !sortModel.sortColDef) {
30
+ return rows;
31
+ }
32
+ var sortFieldName = sortModel.sortColDef.name;
33
+ var sortOrder = sortModel.sortColDef.order;
34
+ var sortColIndex = cols.findIndex(function (_a) {
35
+ var name = _a.name;
36
+ return name === sortFieldName;
37
+ });
38
+ if (sortColIndex < 0) {
39
+ throw new Error("The sortModel for the grid specifies that the data should be sorted based on a column named ".concat(sortFieldName, ", but it was not found among the column definitions."));
40
+ }
41
+ var typeStr = cols[sortColIndex].type;
42
+ var ascComparator = getTypeComparator(typeStr);
43
+ var rowComparator = getRowComparator(ascComparator, sortFieldName);
44
+ if (sortOrder === "desc") {
45
+ var descComparator = function (a, b) {
46
+ return ascComparator(a, b) * -1;
47
+ };
48
+ rowComparator = getRowComparator(descComparator, sortFieldName);
49
+ }
50
+ return rows.slice().sort(rowComparator);
51
+ }, [rows, cols, sortModel]);
52
+ var currentPageRows = useMemo(function () {
53
+ if (pagination === undefined) {
54
+ return sortedRows;
55
+ }
56
+ var pageSizeOptions = pagination.pageSizeOptions, pageSizeIndex = pagination.pageSizeIndex, currentPage = pagination.currentPage;
57
+ var pageSize = pageSizeOptions[pageSizeIndex];
58
+ var lowerIndex = pageSize * (currentPage - 1);
59
+ var upperIndex = lowerIndex + pageSize;
60
+ return sortedRows.slice(lowerIndex, upperIndex);
61
+ }, [sortedRows, pagination]);
62
+ var displayRows = useMemo(function () {
63
+ var nameToIndex = new Map();
64
+ cols.forEach(function (_a, index) {
65
+ var name = _a.name;
66
+ nameToIndex.set(name, index);
67
+ });
68
+ return currentPageRows.map(function (row, index) {
69
+ cols
70
+ .map(function (_a) {
71
+ var name = _a.name;
72
+ return name;
73
+ })
74
+ .forEach(function (name) {
75
+ if (!(name in row)) {
76
+ throw new Error("Column definition specifies a property named \"".concat(name, "\", but it was not found in the row data object at index ").concat(index, "."));
77
+ }
78
+ });
79
+ var displayRow = [];
80
+ Object.keys(row).forEach(function (name) {
81
+ if (!nameToIndex.has(name)) {
82
+ throw new Error("Row data contains a property named \"".concat(name, "\", but it was not found among the column definitions."));
83
+ }
84
+ var index = nameToIndex.get(name);
85
+ var formatter = cols[index].formatter;
86
+ var typeString = cols[index].type;
87
+ var value = row[name];
88
+ if (formatter) {
89
+ displayRow[index] = formatter(value);
90
+ return;
91
+ }
92
+ if (typeString === "date") {
93
+ displayRow[index] = value.toDateString();
94
+ return;
95
+ }
96
+ if (typeString === "datetime") {
97
+ displayRow[index] = value.toLocaleString();
98
+ return;
99
+ }
100
+ if (typeString === "number") {
101
+ displayRow[index] = value.toLocaleString();
102
+ return;
103
+ }
104
+ displayRow[index] = value;
105
+ });
106
+ return displayRow;
107
+ });
108
+ }, [currentPageRows, cols]);
109
+ var handleSetPageNum = function (pageNum) {
110
+ if (pagination === undefined) {
111
+ return;
112
+ }
113
+ pagination.setCurrentPage(pageNum);
114
+ };
115
+ var handleSetPageSize = function (event) {
116
+ if (pagination === undefined) {
117
+ return;
118
+ }
119
+ pagination.setPageSizeIndex(Number(event.target.value));
120
+ };
121
+ // Once this component implements selection state, and if such interactivity is enabled, (conditionally) change the
122
+ // aria role to "grid".
123
+ // Array index is okay for the key for rows until some type of feature involving changing the index of rows, such as
124
+ // sorting or pagination, is implemented.
125
+ return (<div>
126
+ <table className="table">
127
+ <thead>
128
+ <tr>
129
+ {cols.map(function (_a) {
130
+ var _b;
131
+ var name = _a.name, label = _a.label, sortable = _a.sortable;
132
+ var colSortModel = sortModel && sortable
133
+ ? {
134
+ sortOrder: ((_b = sortModel.sortColDef) === null || _b === void 0 ? void 0 : _b.name) === name
135
+ ? sortModel.sortColDef.order
136
+ : null,
137
+ setSortOrder: function (order) {
138
+ sortModel.setSortColDef(order && { name: name, order: order });
139
+ },
140
+ }
141
+ : undefined;
142
+ return (<ColHeaderCell key={name} label={label} sortModel={colSortModel}/>);
143
+ })}
144
+ </tr>
145
+ </thead>
146
+ <tbody>
147
+ {displayRows.map(function (row, index) { return (<tr key={index}>
148
+ {row.map(function (value, index) { return (<td key={index}>{value}</td>); })}
149
+ </tr>); })}
150
+ </tbody>
151
+ </table>
152
+ {pagination && (<div className="d-flex justify-content-end gap-2">
153
+ <div>
154
+ <select className={classNames({
155
+ "form-select": true,
156
+ "form-select-lg": pagination.componentSize === "large",
157
+ "form-select-sm": pagination.componentSize === "small",
158
+ })} value={pagination.pageSizeIndex} aria-label="Number of Rows per Page" onChange={handleSetPageSize}>
159
+ {pagination.pageSizeOptions.map(function (numRows, index) { return (<option key={index} value={index}>
160
+ {numRows}
161
+ </option>); })}
162
+ </select>
163
+ </div>
164
+ <Pagination numPages={Math.ceil(rows.length /
165
+ pagination.pageSizeOptions[pagination.pageSizeIndex])} pageNum={pagination.currentPage} numButtons={pagination.maxPageButtons} setPageNum={handleSetPageNum} size={pagination.componentSize || "medium"}/>
166
+ </div>)}
167
+ </div>);
168
+ };
169
+ export default Grid;
@@ -0,0 +1,24 @@
1
+ import { JustifyContentSetting, Size } from "./types";
2
+ import { FC } from "react";
3
+ export interface PaginationProps {
4
+ numPages: number;
5
+ pageNum: number;
6
+ numButtons: number;
7
+ setPageNum: (pageNum: number) => void;
8
+ ariaLabel?: string;
9
+ alignment?: JustifyContentSetting;
10
+ size?: Size;
11
+ }
12
+ /**
13
+ * An interactive pagination component that parameterizes Bootstrap styling
14
+ * options as props.
15
+ * @param numPages - Total number of pages
16
+ * @param numButtons - Number of buttons of numerical indices to show at once
17
+ * @param pageNum - The currently selected page
18
+ * @param setPageNum - Callback function to set the selected page
19
+ * @param ariaLabel - Aria label of the <nav> element
20
+ * @param alignment - Flexbox justify-content setting on the <ul> element
21
+ * @param size - Size variant of the <ul> element
22
+ */
23
+ declare const Pagination: FC<PaginationProps>;
24
+ export default Pagination;
package/Pagination.jsx ADDED
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ import classNames from "classnames";
4
+ /**
5
+ * An interactive pagination component that parameterizes Bootstrap styling
6
+ * options as props.
7
+ * @param numPages - Total number of pages
8
+ * @param numButtons - Number of buttons of numerical indices to show at once
9
+ * @param pageNum - The currently selected page
10
+ * @param setPageNum - Callback function to set the selected page
11
+ * @param ariaLabel - Aria label of the <nav> element
12
+ * @param alignment - Flexbox justify-content setting on the <ul> element
13
+ * @param size - Size variant of the <ul> element
14
+ */
15
+ var Pagination = function (_a) {
16
+ var numPages = _a.numPages, numButtons = _a.numButtons, pageNum = _a.pageNum, setPageNum = _a.setPageNum, ariaLabel = _a.ariaLabel, alignment = _a.alignment, size = _a.size;
17
+ var ulClasses = ["pagination"];
18
+ if (size === "small") {
19
+ ulClasses.push("pagination-sm");
20
+ }
21
+ else if (size === "large") {
22
+ ulClasses.push("pagination-lg");
23
+ }
24
+ if (alignment) {
25
+ ulClasses.push("justify-content-".concat(alignment));
26
+ }
27
+ var lowerBound = pageNum - Math.floor((numButtons - 1) / 2);
28
+ var upperBound = pageNum + Math.ceil((numButtons - 1) / 2);
29
+ if (upperBound > numPages) {
30
+ var diff = upperBound - numPages;
31
+ lowerBound = Math.max(lowerBound - diff, 1);
32
+ upperBound -= diff;
33
+ }
34
+ else if (lowerBound < 1) {
35
+ var diff = 1 - lowerBound;
36
+ lowerBound = 1;
37
+ upperBound = Math.min(numPages, upperBound + diff);
38
+ }
39
+ var buttonIndices = useMemo(function () {
40
+ var indices = [];
41
+ for (var i = lowerBound; i <= upperBound; i++) {
42
+ indices.push(i);
43
+ }
44
+ return indices;
45
+ }, [lowerBound, upperBound]);
46
+ function getFirstArrowButton() {
47
+ if (lowerBound === 1) {
48
+ return null;
49
+ }
50
+ return (<li className="page-item">
51
+ <a className="page-link" href="#" aria-label="First" onClick={function (event) {
52
+ event.preventDefault();
53
+ setPageNum(1);
54
+ }}>
55
+ <span aria-hidden="true">&lt;&lt;</span>
56
+ </a>
57
+ </li>);
58
+ }
59
+ function getPrevArrowButton() {
60
+ if (pageNum === 1) {
61
+ return null;
62
+ }
63
+ return (<li className="page-item">
64
+ <a className="page-link" href="#" aria-label="Previous" onClick={function (event) {
65
+ event.preventDefault();
66
+ setPageNum(pageNum - 1);
67
+ }}>
68
+ <span aria-hidden="true">&lt;</span>
69
+ </a>
70
+ </li>);
71
+ }
72
+ function getNextArrowButton() {
73
+ if (pageNum === numPages) {
74
+ return null;
75
+ }
76
+ return (<li className="page-item">
77
+ <a className="page-link" href="#" aria-label="Next" onClick={function (event) {
78
+ event.preventDefault();
79
+ setPageNum(pageNum + 1);
80
+ }}>
81
+ <span aria-hidden="true">&gt;</span>
82
+ </a>
83
+ </li>);
84
+ }
85
+ function getLastArrowButton() {
86
+ if (upperBound === numPages) {
87
+ return null;
88
+ }
89
+ return (<li className="page-item">
90
+ <a className="page-link" href="#" aria-label="Last" onClick={function (event) {
91
+ event.preventDefault();
92
+ setPageNum(numPages);
93
+ }}>
94
+ <span aria-hidden="true">&gt;&gt;</span>
95
+ </a>
96
+ </li>);
97
+ }
98
+ var indexButtons = buttonIndices.map(function (buttonIndex) { return (<li key={buttonIndex} className={classNames({
99
+ "page-item": true,
100
+ active: pageNum === buttonIndex,
101
+ })} aria-current={pageNum === buttonIndex ? "page" : undefined}>
102
+ <a className="page-link" href="#" onClick={function (event) {
103
+ event.preventDefault();
104
+ setPageNum(buttonIndex);
105
+ }}>
106
+ {buttonIndex}
107
+ </a>
108
+ </li>); });
109
+ return (<nav aria-label={ariaLabel}>
110
+ <ul className={classNames(ulClasses)}>
111
+ {getFirstArrowButton()}
112
+ {getPrevArrowButton()}
113
+ {indexButtons}
114
+ {getNextArrowButton()}
115
+ {getLastArrowButton()}
116
+ </ul>
117
+ </nav>);
118
+ };
119
+ export default Pagination;
package/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./types";
2
- export { default } from "./component";
2
+ export * from "./Grid";
3
+ export * from "./Pagination";
4
+ export { default } from "./Grid";
package/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./types";
2
- export { default } from "./component";
2
+ export * from "./Grid";
3
+ export * from "./Pagination";
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": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -13,6 +13,9 @@
13
13
  "table",
14
14
  "grid"
15
15
  ],
16
+ "dependencies": {
17
+ "classnames": "^2.5.1"
18
+ },
16
19
  "peerDependencies": {
17
20
  "react": "^18",
18
21
  "react-dom": "^18"
@@ -22,7 +25,7 @@
22
25
  "types": "./index.d.ts",
23
26
  "repository": {
24
27
  "type": "git",
25
- "url": "https://github.com/absreim/react-bootstrap-data-grid.git",
28
+ "url": "git+https://github.com/absreim/react-bootstrap-data-grid.git",
26
29
  "directory": "src/grid"
27
30
  }
28
- }
31
+ }
package/types.d.ts CHANGED
@@ -1,10 +1,25 @@
1
- type ColDataType = string | number | Date;
2
- type ColDataTypeStrings = "string" | "number" | "date" | "datetime";
1
+ export type ColDataType = string | number | Date;
2
+ export type ColDataTypeStrings = "string" | "number" | "date" | "datetime";
3
3
  export interface ColDef {
4
4
  type: ColDataTypeStrings;
5
5
  name: string;
6
6
  label: string;
7
7
  formatter?: (value: any) => string;
8
+ sortable?: boolean;
8
9
  }
9
10
  export type RowDef = Record<string, ColDataType>;
10
- export {};
11
+ export type JustifyContentSetting = "start" | "end" | "center" | "between" | "around" | "evenly";
12
+ export type Size = "small" | "medium" | "large";
13
+ export type SortOrder = "asc" | "desc";
14
+ export interface SortColDef {
15
+ name: string;
16
+ order: SortOrder;
17
+ }
18
+ export interface ColSortModel {
19
+ sortOrder: SortOrder | null;
20
+ setSortOrder: (order: SortOrder | null) => void;
21
+ }
22
+ export interface TableSortModel {
23
+ sortColDef: SortColDef | null;
24
+ setSortColDef: (sortColDef: SortColDef | null) => void;
25
+ }