@proteinjs/ui 4.1.0 → 4.1.1

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.
Files changed (41) hide show
  1. package/dist/generated/index.js +1 -1
  2. package/dist/generated/index.js.map +1 -1
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/container/PageContainer.d.ts +17 -3
  8. package/dist/src/container/PageContainer.d.ts.map +1 -1
  9. package/dist/src/container/PageContainer.js +9 -4
  10. package/dist/src/container/PageContainer.js.map +1 -1
  11. package/dist/src/formatters.d.ts +37 -0
  12. package/dist/src/formatters.d.ts.map +1 -0
  13. package/dist/src/formatters.js +54 -0
  14. package/dist/src/formatters.js.map +1 -0
  15. package/dist/src/router/Page.d.ts +0 -2
  16. package/dist/src/router/Page.d.ts.map +1 -1
  17. package/dist/src/router/Router.d.ts.map +1 -1
  18. package/dist/src/router/Router.js +3 -4
  19. package/dist/src/router/Router.js.map +1 -1
  20. package/dist/src/table/InfiniteScroll.d.ts.map +1 -1
  21. package/dist/src/table/InfiniteScroll.js +4 -7
  22. package/dist/src/table/InfiniteScroll.js.map +1 -1
  23. package/dist/src/table/Table.d.ts +10 -4
  24. package/dist/src/table/Table.d.ts.map +1 -1
  25. package/dist/src/table/Table.js +31 -17
  26. package/dist/src/table/Table.js.map +1 -1
  27. package/dist/src/table/tableData.d.ts +7 -3
  28. package/dist/src/table/tableData.d.ts.map +1 -1
  29. package/dist/src/table/tableData.js +10 -7
  30. package/dist/src/table/tableData.js.map +1 -1
  31. package/generated/index.ts +1 -1
  32. package/index.ts +2 -0
  33. package/package.json +5 -6
  34. package/src/container/PageContainer.tsx +34 -5
  35. package/src/formatters.ts +69 -0
  36. package/src/router/Page.ts +0 -1
  37. package/src/router/Router.tsx +4 -5
  38. package/src/table/InfiniteScroll.tsx +4 -6
  39. package/src/table/Table.tsx +66 -22
  40. package/src/table/tableData.ts +15 -7
  41. package/LICENSE +0 -21
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
3
  import {
4
4
  TableContainer,
@@ -14,6 +14,8 @@ import {
14
14
  TableContainerOwnProps,
15
15
  ToolbarProps,
16
16
  CircularProgress,
17
+ TablePaginationProps,
18
+ TableCellProps,
17
19
  } from '@mui/material';
18
20
  import moment from 'moment';
19
21
  import { StringUtil } from '@proteinjs/util';
@@ -27,13 +29,16 @@ type ColumnValue<T, K extends keyof T> = T[K];
27
29
  export type CustomRenderer<T, K extends keyof T> = (value: ColumnValue<T, K>, row: T) => React.ReactNode;
28
30
  export type ColumnConfig<T> = {
29
31
  [K in keyof T]?: {
32
+ cellProps?: TableCellProps;
30
33
  renderer?: CustomRenderer<T, K>;
31
34
  /** If no header is provided, a default header will be used. Pass in `null` if you'd like the header to be omitted. */
32
35
  header?: string | React.ReactNode;
33
36
  };
34
37
  };
35
38
 
36
- type RowClickAction<T> = string | ((row: T) => void | Promise<void> | string | Promise<string>);
39
+ type RowClickAction<T> =
40
+ | string
41
+ | ((row: T, event?: React.MouseEvent) => void | Promise<void> | string | Promise<string>);
37
42
 
38
43
  export type TableProps<T> = {
39
44
  title?: string;
@@ -42,6 +47,7 @@ export type TableProps<T> = {
42
47
  columnConfig?: ColumnConfig<T>;
43
48
  hideColumnHeaders?: boolean;
44
49
  tableLoader: TableLoader<T>;
50
+ refetchOnWindowFocus?: boolean;
45
51
  /** Setter which will be used to update the row count when rows are loaded */
46
52
  setRowCount?: React.Dispatch<React.SetStateAction<number | undefined>>;
47
53
  rowOnClick?: RowClickAction<T>;
@@ -49,8 +55,10 @@ export type TableProps<T> = {
49
55
  buttons?: TableButton<T>[];
50
56
  /** If true, use pagination for table page navigation, if false uses infinite scroll. Defaults to false. */
51
57
  pagination?: boolean;
52
- /* Pertains to pagination or infinite scroll, depending on which is enabled. */
53
- defaultRowsPerPage?: number;
58
+ /** Props passed into the TablePagination component. This component is only displayed if `pagination` is true. */
59
+ tablePaginationProps?: Partial<TablePaginationProps>;
60
+ /* Number of rows that are loaded per page. */
61
+ rowsPerPage?: number;
54
62
  /* Styling set on the root element of the toolbar. */
55
63
  toolbarSx?: ToolbarProps['sx'];
56
64
  /* Content that will be displayed in the toolbar section of the table. */
@@ -59,8 +67,15 @@ export type TableProps<T> = {
59
67
  tableContainerSx?: TableContainerOwnProps['sx'];
60
68
  /* Component displayed when there are no rows to display. */
61
69
  emptyTableComponent?: React.ReactNode;
62
- /* Loading skeleton that's displayed before the table rows are first fetched. */
70
+ /* Loading skeleton that's displayed before the table rows are first fetched.\
71
+ * You can use these class names to target the containers with styling:
72
+ * - `loading-skeleton-table-body`
73
+ * - `loading-skeleton-row`
74
+ * - `loading-skeleton-cell`
75
+ */
63
76
  skeleton?: React.ReactNode;
77
+ /** Loader to display while items are fetching. Only applicable when pagination prop is false. */
78
+ infiniteScrollLoader?: React.ReactNode;
64
79
  };
65
80
 
66
81
  export function Table<T>({
@@ -70,26 +85,43 @@ export function Table<T>({
70
85
  columnConfig = {},
71
86
  hideColumnHeaders = false,
72
87
  tableLoader,
88
+ refetchOnWindowFocus = false,
73
89
  rowOnClick,
74
90
  setRowCount,
75
91
  pagination = false,
76
- defaultRowsPerPage = 10,
92
+ tablePaginationProps,
93
+ rowsPerPage: rowsPerPageProp = 10,
77
94
  buttons,
78
95
  tableContainerSx,
79
96
  toolbarSx,
80
97
  toolbarContent,
81
98
  emptyTableComponent,
82
99
  skeleton,
100
+ infiniteScrollLoader,
83
101
  }: TableProps<T>) {
84
102
  const infiniteScroll = !pagination;
85
- const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage);
103
+ const [rowsPerPage, setRowsPerPage] = useState(rowsPerPageProp);
86
104
  const [page, setPage] = useState(0);
87
105
  const [selectedRows, setSelectedRows] = useState<{ [key: number]: T }>({});
88
106
  const [selectAll, setSelectAll] = useState(false);
107
+ const infScrollContainerRef = useRef<HTMLDivElement>(null);
89
108
  const navigate = useNavigate();
90
109
 
91
110
  const { rows, totalRows, isLoading, error, fetchNextPage, hasNextPage, isFetchingNextPage, resetQuery } =
92
- useTableData<T>(tableLoader, rowsPerPage, page, infiniteScroll, setRowCount);
111
+ useTableData<T>(tableLoader, rowsPerPage, page, infiniteScroll, setRowCount, refetchOnWindowFocus);
112
+
113
+ const maxPage = Math.max(0, Math.ceil((totalRows || 0) / rowsPerPage) - 1);
114
+
115
+ // Adjust page if it exceeds maxPage
116
+ useEffect(() => {
117
+ if (!pagination || isLoading) {
118
+ return;
119
+ }
120
+
121
+ if (page > maxPage && maxPage >= 0) {
122
+ setPage(maxPage);
123
+ }
124
+ }, [page, maxPage, pagination, isLoading, totalRows]);
93
125
 
94
126
  useEffect(() => {
95
127
  resetQuery();
@@ -115,7 +147,12 @@ export function Table<T>({
115
147
  }
116
148
  }, [fetchNextPage, isFetchingNextPage]);
117
149
 
118
- async function handleRowOnClick<T>(row: T, action: RowClickAction<T>, navigate: (url: string) => void) {
150
+ async function handleRowOnClick<T>(
151
+ row: T,
152
+ event: React.MouseEvent,
153
+ action: RowClickAction<T>,
154
+ navigate: (url: string) => void
155
+ ) {
119
156
  if (!action) {
120
157
  return;
121
158
  }
@@ -131,7 +168,7 @@ export function Table<T>({
131
168
  }
132
169
 
133
170
  // If action is a function, execute it
134
- const result = action(row);
171
+ const result = action(row, event);
135
172
 
136
173
  if (result instanceof Promise) {
137
174
  // If the result is a Promise, wait for it to resolve
@@ -250,9 +287,9 @@ export function Table<T>({
250
287
  </TableRow>
251
288
  </TableHead>
252
289
  {isLoading && (
253
- <TableBody>
254
- <TableRow>
255
- <TableCell colSpan={totalColumns} align='center' sx={{ py: 3 }}>
290
+ <TableBody className='loading-skeleton-table-body'>
291
+ <TableRow className='loading-skeleton-row'>
292
+ <TableCell colSpan={totalColumns} align='center' className='loading-skeleton-cell' sx={{ py: 3 }}>
256
293
  {skeleton ? skeleton : <CircularProgress />}
257
294
  </TableCell>
258
295
  </TableRow>
@@ -278,7 +315,11 @@ export function Table<T>({
278
315
  tabIndex={-1}
279
316
  key={index}
280
317
  selected={isSelected}
281
- onClick={rowOnClick ? () => handleRowOnClick(row, rowOnClick, navigate) : undefined}
318
+ onClick={
319
+ rowOnClick
320
+ ? (event: React.MouseEvent) => handleRowOnClick(row, event, rowOnClick, navigate)
321
+ : undefined
322
+ }
282
323
  >
283
324
  {buttons && buttons.length > 0 && (
284
325
  <TableCell padding='checkbox'>
@@ -298,7 +339,7 @@ export function Table<T>({
298
339
  {columns.map((column, index) => {
299
340
  const { value: cellValue, isCustomRendered } = formatCellValue(row[column], column, row);
300
341
  return (
301
- <TableCell key={index}>
342
+ <TableCell key={index} {...columnConfig?.[column]?.cellProps}>
302
343
  {isCustomRendered ? cellValue : <Typography>{cellValue}</Typography>}
303
344
  </TableCell>
304
345
  );
@@ -325,33 +366,36 @@ export function Table<T>({
325
366
  sx={toolbarSx}
326
367
  />
327
368
  )}
328
- <Box id='infinite-scroll-container' sx={{ width: '100%', flexGrow: 1, overflow: 'auto' }}>
369
+ <Box ref={infScrollContainerRef} sx={{ width: '100%', flexGrow: 1, overflow: 'auto' }}>
329
370
  {infiniteScroll ? (
330
371
  <InfiniteScroll
331
372
  dataLength={rows.length}
332
373
  next={handleFetchNextPage}
333
374
  hasMore={!!hasNextPage}
334
375
  loader={
335
- <Typography variant='body2' sx={{ p: 3 }}>
336
- Loading...
337
- </Typography>
376
+ infiniteScrollLoader || (
377
+ <Typography variant='body2' sx={{ p: 2 }}>
378
+ Loading...
379
+ </Typography>
380
+ )
338
381
  }
339
- scrollableTarget='infinite-scroll-container'
382
+ scrollableTarget={React.createElement('div', { ref: infScrollContainerRef })}
340
383
  >
341
384
  {renderTableContainer()}
342
385
  </InfiniteScroll>
343
386
  ) : (
344
387
  renderTableContainer()
345
388
  )}
346
- {pagination && !isLoading && (
389
+ {pagination && (
347
390
  <TablePagination
348
391
  rowsPerPageOptions={[5, 10, 25, 50, 100, 200]}
349
392
  component='div'
350
393
  count={totalRows || 0}
351
394
  rowsPerPage={rowsPerPage}
352
- page={page}
395
+ page={isLoading ? page : Math.min(page, maxPage)}
353
396
  onPageChange={(event, newPage) => setPage(newPage)}
354
397
  onRowsPerPageChange={(event) => updateRowsPerPage(parseInt(event.target.value))}
398
+ {...tablePaginationProps}
355
399
  />
356
400
  )}
357
401
  </Box>
@@ -11,12 +11,18 @@ import {
11
11
  import { TableLoader, RowWindow } from './TableLoader';
12
12
  import { useCallback, useMemo } from 'react';
13
13
 
14
+ export type InfiniteQueryData<T> = {
15
+ pages: RowWindow<T>[];
16
+ pageParams: any[];
17
+ };
18
+
14
19
  export function useTableData<T>(
15
20
  tableLoader: TableLoader<T>,
16
21
  rowsPerPage: number,
17
22
  page: number,
18
23
  infiniteScroll: boolean,
19
- setRowCount?: React.Dispatch<React.SetStateAction<number | undefined>>
24
+ setRowCount?: React.Dispatch<React.SetStateAction<number | undefined>>,
25
+ refetchOnWindowFocus = false
20
26
  ) {
21
27
  const startIndex = useMemo(() => page * rowsPerPage, [page, rowsPerPage]);
22
28
  const endIndex = useMemo(() => startIndex + rowsPerPage, [startIndex, rowsPerPage]);
@@ -26,7 +32,7 @@ export function useTableData<T>(
26
32
  isLoading: isPaginatedLoading,
27
33
  error: paginatedError,
28
34
  refetch: refetchPaginatedData,
29
- } = usePaginationTableQuery<T>(tableLoader, startIndex, endIndex, !infiniteScroll);
35
+ } = usePaginationTableQuery<T>(tableLoader, startIndex, endIndex, !infiniteScroll, refetchOnWindowFocus);
30
36
 
31
37
  const {
32
38
  data: infiniteData,
@@ -36,7 +42,7 @@ export function useTableData<T>(
36
42
  isFetchingNextPage,
37
43
  error: infiniteError,
38
44
  refetch: refetchInfiniteData,
39
- } = useInfiniteScrollTableQuery<T>(tableLoader, rowsPerPage, setRowCount, infiniteScroll);
45
+ } = useInfiniteScrollTableQuery<T>(tableLoader, rowsPerPage, setRowCount, infiniteScroll, refetchOnWindowFocus);
40
46
 
41
47
  const rows = useMemo(
42
48
  () =>
@@ -90,7 +96,8 @@ export const usePaginationTableQuery = <T>(
90
96
  tableLoader: TableLoader<T>,
91
97
  startIndex: number,
92
98
  endIndex: number,
93
- enabled = false
99
+ enabled = false,
100
+ refetchOnWindowFocus = false
94
101
  ): UseQueryResult<RowWindow<T>, Error> => {
95
102
  const { reactQueryKeys } = tableLoader;
96
103
 
@@ -105,7 +112,7 @@ export const usePaginationTableQuery = <T>(
105
112
  async () => await tableLoader.load(startIndex, endIndex),
106
113
  {
107
114
  enabled,
108
- refetchOnWindowFocus: false,
115
+ refetchOnWindowFocus,
109
116
  }
110
117
  );
111
118
  };
@@ -114,7 +121,8 @@ export const useInfiniteScrollTableQuery = <T>(
114
121
  tableLoader: TableLoader<T>,
115
122
  rowsPerPage: number,
116
123
  setRowCount?: React.Dispatch<React.SetStateAction<number | undefined>>,
117
- enabled = true
124
+ enabled = true,
125
+ refetchOnWindowFocus = false
118
126
  ): UseInfiniteQueryResult<RowWindow<T>, Error> => {
119
127
  const { reactQueryKeys } = tableLoader;
120
128
 
@@ -145,7 +153,7 @@ export const useInfiniteScrollTableQuery = <T>(
145
153
  endIndex: nextStartIndex + rowsPerPage,
146
154
  };
147
155
  },
148
- refetchOnWindowFocus: false,
156
+ refetchOnWindowFocus,
149
157
  });
150
158
  };
151
159
 
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 Brent Bahry
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.