@propellerads/table 2.1.0 → 4.3.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/src/index.tsx CHANGED
@@ -1,82 +1,474 @@
1
- import React from 'react';
2
- import ReactTable, {TableProps} from 'react-table';
1
+ import React, {
2
+ FunctionComponent,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ } from 'react';
7
+ import {
8
+ useSortBy,
9
+ useTable,
10
+ useRowSelect,
11
+ usePagination,
12
+ useFlexLayout,
13
+ Column,
14
+ } from 'react-table';
3
15
 
4
- import {CommonStyles} from './style';
16
+ import {
17
+ ArrowDownFull, ArrowUp, COLOR, SIZE,
18
+ } from '@propellerads/icon';
19
+ import Checkbox from '@propellerads/input-checkbox';
5
20
 
6
21
  import useLoadingState from './useLoadingState';
22
+ import useTableShadow from './useTableShadow';
7
23
 
8
- const Table = ({
24
+ import {
25
+ FOOTER_PLACEMENT,
26
+ TableProps,
27
+ PaginationProps,
28
+ StandardHooks,
29
+ StandardColumn,
30
+ StandardRow,
31
+ StandardCell,
32
+ ColumnWithSort,
33
+ TableHooksInstanceProps,
34
+ SelectableRowInstanceProps,
35
+ TableOptionsProps,
36
+ SelectableRow,
37
+ DefaultObject,
38
+ } from './types';
39
+
40
+ import {
41
+ getTableProps as getTableBodyProps,
42
+ getTableElementProps,
43
+ getTableElementInternalProps,
44
+ cellGetter,
45
+ mainCellGetter,
46
+ getTableCellProps,
47
+ getTableRowProps,
48
+ } from './propsGetter';
49
+
50
+ import {
51
+ TableRoot,
52
+ TableWrapper,
53
+ TableContent,
54
+ HeadCell,
55
+ TableLoading,
56
+ TableLoadingInner,
57
+ TD,
58
+ TableCore,
59
+ THead,
60
+ TR,
61
+ TH,
62
+ TBody,
63
+ TRGroup,
64
+ TFoot,
65
+ EmptyStateCell,
66
+ } from './style';
67
+
68
+ export const defaultProps = {
69
+ hasDefaultPagination: false,
70
+ isLoading: false,
71
+ footerPlacement: [],
72
+ loadingMessage: 'loading...',
73
+ labelPerPage: 'Show rows',
74
+ parentElementId: 'parent-element',
75
+ tableContentId: '',
76
+ showLoadingState: false,
77
+ LoadingCellComponent: EmptyStateCell,
78
+ getRowPreProps: () => ({}),
79
+ noDataMessage: '',
80
+ };
81
+
82
+ type DefaultProps = Readonly<typeof defaultProps>;
83
+
84
+ const disableSortRemove = true;
85
+ const disableMultiSort = true;
86
+ const disabledMultiRemove = true;
87
+
88
+ const DEFAULT_PAGE_INDEX = 0;
89
+ const DEFAULT_PAGE_SIZE = 10;
90
+
91
+ function isFunction(reference?: (arg1?: any, arg2?: any) => void) {
92
+ return typeof reference === 'function';
93
+ }
94
+
95
+ function getHeadContent(column: ColumnWithSort) {
96
+ if (column.isSorted && column.isSortedDesc) {
97
+ return (
98
+ <HeadCell>
99
+ {column.render('Header')}
100
+ <ArrowDownFull
101
+ size={SIZE.SMALL}
102
+ color={COLOR.GRAY_DARK}
103
+ />
104
+ </HeadCell>
105
+ );
106
+ }
107
+
108
+ if (column.isSorted && !column.isSortedDesc) {
109
+ return (
110
+ <HeadCell>
111
+ {column.render('Header')}
112
+ <ArrowUp
113
+ size={SIZE.SMALL}
114
+ color={COLOR.GRAY_DARK}
115
+ />
116
+ </HeadCell>
117
+ );
118
+ }
119
+
120
+ return column.render('Header');
121
+ }
122
+
123
+ const Table: FunctionComponent<TableProps & DefaultProps> = (props) => {
124
+ const {
125
+ columns,
9
126
  data,
10
- columns = [],
11
- defaultPageSize = 20,
12
- loading = false,
13
- defaultSorted = [],
14
- pageText = 'page',
15
- ofText = 'of',
16
- previousText = 'Previous page',
17
- nextText = 'Next page',
18
- loadingText = 'Loading...',
19
- noDataText = 'Woops, no data.',
20
- getTrProps,
21
- manual = false,
22
- filterable = true,
23
- showPagination,
24
- showPageSizeOptions = false,
25
- showPageJump = false,
26
- style = {},
27
- ExpanderComponent,
28
- SubComponent,
29
- resizable = false,
30
- showLoadingState = false,
127
+ totalItems,
128
+ fetchData,
129
+ controlledPagination,
130
+ initialState,
131
+ isLoading,
132
+ loadingMessage,
133
+ labelPerPage,
134
+ footerPlacement,
135
+ onSortedChange,
136
+ hasDefaultPagination,
137
+ onSelectRowsChange,
138
+ parentElementId,
139
+ tableContentId,
31
140
  LoadingCellComponent,
32
- defaultFilterMethod = (filter, row) => String(row[filter.id])
33
- .toLowerCase()
34
- .includes(filter.value.toLowerCase()),
141
+ PaginationComponent,
142
+ getRowPreProps,
143
+ getTableProps,
144
+ getHeaderGroupProps,
145
+ getHeaderProps,
146
+ getRowProps,
147
+ getCellProps,
148
+ getFooterProps,
149
+ getFooterGroupProps,
150
+ showLoadingState,
151
+ noDataMessage,
152
+ } = props;
153
+
154
+ const memoColumns = useMemo(() => columns, [columns]);
155
+
156
+ const {loadingColumns, loadingData} = useLoadingState(
157
+ showLoadingState, isLoading, controlledPagination?.pageSize ?? DEFAULT_PAGE_SIZE, memoColumns, LoadingCellComponent,
158
+ );
159
+
160
+ const showLoading = showLoadingState && isLoading;
161
+ const hasSelectedRowsAbility = onSelectRowsChange && isFunction(onSelectRowsChange);
162
+ const hasManualSortBy = onSortedChange && isFunction(onSortedChange);
163
+ const hasControlledPagination = fetchData && controlledPagination && Object.keys(controlledPagination).length > 0;
164
+
165
+ const options: TableOptionsProps = {
166
+ columns: showLoading ? (loadingColumns as Column<DefaultObject>[]) : memoColumns,
167
+ data: showLoading ? loadingData : data,
168
+ initialState,
169
+ disableSortRemove,
170
+ disableMultiSort,
171
+ disabledMultiRemove,
172
+ };
173
+
174
+ if (hasManualSortBy) {
175
+ options.manualSortBy = true;
176
+ }
177
+
178
+ if (hasControlledPagination) {
179
+ if (typeof controlledPagination?.pageCount === 'undefined') {
180
+ throw new Error('You have to pass pageCount in controlledPagination data');
181
+ }
182
+
183
+ options.initialState = {
184
+ ...options.initialState,
185
+ pageIndex: controlledPagination.pageIndex ?? DEFAULT_PAGE_INDEX,
186
+ pageSize: controlledPagination.pageSize ?? DEFAULT_PAGE_SIZE,
187
+ };
188
+
189
+ options.autoResetPage = true;
190
+ options.manualPagination = true;
191
+ options.pageCount = controlledPagination.pageCount;
192
+ }
193
+
194
+ const useSelect = (hooks: StandardHooks) => {
195
+ if (!hasSelectedRowsAbility) {
196
+ return;
197
+ }
198
+
199
+ const newColumn = {
200
+ id: 'selection',
201
+ disableSortBy: true,
202
+ Header: (instance: SelectableRowInstanceProps) => {
203
+ const {getToggleAllRowsSelectedProps, toggleAllRowsSelected} = instance;
204
+
205
+ return (
206
+ <Checkbox
207
+ elementId="all"
208
+ onChange={toggleAllRowsSelected}
209
+ isChecked={getToggleAllRowsSelectedProps().checked}
210
+ />
211
+ );
212
+ },
213
+ Cell: ({row}: { row: SelectableRow }) => {
214
+ const {
215
+ id,
216
+ original,
217
+ toggleRowSelected,
218
+ getToggleRowSelectedProps,
219
+ } = row;
220
+
221
+ const elementId = Number.isInteger(original.id) ? original.id : id;
222
+
223
+ return (
224
+ <Checkbox
225
+ elementId={elementId}
226
+ onChange={toggleRowSelected}
227
+ isChecked={getToggleRowSelectedProps().checked}
228
+ />
229
+ );
230
+ },
231
+ };
232
+
233
+ hooks.visibleColumns.push((tableColumns: Array<StandardColumn>) => ([
234
+ newColumn,
235
+ ...tableColumns,
236
+ ]));
237
+ };
238
+
239
+ const {
240
+ getTableProps: _getTableProps,
241
+ headerGroups,
242
+ footerGroups,
243
+ setHiddenColumns,
244
+ rows,
245
+ prepareRow,
246
+ visibleColumns,
35
247
  ...rest
36
- }: Partial<TableProps> & { showLoadingState?: boolean, LoadingCellComponent?: (props: any) => React.ReactElement }) => {
37
- const {loadingColumns, loadingData} = useLoadingState(
38
- showLoadingState, loading, defaultPageSize, columns, LoadingCellComponent
39
- );
40
- const showLoading = showLoadingState && loading;
41
- return (
42
- <>
43
- <ReactTable
44
- data={showLoading ? loadingData : data}
45
- columns={showLoading ? loadingColumns : columns}
46
- defaultPageSize={defaultPageSize}
47
- filterable={filterable}
48
- defaultSorted={defaultSorted}
49
- showPagination={!showLoading && showPagination}
50
- showPageSizeOptions={showPageSizeOptions}
51
- showPageJump={showPageJump}
52
- loading={!showLoadingState && loading}
53
- resizable={resizable}
54
- minRows={0}
55
- pageText={pageText}
56
- ofText={ofText}
57
- previousText={previousText}
58
- nextText={nextText}
59
- loadingText={loadingText}
60
- noDataText={noDataText}
61
- defaultFilterMethod={defaultFilterMethod}
62
- getTrProps={getTrProps}
63
- manual={manual}
64
- style={style}
65
- ExpanderComponent={ExpanderComponent}
66
- SubComponent={SubComponent}
67
- {...rest}
68
- />
69
- <CommonStyles />
70
- </>
248
+ } = useTable(
249
+ options,
250
+ useFlexLayout,
251
+ useSortBy,
252
+ usePagination,
253
+ useRowSelect,
254
+ useSelect,
255
+ );
256
+
257
+ const {
258
+ selectedFlatRows,
259
+ setSortBy,
260
+ page,
261
+ canPreviousPage,
262
+ canNextPage,
263
+ pageCount,
264
+ gotoPage,
265
+ nextPage,
266
+ previousPage,
267
+ setPageSize,
268
+ state: {selectedRowIds, pageIndex, pageSize},
269
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
270
+ // @ts-ignore
271
+ } = rest as TableHooksInstanceProps;
272
+
273
+ const tableRef = useRef(null);
274
+ const tableWrapperRef = useRef(null);
275
+
276
+ useTableShadow(tableRef, tableWrapperRef);
277
+
278
+ useEffect(() => {
279
+ if (!initialState?.hiddenColumns) {
280
+ return;
281
+ }
282
+
283
+ setHiddenColumns(initialState.hiddenColumns);
284
+ }, [initialState?.hiddenColumns]);
285
+
286
+ useEffect(() => {
287
+ if (hasSelectedRowsAbility) {
288
+ onSelectRowsChange?.(selectedFlatRows?.map((row: StandardRow) => row.original));
289
+ }
290
+ }, [selectedRowIds]);
291
+
292
+ let pagination: JSX.Element | null = null;
293
+
294
+ if (hasDefaultPagination && hasControlledPagination) {
295
+ // eslint-disable-next-line max-len
296
+ throw new Error('You have to pass either hasDefaultPagination true boolean prop or pass fetchData callback and controlledPagination data');
297
+ }
298
+
299
+ if (hasControlledPagination || hasDefaultPagination) {
300
+ const paginationData: PaginationProps = {
301
+ setPageSize,
302
+ gotoPage,
303
+ canNextPage,
304
+ canPreviousPage,
305
+ parentElementId,
306
+ labelPerPage,
307
+ pageIndex,
308
+ previousPageHandler: previousPage,
309
+ nextPageHandler: nextPage,
310
+ totalPages: pageCount,
311
+ perPage: pageSize,
312
+ totalItems: totalItems ?? data.length,
313
+ };
314
+
315
+ if (hasControlledPagination && controlledPagination) {
316
+ paginationData.canNextPage = controlledPagination.pageIndex + 1 !== paginationData.totalPages;
317
+ paginationData.canPreviousPage = controlledPagination.pageIndex !== 0;
318
+ paginationData.pageIndex = controlledPagination.pageIndex;
319
+ paginationData.perPage = controlledPagination.pageSize;
320
+
321
+ paginationData.setPageSize = (newPageSize: number) => {
322
+ fetchData?.({
323
+ pageIndex: paginationData.pageIndex,
324
+ pageSize: newPageSize,
325
+ });
326
+ };
327
+
328
+ paginationData.gotoPage = (newPageIndex: number) => {
329
+ fetchData?.({
330
+ pageIndex: newPageIndex,
331
+ pageSize: paginationData.perPage,
332
+ });
333
+ };
334
+
335
+ paginationData.nextPageHandler = () => {
336
+ const newPageIndex = paginationData.pageIndex + 1;
337
+ paginationData.gotoPage(newPageIndex);
338
+ };
339
+
340
+ paginationData.previousPageHandler = () => {
341
+ const newPageIndex = paginationData.pageIndex - 1;
342
+ paginationData.gotoPage(newPageIndex);
343
+ };
344
+
345
+ if (controlledPagination?.paginationAmount) {
346
+ paginationData.paginationAmount = controlledPagination.paginationAmount;
347
+ }
348
+ }
349
+
350
+ pagination = (
351
+ <PaginationComponent {...paginationData} />
71
352
  );
72
- };
353
+ }
354
+
355
+ function extendSortByProps(column: ColumnWithSort) {
356
+ const headerProps = column.getSortByToggleProps && column.getSortByToggleProps();
357
+
358
+ delete headerProps?.style;
73
359
 
74
- Table.defaultProps = {
75
- getTrProps: undefined,
76
- onSortedChange: undefined,
77
- expanderComponent: undefined,
78
- subComponent: undefined,
79
- onPageChange: undefined,
360
+ if (hasManualSortBy && !column.canSort && headerProps) {
361
+ headerProps.onClick = () => {
362
+ onSortedChange?.(column.id, Boolean(column.isSortedDesc));
363
+ setSortBy([{
364
+ id: column.id,
365
+ desc: !column.isSortedDesc,
366
+ }]);
367
+ };
368
+ }
369
+
370
+ return headerProps;
371
+ }
372
+
373
+ const tBodyTr = (hasControlledPagination || hasDefaultPagination) ? page : rows;
374
+
375
+ return (
376
+ <TableRoot>
377
+ <TableLoading isLoading={!showLoadingState && isLoading}>
378
+ <TableLoadingInner>
379
+ {loadingMessage}
380
+ </TableLoadingInner>
381
+ </TableLoading>
382
+ <TableWrapper ref={tableWrapperRef}>
383
+ <TableContent id={tableContentId}>
384
+ <TableCore {..._getTableProps(getTableBodyProps(getTableProps))} ref={tableRef}>
385
+ <THead>
386
+ {headerGroups.map((headerGroup) => (
387
+ <TR {...headerGroup.getHeaderGroupProps(getTableElementProps(getHeaderGroupProps))}>
388
+ {headerGroup.headers.map((column) => {
389
+ const headerProps = extendSortByProps(column) as DefaultObject;
390
+
391
+ return (
392
+ <TH
393
+ {...column.getHeaderProps(getTableElementInternalProps(
394
+ headerProps, getHeaderProps, mainCellGetter,
395
+ ))}
396
+ >
397
+ {getHeadContent(column)}
398
+ </TH>
399
+ );
400
+ })}
401
+ </TR>
402
+ ))}
403
+ </THead>
404
+ {footerPlacement.includes(FOOTER_PLACEMENT.TOP) && (
405
+ <TFoot>
406
+ {footerGroups.map((group) => (
407
+ <TR {...group.getFooterGroupProps(getTableElementProps(getFooterGroupProps))}>
408
+ {group.headers.map((column) => (
409
+ <TD {...column.getFooterProps(getTableElementProps(getFooterProps, mainCellGetter))}>
410
+ {column.render('Footer')}
411
+ </TD>
412
+ ))}
413
+ </TR>
414
+ ))}
415
+ </TFoot>
416
+ )}
417
+ <TBody>
418
+ {tBodyTr.map((row: StandardRow) => {
419
+ prepareRow(row);
420
+ const {isDelimiterTd} = getRowPreProps(row);
421
+
422
+ if (isDelimiterTd) {
423
+ return (
424
+ <TRGroup key={`group_${row.index}`}>
425
+ <TR {...row.getRowProps(getTableRowProps(getRowProps))}>
426
+ <TD
427
+ colSpan={visibleColumns.length}
428
+ {...row.cells[0].getCellProps(getTableCellProps(getCellProps, cellGetter))}
429
+ >
430
+ <strong>{row.cells[0].render('Cell')}</strong>
431
+ </TD>
432
+ </TR>
433
+ </TRGroup>
434
+ );
435
+ }
436
+
437
+ return (
438
+ <TRGroup key={`group_${row.index}`}>
439
+ <TR {...row.getRowProps(getTableRowProps(getRowProps))}>
440
+ {row.cells.map((cell: StandardCell) => (
441
+ <TD {...cell.getCellProps(getTableCellProps(getCellProps, cellGetter))}>
442
+ {cell.render('Cell')}
443
+ </TD>
444
+ ))}
445
+ </TR>
446
+ </TRGroup>
447
+ );
448
+ })}
449
+ </TBody>
450
+ {footerPlacement.includes(FOOTER_PLACEMENT.BOTTOM) && (
451
+ <TFoot>
452
+ {footerGroups.map((group) => (
453
+ <TR {...group.getFooterGroupProps(getTableElementProps(getFooterGroupProps))}>
454
+ {group.headers.map((column) => (
455
+ <TD {...column.getFooterProps(getTableElementProps(getFooterProps, mainCellGetter))}>
456
+ {column.render('Footer')}
457
+ </TD>
458
+ ))}
459
+ </TR>
460
+ ))}
461
+ </TFoot>
462
+ )}
463
+ </TableCore>
464
+ </TableContent>
465
+ </TableWrapper>
466
+ {!isLoading && !data.length && noDataMessage}
467
+ {pagination}
468
+ </TableRoot>
469
+ );
80
470
  };
81
471
 
472
+ Table.defaultProps = defaultProps;
473
+
82
474
  export {Table};
@@ -0,0 +1,86 @@
1
+ import {
2
+ ElementGetter,
3
+ BaseMeta,
4
+ TableGetter,
5
+ ElementCellPropGetter,
6
+ ElementRowPropGetter,
7
+ DefaultObject,
8
+ ElementHeaderFooterPropGetter,
9
+ GetTableElementPropsGetter,
10
+ GetTableContainerPropsGetter,
11
+ GetTableElementInternalPropsGetter,
12
+ } from './types';
13
+
14
+ export const defaultGetter: ElementGetter = (props) => props;
15
+
16
+ export const cellGetter: ElementGetter = (props, {cell}) => ({
17
+ ...props,
18
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
19
+ // @ts-ignore
20
+ align: cell?.column?.align,
21
+ });
22
+ export const mainCellGetter: ElementGetter = (props, {column}) => ({
23
+ ...props,
24
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
25
+ // @ts-ignore
26
+ align: column?.align,
27
+ });
28
+
29
+ export const getTableProps: GetTableContainerPropsGetter = (userGetter, getter = defaultGetter as TableGetter) => {
30
+ if (userGetter) {
31
+ return (props) => ({
32
+ ...getter(props),
33
+ ...userGetter(props),
34
+ });
35
+ }
36
+
37
+ return getter;
38
+ };
39
+
40
+ export const getTableElementProps:
41
+ GetTableElementPropsGetter<ElementHeaderFooterPropGetter> = (userGetter, getter = defaultGetter) => {
42
+ if (userGetter) {
43
+ return (props, meta) => ({
44
+ ...getter(props, meta),
45
+ ...userGetter(props, meta),
46
+ });
47
+ }
48
+
49
+ return getter;
50
+ };
51
+
52
+ export const getTableRowProps:
53
+ GetTableElementPropsGetter<ElementRowPropGetter> = (userGetter, getter = defaultGetter) => {
54
+ if (userGetter) {
55
+ return (props, meta) => ({
56
+ ...getter(props, meta),
57
+ ...userGetter(props, meta),
58
+ });
59
+ }
60
+
61
+ return getter;
62
+ };
63
+
64
+ export const getTableCellProps:
65
+ GetTableElementPropsGetter<ElementCellPropGetter> = (userGetter, getter = defaultGetter) => {
66
+ if (userGetter) {
67
+ return (props, meta) => ({
68
+ ...getter(props, meta),
69
+ ...userGetter(props, meta),
70
+ });
71
+ }
72
+
73
+ return getter;
74
+ };
75
+
76
+ export const getTableElementInternalProps:
77
+ GetTableElementInternalPropsGetter = (internalProps, userGetter, getter = defaultGetter) => {
78
+ if (userGetter) {
79
+ return (props, meta) => ({
80
+ ...getter({...props, ...internalProps}, meta),
81
+ ...userGetter(props, meta),
82
+ });
83
+ }
84
+
85
+ return (props: DefaultObject, meta: BaseMeta) => getter({...props, ...internalProps}, meta);
86
+ };