@snack-uikit/table 0.6.5-preview-85c5f47b.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/CHANGELOG.md +376 -0
- package/LICENSE +201 -0
- package/README.md +165 -0
- package/dist/components/Table/Table.d.ts +104 -0
- package/dist/components/Table/Table.js +125 -0
- package/dist/components/Table/constants.d.ts +3 -0
- package/dist/components/Table/constants.js +14 -0
- package/dist/components/Table/hooks.d.ts +19 -0
- package/dist/components/Table/hooks.js +37 -0
- package/dist/components/Table/index.d.ts +1 -0
- package/dist/components/Table/index.js +1 -0
- package/dist/components/Table/styles.module.css +46 -0
- package/dist/components/TableEmptyState/TableEmptyState.d.ts +8 -0
- package/dist/components/TableEmptyState/TableEmptyState.js +8 -0
- package/dist/components/TableEmptyState/index.d.ts +1 -0
- package/dist/components/TableEmptyState/index.js +1 -0
- package/dist/components/TableEmptyState/styles.module.css +15 -0
- package/dist/components/TablePagination/TablePagination.d.ts +8 -0
- package/dist/components/TablePagination/TablePagination.js +19 -0
- package/dist/components/TablePagination/index.d.ts +1 -0
- package/dist/components/TablePagination/index.js +1 -0
- package/dist/components/TablePagination/styles.module.css +11 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +31 -0
- package/dist/exportTable.d.ts +9 -0
- package/dist/exportTable.js +51 -0
- package/dist/helperComponents/Cells/BodyCell/BodyCell.d.ts +7 -0
- package/dist/helperComponents/Cells/BodyCell/BodyCell.js +24 -0
- package/dist/helperComponents/Cells/BodyCell/index.d.ts +1 -0
- package/dist/helperComponents/Cells/BodyCell/index.js +1 -0
- package/dist/helperComponents/Cells/BodyCell/styles.module.css +12 -0
- package/dist/helperComponents/Cells/Cell.d.ts +9 -0
- package/dist/helperComponents/Cells/Cell.js +20 -0
- package/dist/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +7 -0
- package/dist/helperComponents/Cells/HeaderCell/HeaderCell.js +17 -0
- package/dist/helperComponents/Cells/HeaderCell/helpers.d.ts +2 -0
- package/dist/helperComponents/Cells/HeaderCell/helpers.js +12 -0
- package/dist/helperComponents/Cells/HeaderCell/index.d.ts +1 -0
- package/dist/helperComponents/Cells/HeaderCell/index.js +1 -0
- package/dist/helperComponents/Cells/HeaderCell/styles.module.css +53 -0
- package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.d.ts +22 -0
- package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.js +36 -0
- package/dist/helperComponents/Cells/RowActionsCell/index.d.ts +1 -0
- package/dist/helperComponents/Cells/RowActionsCell/index.js +1 -0
- package/dist/helperComponents/Cells/RowActionsCell/styles.module.css +34 -0
- package/dist/helperComponents/Cells/SelectionCell/SelectionCell.d.ts +2 -0
- package/dist/helperComponents/Cells/SelectionCell/SelectionCell.js +45 -0
- package/dist/helperComponents/Cells/SelectionCell/index.d.ts +1 -0
- package/dist/helperComponents/Cells/SelectionCell/index.js +1 -0
- package/dist/helperComponents/Cells/SelectionCell/styles.module.css +13 -0
- package/dist/helperComponents/Cells/StatusCell/StatusCell.d.ts +27 -0
- package/dist/helperComponents/Cells/StatusCell/StatusCell.js +43 -0
- package/dist/helperComponents/Cells/StatusCell/constants.d.ts +13 -0
- package/dist/helperComponents/Cells/StatusCell/constants.js +14 -0
- package/dist/helperComponents/Cells/StatusCell/index.d.ts +1 -0
- package/dist/helperComponents/Cells/StatusCell/index.js +1 -0
- package/dist/helperComponents/Cells/StatusCell/styles.module.css +96 -0
- package/dist/helperComponents/Cells/index.d.ts +5 -0
- package/dist/helperComponents/Cells/index.js +5 -0
- package/dist/helperComponents/Cells/styles.module.css +13 -0
- package/dist/helperComponents/ExportButton/ExportButton.d.ts +10 -0
- package/dist/helperComponents/ExportButton/ExportButton.js +27 -0
- package/dist/helperComponents/ExportButton/index.d.ts +1 -0
- package/dist/helperComponents/ExportButton/index.js +1 -0
- package/dist/helperComponents/Rows/BodyRow.d.ts +15 -0
- package/dist/helperComponents/Rows/BodyRow.js +25 -0
- package/dist/helperComponents/Rows/HeaderRow.d.ts +1 -0
- package/dist/helperComponents/Rows/HeaderRow.js +11 -0
- package/dist/helperComponents/Rows/PinnedCells.d.ts +8 -0
- package/dist/helperComponents/Rows/PinnedCells.js +6 -0
- package/dist/helperComponents/Rows/Row.d.ts +10 -0
- package/dist/helperComponents/Rows/Row.js +18 -0
- package/dist/helperComponents/Rows/index.d.ts +2 -0
- package/dist/helperComponents/Rows/index.js +2 -0
- package/dist/helperComponents/Rows/styles.module.css +87 -0
- package/dist/helperComponents/contexts.d.ts +14 -0
- package/dist/helperComponents/contexts.js +14 -0
- package/dist/helperComponents/helpers.d.ts +2 -0
- package/dist/helperComponents/helpers.js +9 -0
- package/dist/helperComponents/hooks.d.ts +28 -0
- package/dist/helperComponents/hooks.js +67 -0
- package/dist/helperComponents/index.d.ts +7 -0
- package/dist/helperComponents/index.js +7 -0
- package/dist/helperComponents/types.d.ts +7 -0
- package/dist/helperComponents/types.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +9 -0
- package/package.json +55 -0
- package/src/components/Table/Table.tsx +385 -0
- package/src/components/Table/constants.ts +18 -0
- package/src/components/Table/hooks.tsx +68 -0
- package/src/components/Table/index.ts +1 -0
- package/src/components/Table/styles.module.scss +55 -0
- package/src/components/TableEmptyState/TableEmptyState.tsx +25 -0
- package/src/components/TableEmptyState/index.ts +1 -0
- package/src/components/TableEmptyState/styles.module.scss +18 -0
- package/src/components/TablePagination/TablePagination.tsx +70 -0
- package/src/components/TablePagination/index.ts +1 -0
- package/src/components/TablePagination/styles.module.scss +13 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +31 -0
- package/src/exportTable.ts +82 -0
- package/src/helperComponents/Cells/BodyCell/BodyCell.tsx +32 -0
- package/src/helperComponents/Cells/BodyCell/index.ts +1 -0
- package/src/helperComponents/Cells/BodyCell/styles.module.scss +15 -0
- package/src/helperComponents/Cells/Cell.tsx +21 -0
- package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +53 -0
- package/src/helperComponents/Cells/HeaderCell/helpers.tsx +14 -0
- package/src/helperComponents/Cells/HeaderCell/index.ts +1 -0
- package/src/helperComponents/Cells/HeaderCell/styles.module.scss +74 -0
- package/src/helperComponents/Cells/RowActionsCell/RowActionsCell.tsx +118 -0
- package/src/helperComponents/Cells/RowActionsCell/index.ts +1 -0
- package/src/helperComponents/Cells/RowActionsCell/styles.module.scss +44 -0
- package/src/helperComponents/Cells/SelectionCell/SelectionCell.tsx +54 -0
- package/src/helperComponents/Cells/SelectionCell/index.ts +1 -0
- package/src/helperComponents/Cells/SelectionCell/styles.module.scss +18 -0
- package/src/helperComponents/Cells/StatusCell/StatusCell.tsx +108 -0
- package/src/helperComponents/Cells/StatusCell/constants.ts +14 -0
- package/src/helperComponents/Cells/StatusCell/index.ts +1 -0
- package/src/helperComponents/Cells/StatusCell/styles.module.scss +95 -0
- package/src/helperComponents/Cells/index.ts +5 -0
- package/src/helperComponents/Cells/styles.module.scss +13 -0
- package/src/helperComponents/ExportButton/ExportButton.tsx +70 -0
- package/src/helperComponents/ExportButton/index.ts +1 -0
- package/src/helperComponents/Rows/BodyRow.tsx +78 -0
- package/src/helperComponents/Rows/HeaderRow.tsx +36 -0
- package/src/helperComponents/Rows/PinnedCells.tsx +17 -0
- package/src/helperComponents/Rows/Row.tsx +20 -0
- package/src/helperComponents/Rows/index.ts +2 -0
- package/src/helperComponents/Rows/styles.module.scss +127 -0
- package/src/helperComponents/contexts.ts +29 -0
- package/src/helperComponents/helpers.ts +13 -0
- package/src/helperComponents/hooks.ts +84 -0
- package/src/helperComponents/index.ts +7 -0
- package/src/helperComponents/types.ts +13 -0
- package/src/index.ts +3 -0
- package/src/types.ts +80 -0
- package/src/utils.ts +13 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Table } from '@tanstack/react-table';
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { ChipChoice } from '@snack-uikit/chips';
|
|
5
|
+
import { Pagination } from '@snack-uikit/pagination';
|
|
6
|
+
|
|
7
|
+
import styles from './styles.module.scss';
|
|
8
|
+
|
|
9
|
+
export type TablePaginationProps<TData> = {
|
|
10
|
+
table: Table<TData>;
|
|
11
|
+
options?: number[];
|
|
12
|
+
optionsLabel?: string;
|
|
13
|
+
pageCount?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function TablePagination<TData>({
|
|
17
|
+
table,
|
|
18
|
+
options: optionsProp,
|
|
19
|
+
optionsLabel = 'Rows volume: ',
|
|
20
|
+
}: TablePaginationProps<TData>) {
|
|
21
|
+
const handlePaginationOnChange = useCallback(
|
|
22
|
+
(pageIndex: number) => {
|
|
23
|
+
table.setPageIndex(pageIndex - 1);
|
|
24
|
+
},
|
|
25
|
+
[table],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const handleRowsVolumeOnChange = useCallback(
|
|
29
|
+
(value: string) => {
|
|
30
|
+
table.setPageSize(Number(value));
|
|
31
|
+
},
|
|
32
|
+
[table],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const options = useMemo(
|
|
36
|
+
() => optionsProp?.sort((a, b) => a - b).map(value => ({ label: String(value), value: String(value) })),
|
|
37
|
+
[optionsProp],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const tablePaginationState = table.getState().pagination;
|
|
41
|
+
|
|
42
|
+
if (table.getPageCount() <= 1 && !options) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={styles.footer}>
|
|
48
|
+
{table.getPageCount() > 1 && (
|
|
49
|
+
<Pagination
|
|
50
|
+
total={table.getPageCount()}
|
|
51
|
+
page={tablePaginationState.pageIndex + 1}
|
|
52
|
+
onChange={handlePaginationOnChange}
|
|
53
|
+
className={styles.pagination}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{options && table.getRowModel().rows.length >= Number(options[0].value) && (
|
|
58
|
+
<ChipChoice.Single
|
|
59
|
+
value={String(tablePaginationState.pageSize)}
|
|
60
|
+
onChange={handleRowsVolumeOnChange}
|
|
61
|
+
placement={ChipChoice.placements.TopEnd}
|
|
62
|
+
options={options}
|
|
63
|
+
label={optionsLabel}
|
|
64
|
+
widthStrategy={ChipChoice.widthStrategies.Auto}
|
|
65
|
+
showClearButton={false}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TablePagination';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Table';
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export enum ColumnPinPosition {
|
|
2
|
+
Left = 'left',
|
|
3
|
+
Right = 'right',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export enum ColumnAlign {
|
|
7
|
+
Left = 'left',
|
|
8
|
+
Right = 'right',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const TEST_IDS = {
|
|
12
|
+
headerSortIndicator: 'table__header__sort-indicator',
|
|
13
|
+
headerRow: 'table__header-row',
|
|
14
|
+
headerCell: 'table__header-cell',
|
|
15
|
+
bodyRow: 'table__body-row',
|
|
16
|
+
bodyCell: 'table__body-cell',
|
|
17
|
+
pinnedCells: 'table__pinned-cells',
|
|
18
|
+
rowSelect: 'table__row-select',
|
|
19
|
+
rowActions: {
|
|
20
|
+
droplistTrigger: 'table__body-row__droplistTrigger',
|
|
21
|
+
droplist: 'table__body-row__actions-droplist',
|
|
22
|
+
option: 'table__body-row__action-option',
|
|
23
|
+
},
|
|
24
|
+
statusIndicator: 'table__status-indicator',
|
|
25
|
+
statusLabel: 'table__status-label',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export enum SortFn {
|
|
29
|
+
DateTime = 'datetime',
|
|
30
|
+
AlphaNumeric = 'alphanumeric',
|
|
31
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { utils as xlsxUtils, writeFileXLSX } from 'xlsx';
|
|
2
|
+
|
|
3
|
+
import { ColumnDefinition } from './types';
|
|
4
|
+
|
|
5
|
+
type ExportTableData<TData> = {
|
|
6
|
+
data: TData[];
|
|
7
|
+
columnDefinitions: ColumnDefinition<TData>[];
|
|
8
|
+
fileName?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function getColumnAccessorKey<TData>(column: ColumnDefinition<TData>): string | undefined {
|
|
12
|
+
if ('accessorKey' in column && column.accessorKey) {
|
|
13
|
+
return String(column.accessorKey);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getFilteredColumnsIds<TData extends object>(columnDefinitions: ColumnDefinition<TData>[]) {
|
|
18
|
+
return (columnDefinitions as ColumnDefinition<TData>[])
|
|
19
|
+
.filter(column => getColumnAccessorKey(column) && !column.meta?.skipOnExport)
|
|
20
|
+
.map(column => getColumnAccessorKey(column));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getXlsxFormatTable<TData extends object>({
|
|
24
|
+
data,
|
|
25
|
+
columnDefinitions,
|
|
26
|
+
}: {
|
|
27
|
+
data: TData[];
|
|
28
|
+
columnDefinitions: ColumnDefinition<TData>[];
|
|
29
|
+
}) {
|
|
30
|
+
const filteredIds = getFilteredColumnsIds(columnDefinitions);
|
|
31
|
+
return data.map((line: TData) => {
|
|
32
|
+
const lineRecord = line as Record<string, string>;
|
|
33
|
+
const result: string[] = [];
|
|
34
|
+
filteredIds.forEach(key => {
|
|
35
|
+
if (!key) {
|
|
36
|
+
result.push('');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
result.push(lineRecord[key]);
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function exportToCSV<TData extends object>({
|
|
47
|
+
columnDefinitions,
|
|
48
|
+
fileName = 'Table',
|
|
49
|
+
data,
|
|
50
|
+
}: ExportTableData<TData>) {
|
|
51
|
+
const xlsxData = getXlsxFormatTable({ data, columnDefinitions });
|
|
52
|
+
const filteredIds = getFilteredColumnsIds(columnDefinitions);
|
|
53
|
+
const table = [filteredIds, ...xlsxData];
|
|
54
|
+
const csv = table.map(line => line.map(el => `"${el}"`).join(',')).join('\n');
|
|
55
|
+
|
|
56
|
+
const blob = new Blob([csv], { type: 'text/csv' });
|
|
57
|
+
const url = window.URL.createObjectURL(blob);
|
|
58
|
+
const tempLink = Object.assign(document.createElement('a'), {
|
|
59
|
+
target: '_blank',
|
|
60
|
+
href: url,
|
|
61
|
+
download: fileName,
|
|
62
|
+
});
|
|
63
|
+
tempLink.click();
|
|
64
|
+
tempLink.remove();
|
|
65
|
+
|
|
66
|
+
return csv;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function exportToXLSX<TData extends object>({
|
|
70
|
+
columnDefinitions,
|
|
71
|
+
fileName = 'Table',
|
|
72
|
+
data,
|
|
73
|
+
}: ExportTableData<TData>) {
|
|
74
|
+
const xlsxData = getXlsxFormatTable({ data, columnDefinitions });
|
|
75
|
+
const filteredIds = getFilteredColumnsIds(columnDefinitions);
|
|
76
|
+
const workbook = xlsxUtils.book_new();
|
|
77
|
+
const worksheet = xlsxUtils.aoa_to_sheet([filteredIds, ...xlsxData]);
|
|
78
|
+
xlsxUtils.book_append_sheet(workbook, worksheet);
|
|
79
|
+
writeFileXLSX(workbook, `${fileName}.xlsx`);
|
|
80
|
+
|
|
81
|
+
return worksheet;
|
|
82
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Cell as TableCell, flexRender } from '@tanstack/react-table';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
|
|
4
|
+
import { TEST_IDS } from '../../../constants';
|
|
5
|
+
import { ColumnDefinition } from '../../../types';
|
|
6
|
+
import { useCellSizes } from '../../hooks';
|
|
7
|
+
import { Cell, CellProps } from '../Cell';
|
|
8
|
+
import styles from './styles.module.scss';
|
|
9
|
+
|
|
10
|
+
type BodyCellProps<TData> = Omit<CellProps, 'style' | 'children'> & {
|
|
11
|
+
cell: TableCell<TData, unknown>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function BodyCell<TData>({ cell, className, ...props }: BodyCellProps<TData>) {
|
|
15
|
+
const columnDef: ColumnDefinition<TData> = cell.column.columnDef;
|
|
16
|
+
|
|
17
|
+
const style = useCellSizes(cell);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Cell
|
|
21
|
+
{...props}
|
|
22
|
+
style={style}
|
|
23
|
+
className={cn(styles.tableBodyCell, className, columnDef.cellClassName)}
|
|
24
|
+
data-align={columnDef.align}
|
|
25
|
+
data-no-padding={columnDef.noBodyCellPadding || undefined}
|
|
26
|
+
data-column-id={cell.column.id}
|
|
27
|
+
data-test-id={TEST_IDS.bodyCell}
|
|
28
|
+
>
|
|
29
|
+
{flexRender(columnDef.cell, cell.getContext())}
|
|
30
|
+
</Cell>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BodyCell';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
|
|
2
|
+
|
|
3
|
+
.tableBodyCell {
|
|
4
|
+
@include composite-var($table-cells);
|
|
5
|
+
|
|
6
|
+
color: $sys-neutral-text-main;
|
|
7
|
+
|
|
8
|
+
&[data-align='right'] {
|
|
9
|
+
justify-content: flex-end;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&[data-no-padding] {
|
|
13
|
+
padding: 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { CSSProperties, MouseEventHandler, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { DataAttributes } from '../types';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
export type CellProps = {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
onClick?: MouseEventHandler;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: CSSProperties;
|
|
12
|
+
} & DataAttributes;
|
|
13
|
+
|
|
14
|
+
export function Cell({ onClick, className, style, children, ...attributes }: CellProps) {
|
|
15
|
+
return (
|
|
16
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
17
|
+
<div role='cell' onClick={onClick} className={cn(styles.tableCell, className)} style={style} {...attributes}>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { flexRender, Header } from '@tanstack/react-table';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
|
|
4
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
5
|
+
|
|
6
|
+
import { TEST_IDS } from '../../../constants';
|
|
7
|
+
import { ColumnDefinition } from '../../../types';
|
|
8
|
+
import { useCellSizes } from '../../hooks';
|
|
9
|
+
import { Cell, CellProps } from '../Cell';
|
|
10
|
+
import { getSortingIcon } from './helpers';
|
|
11
|
+
import styles from './styles.module.scss';
|
|
12
|
+
|
|
13
|
+
type HeaderCellProps<TData> = Omit<CellProps, 'align' | 'children' | 'onClick' | 'style'> & {
|
|
14
|
+
header: Header<TData, unknown>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function HeaderCell<TData>({ header, className }: HeaderCellProps<TData>) {
|
|
18
|
+
const isSortable = header.column.getCanSort();
|
|
19
|
+
const sortDirection = isSortable && (header.column.getIsSorted() || undefined);
|
|
20
|
+
const sortIcon = getSortingIcon(sortDirection);
|
|
21
|
+
|
|
22
|
+
const columnDef: ColumnDefinition<TData> = header.column.columnDef;
|
|
23
|
+
|
|
24
|
+
const style = useCellSizes(header);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Cell
|
|
28
|
+
style={style}
|
|
29
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
30
|
+
data-sortable={isSortable || undefined}
|
|
31
|
+
data-no-padding={columnDef.noHeaderCellPadding || undefined}
|
|
32
|
+
data-no-offset={columnDef.noHeaderCellBorderOffset || undefined}
|
|
33
|
+
data-test-id={TEST_IDS.headerCell}
|
|
34
|
+
className={cn(styles.tableHeaderCell, className, columnDef.headerClassName)}
|
|
35
|
+
>
|
|
36
|
+
{columnDef.header && (
|
|
37
|
+
<div className={styles.tableHeaderCellName}>
|
|
38
|
+
<TruncateString text={flexRender(columnDef.header, header.getContext()) as string} />
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
{Boolean(sortIcon) && (
|
|
43
|
+
<div
|
|
44
|
+
className={styles.tableHeaderSortIcon}
|
|
45
|
+
data-sort-direction={sortDirection}
|
|
46
|
+
data-test-id={TEST_IDS.headerSortIndicator}
|
|
47
|
+
>
|
|
48
|
+
{sortIcon}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</Cell>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SortDirection } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
import { ArrowDownSVG, ArrowUpSVG } from '@snack-uikit/icons';
|
|
4
|
+
|
|
5
|
+
export function getSortingIcon(sort?: SortDirection | false) {
|
|
6
|
+
switch (sort) {
|
|
7
|
+
case 'asc':
|
|
8
|
+
return <ArrowUpSVG size={16} />;
|
|
9
|
+
case 'desc':
|
|
10
|
+
return <ArrowDownSVG size={16} />;
|
|
11
|
+
default:
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './HeaderCell';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
|
|
4
|
+
.tableHeaderCell {
|
|
5
|
+
@include composite-var($table-head-column);
|
|
6
|
+
|
|
7
|
+
position: relative;
|
|
8
|
+
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
width: 100%;
|
|
14
|
+
|
|
15
|
+
background-color: inherit;
|
|
16
|
+
|
|
17
|
+
&::after {
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
content: '';
|
|
20
|
+
|
|
21
|
+
position: absolute;
|
|
22
|
+
bottom: 0;
|
|
23
|
+
left: 50%;
|
|
24
|
+
transform: translateX(-50%);
|
|
25
|
+
|
|
26
|
+
width: calc(100% - $space-table-head-separator-padding * 2);
|
|
27
|
+
height: 1px;
|
|
28
|
+
|
|
29
|
+
background-color: $sys-neutral-decor-default;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&[data-sortable] {
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&[data-no-offset] {
|
|
37
|
+
&::after {
|
|
38
|
+
left: 0;
|
|
39
|
+
transform: none;
|
|
40
|
+
width: calc(100% - $space-table-head-separator-padding);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&[data-no-padding] {
|
|
45
|
+
padding: 0;
|
|
46
|
+
|
|
47
|
+
&::after {
|
|
48
|
+
width: 100%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tableHeaderCellName {
|
|
54
|
+
@include composite-var($table-head-name-layout);
|
|
55
|
+
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
min-width: 0;
|
|
61
|
+
|
|
62
|
+
color: simple-var($sys-neutral-text-light);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tableHeaderSortIcon {
|
|
66
|
+
display: flex;
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
color: simple-var($sys-neutral-text-light);
|
|
69
|
+
|
|
70
|
+
svg {
|
|
71
|
+
width: simple-var($icon-xs) !important; /* stylelint-disable-line declaration-no-important */
|
|
72
|
+
height: simple-var($icon-xs) !important; /* stylelint-disable-line declaration-no-important */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { CellContext, Row } from '@tanstack/react-table';
|
|
2
|
+
import { MouseEvent } from 'react';
|
|
3
|
+
|
|
4
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
5
|
+
import { Droplist, ItemSingleProps } from '@snack-uikit/droplist';
|
|
6
|
+
import { MoreSVG } from '@snack-uikit/icons';
|
|
7
|
+
|
|
8
|
+
import { ColumnPinPosition, TEST_IDS } from '../../../constants';
|
|
9
|
+
import { ColumnDefinition } from '../../../types';
|
|
10
|
+
import { useRowContext } from '../../contexts';
|
|
11
|
+
import styles from './styles.module.scss';
|
|
12
|
+
|
|
13
|
+
export type RowActionInfo<TData> = {
|
|
14
|
+
rowId: string;
|
|
15
|
+
data: TData;
|
|
16
|
+
itemId?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RowActionProps<TData> = Pick<
|
|
20
|
+
ItemSingleProps,
|
|
21
|
+
'option' | 'disabled' | 'icon' | 'description' | 'caption' | 'tagLabel'
|
|
22
|
+
> & {
|
|
23
|
+
id?: string;
|
|
24
|
+
onClick(row: RowActionInfo<TData>, e: MouseEvent<HTMLButtonElement>): void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type RowActionsCellProps<TData> = {
|
|
28
|
+
actions: RowActionProps<TData>[];
|
|
29
|
+
row: Row<TData>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function RowActionsCell<TData>({ row, actions }: RowActionsCellProps<TData>) {
|
|
33
|
+
const { droplistOpened, setDroplistOpen } = useRowContext();
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
triggerElementRef,
|
|
37
|
+
handleDroplistFocusLeave,
|
|
38
|
+
handleDroplistItemKeyDown,
|
|
39
|
+
handleTriggerKeyDown,
|
|
40
|
+
handleDroplistItemClick,
|
|
41
|
+
firstElementRefCallback,
|
|
42
|
+
} = Droplist.useKeyboardNavigation<HTMLButtonElement>({ setDroplistOpen });
|
|
43
|
+
|
|
44
|
+
const handleItemClick = (item: RowActionProps<TData>) => (e: MouseEvent<HTMLButtonElement>) => {
|
|
45
|
+
item.onClick({ rowId: row.id, itemId: item.id, data: row.original }, e);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const disabled = !row.getCanSelect();
|
|
49
|
+
|
|
50
|
+
const stopPropagationClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
56
|
+
<div onClick={stopPropagationClick} className={styles.rowActionsCellWrap} data-open={droplistOpened || undefined}>
|
|
57
|
+
{!disabled && Boolean(actions.length) && (
|
|
58
|
+
<Droplist
|
|
59
|
+
open={droplistOpened}
|
|
60
|
+
onOpenChange={setDroplistOpen}
|
|
61
|
+
placement={Droplist.placements.BottomEnd}
|
|
62
|
+
firstElementRefCallback={firstElementRefCallback}
|
|
63
|
+
onFocusLeave={handleDroplistFocusLeave}
|
|
64
|
+
triggerElement={
|
|
65
|
+
<span>
|
|
66
|
+
<ButtonFunction
|
|
67
|
+
icon={<MoreSVG size={24} />}
|
|
68
|
+
data-test-id={TEST_IDS.rowActions.droplistTrigger}
|
|
69
|
+
onKeyDown={handleTriggerKeyDown}
|
|
70
|
+
ref={triggerElementRef}
|
|
71
|
+
/>
|
|
72
|
+
</span>
|
|
73
|
+
}
|
|
74
|
+
triggerClassName={styles.rowActionsCellTrigger}
|
|
75
|
+
size={Droplist.sizes.S}
|
|
76
|
+
data-test-id={TEST_IDS.rowActions.droplist}
|
|
77
|
+
>
|
|
78
|
+
{actions.map(item => (
|
|
79
|
+
<Droplist.ItemSingle
|
|
80
|
+
{...item}
|
|
81
|
+
key={`${row.id}-${item.id || item.option}`}
|
|
82
|
+
onClick={e => handleDroplistItemClick(e, handleItemClick(item))}
|
|
83
|
+
data-test-id={TEST_IDS.rowActions.option}
|
|
84
|
+
onKeyDown={handleDroplistItemKeyDown}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</Droplist>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ActionsGenerator<TData> = (cell: CellContext<TData, unknown>) => RowActionProps<TData>[];
|
|
94
|
+
|
|
95
|
+
export type RowActionsColumnDefProps<TData> = {
|
|
96
|
+
/** Действия для строки */
|
|
97
|
+
actionsGenerator: ActionsGenerator<TData>;
|
|
98
|
+
/** Закрепление колонки справа в таблице */
|
|
99
|
+
pinned?: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Вспомогательная функция для создания ячейки с дополнительными действиями у строки */
|
|
103
|
+
export function getRowActionsColumnDef<TData>({
|
|
104
|
+
actionsGenerator,
|
|
105
|
+
pinned,
|
|
106
|
+
}: RowActionsColumnDefProps<TData>): ColumnDefinition<TData> {
|
|
107
|
+
return {
|
|
108
|
+
id: 'rowActions',
|
|
109
|
+
pinned: pinned ? ColumnPinPosition.Right : (undefined as never),
|
|
110
|
+
size: 40,
|
|
111
|
+
meta: {
|
|
112
|
+
skipOnExport: true,
|
|
113
|
+
},
|
|
114
|
+
noBodyCellPadding: true,
|
|
115
|
+
cellClassName: styles.rowActionsCell,
|
|
116
|
+
cell: cell => <RowActionsCell row={cell.row} actions={actionsGenerator(cell)} />,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RowActionsCell';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
|
|
2
|
+
|
|
3
|
+
.rowActionsCell {
|
|
4
|
+
position: relative;
|
|
5
|
+
border-color: inherit;
|
|
6
|
+
|
|
7
|
+
&::after {
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
content: '';
|
|
10
|
+
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: calc(0px - $border-width-table);
|
|
13
|
+
bottom: calc(0px - $border-width-table);
|
|
14
|
+
left: 0;
|
|
15
|
+
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
width: 100%;
|
|
18
|
+
|
|
19
|
+
/* stylelint-disable */
|
|
20
|
+
border-left: $border-width-table solid;
|
|
21
|
+
border-color: inherit;
|
|
22
|
+
/* stylelint-enable */
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.rowActionsCellWrap {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.rowActionsCellTrigger {
|
|
33
|
+
@include composite-var($table-cells-action);
|
|
34
|
+
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Checkbox, Radio } from '@snack-uikit/toggles';
|
|
4
|
+
|
|
5
|
+
import { ColumnPinPosition, TEST_IDS } from '../../../constants';
|
|
6
|
+
import { ColumnDefinition } from '../../../types';
|
|
7
|
+
import styles from './styles.module.scss';
|
|
8
|
+
|
|
9
|
+
type SelectionCellProps = {
|
|
10
|
+
checked: boolean;
|
|
11
|
+
onChange(checked: boolean): void;
|
|
12
|
+
isMulti?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function SelectionCell({ isMulti, onChange, ...props }: SelectionCellProps) {
|
|
16
|
+
const handleCellClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
17
|
+
e.stopPropagation();
|
|
18
|
+
|
|
19
|
+
onChange(props.checked);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
24
|
+
<div onClick={handleCellClick} className={styles.selectionCell} data-test-id={TEST_IDS.rowSelect}>
|
|
25
|
+
{isMulti ? <Checkbox {...props} size={Checkbox.sizes.S} /> : <Radio {...props} size={Radio.sizes.S} />}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getSelectionCellColumnDef<TData>(): ColumnDefinition<TData> {
|
|
31
|
+
return {
|
|
32
|
+
id: 'selectionCell',
|
|
33
|
+
pinned: ColumnPinPosition.Left,
|
|
34
|
+
noBodyCellPadding: true,
|
|
35
|
+
size: 40,
|
|
36
|
+
headerClassName: styles.selectionCellHeader,
|
|
37
|
+
cell: ({ row, table }) => {
|
|
38
|
+
const disabled = !row.getCanSelect();
|
|
39
|
+
|
|
40
|
+
if (disabled) return null;
|
|
41
|
+
|
|
42
|
+
const { enableMultiRowSelection } = table.options;
|
|
43
|
+
|
|
44
|
+
const isMulti = typeof enableMultiRowSelection === 'boolean' ? enableMultiRowSelection : true;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<SelectionCell isMulti={isMulti} checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
meta: {
|
|
51
|
+
skipOnExport: true,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SelectionCell';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
|
|
2
|
+
|
|
3
|
+
.selectionCell {
|
|
4
|
+
@include composite-var($table-cell-check);
|
|
5
|
+
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.selectionCellHeader {
|
|
17
|
+
padding: 0;
|
|
18
|
+
}
|