@propellerads/table 3.0.0 → 4.3.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.
package/src/index.tsx CHANGED
@@ -1,78 +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';
23
+
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,
126
+ data,
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,
140
+ LoadingCellComponent,
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]);
7
155
 
8
- const Table = ({
9
- 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,
31
- LoadingCellComponent,
32
- defaultFilterMethod = (filter, row) => String(row[filter.id])
33
- .toLowerCase()
34
- .includes(filter.value.toLowerCase()),
35
- ...rest
36
- }: Partial<TableProps> & { showLoadingState: boolean, LoadingCellComponent?: (props: any) => React.ReactElement}) => {
37
156
  const {loadingColumns, loadingData} = useLoadingState(
38
- showLoadingState, loading, defaultPageSize, columns, LoadingCellComponent,
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,
247
+ ...rest
248
+ } = useTable(
249
+ options,
250
+ useFlexLayout,
251
+ useSortBy,
252
+ usePagination,
253
+ useRowSelect,
254
+ useSelect,
39
255
  );
40
- const showLoading = showLoadingState && loading;
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} />
352
+ );
353
+ }
354
+
355
+ function extendSortByProps(column: ColumnWithSort) {
356
+ const headerProps = column.getSortByToggleProps && column.getSortByToggleProps();
357
+
358
+ delete headerProps?.style;
359
+
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
+
41
375
  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
- </>
376
+ <TableRoot className="table-root">
377
+ <TableLoading isLoading={!showLoadingState && isLoading} className="table-loading">
378
+ <TableLoadingInner>
379
+ {loadingMessage}
380
+ </TableLoadingInner>
381
+ </TableLoading>
382
+ <TableWrapper ref={tableWrapperRef} className="table-wrapper">
383
+ <TableContent id={tableContentId} className="table-content">
384
+ <TableCore {..._getTableProps(getTableBodyProps(getTableProps))} ref={tableRef}>
385
+ <THead className="table-head">
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 className="table-footer-top">
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 className="table-body">
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 className="table-footer-bottom">
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>
71
469
  );
72
470
  };
73
471
 
74
- Table.defaultProps = {
75
- LoadingCellComponent: undefined,
76
- };
472
+ Table.defaultProps = defaultProps;
77
473
 
78
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
+ };