@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.
Files changed (144) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/LICENSE +201 -0
  3. package/README.md +165 -0
  4. package/dist/components/Table/Table.d.ts +104 -0
  5. package/dist/components/Table/Table.js +125 -0
  6. package/dist/components/Table/constants.d.ts +3 -0
  7. package/dist/components/Table/constants.js +14 -0
  8. package/dist/components/Table/hooks.d.ts +19 -0
  9. package/dist/components/Table/hooks.js +37 -0
  10. package/dist/components/Table/index.d.ts +1 -0
  11. package/dist/components/Table/index.js +1 -0
  12. package/dist/components/Table/styles.module.css +46 -0
  13. package/dist/components/TableEmptyState/TableEmptyState.d.ts +8 -0
  14. package/dist/components/TableEmptyState/TableEmptyState.js +8 -0
  15. package/dist/components/TableEmptyState/index.d.ts +1 -0
  16. package/dist/components/TableEmptyState/index.js +1 -0
  17. package/dist/components/TableEmptyState/styles.module.css +15 -0
  18. package/dist/components/TablePagination/TablePagination.d.ts +8 -0
  19. package/dist/components/TablePagination/TablePagination.js +19 -0
  20. package/dist/components/TablePagination/index.d.ts +1 -0
  21. package/dist/components/TablePagination/index.js +1 -0
  22. package/dist/components/TablePagination/styles.module.css +11 -0
  23. package/dist/components/index.d.ts +1 -0
  24. package/dist/components/index.js +1 -0
  25. package/dist/constants.d.ts +28 -0
  26. package/dist/constants.js +31 -0
  27. package/dist/exportTable.d.ts +9 -0
  28. package/dist/exportTable.js +51 -0
  29. package/dist/helperComponents/Cells/BodyCell/BodyCell.d.ts +7 -0
  30. package/dist/helperComponents/Cells/BodyCell/BodyCell.js +24 -0
  31. package/dist/helperComponents/Cells/BodyCell/index.d.ts +1 -0
  32. package/dist/helperComponents/Cells/BodyCell/index.js +1 -0
  33. package/dist/helperComponents/Cells/BodyCell/styles.module.css +12 -0
  34. package/dist/helperComponents/Cells/Cell.d.ts +9 -0
  35. package/dist/helperComponents/Cells/Cell.js +20 -0
  36. package/dist/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +7 -0
  37. package/dist/helperComponents/Cells/HeaderCell/HeaderCell.js +17 -0
  38. package/dist/helperComponents/Cells/HeaderCell/helpers.d.ts +2 -0
  39. package/dist/helperComponents/Cells/HeaderCell/helpers.js +12 -0
  40. package/dist/helperComponents/Cells/HeaderCell/index.d.ts +1 -0
  41. package/dist/helperComponents/Cells/HeaderCell/index.js +1 -0
  42. package/dist/helperComponents/Cells/HeaderCell/styles.module.css +53 -0
  43. package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.d.ts +22 -0
  44. package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.js +36 -0
  45. package/dist/helperComponents/Cells/RowActionsCell/index.d.ts +1 -0
  46. package/dist/helperComponents/Cells/RowActionsCell/index.js +1 -0
  47. package/dist/helperComponents/Cells/RowActionsCell/styles.module.css +34 -0
  48. package/dist/helperComponents/Cells/SelectionCell/SelectionCell.d.ts +2 -0
  49. package/dist/helperComponents/Cells/SelectionCell/SelectionCell.js +45 -0
  50. package/dist/helperComponents/Cells/SelectionCell/index.d.ts +1 -0
  51. package/dist/helperComponents/Cells/SelectionCell/index.js +1 -0
  52. package/dist/helperComponents/Cells/SelectionCell/styles.module.css +13 -0
  53. package/dist/helperComponents/Cells/StatusCell/StatusCell.d.ts +27 -0
  54. package/dist/helperComponents/Cells/StatusCell/StatusCell.js +43 -0
  55. package/dist/helperComponents/Cells/StatusCell/constants.d.ts +13 -0
  56. package/dist/helperComponents/Cells/StatusCell/constants.js +14 -0
  57. package/dist/helperComponents/Cells/StatusCell/index.d.ts +1 -0
  58. package/dist/helperComponents/Cells/StatusCell/index.js +1 -0
  59. package/dist/helperComponents/Cells/StatusCell/styles.module.css +96 -0
  60. package/dist/helperComponents/Cells/index.d.ts +5 -0
  61. package/dist/helperComponents/Cells/index.js +5 -0
  62. package/dist/helperComponents/Cells/styles.module.css +13 -0
  63. package/dist/helperComponents/ExportButton/ExportButton.d.ts +10 -0
  64. package/dist/helperComponents/ExportButton/ExportButton.js +27 -0
  65. package/dist/helperComponents/ExportButton/index.d.ts +1 -0
  66. package/dist/helperComponents/ExportButton/index.js +1 -0
  67. package/dist/helperComponents/Rows/BodyRow.d.ts +15 -0
  68. package/dist/helperComponents/Rows/BodyRow.js +25 -0
  69. package/dist/helperComponents/Rows/HeaderRow.d.ts +1 -0
  70. package/dist/helperComponents/Rows/HeaderRow.js +11 -0
  71. package/dist/helperComponents/Rows/PinnedCells.d.ts +8 -0
  72. package/dist/helperComponents/Rows/PinnedCells.js +6 -0
  73. package/dist/helperComponents/Rows/Row.d.ts +10 -0
  74. package/dist/helperComponents/Rows/Row.js +18 -0
  75. package/dist/helperComponents/Rows/index.d.ts +2 -0
  76. package/dist/helperComponents/Rows/index.js +2 -0
  77. package/dist/helperComponents/Rows/styles.module.css +87 -0
  78. package/dist/helperComponents/contexts.d.ts +14 -0
  79. package/dist/helperComponents/contexts.js +14 -0
  80. package/dist/helperComponents/helpers.d.ts +2 -0
  81. package/dist/helperComponents/helpers.js +9 -0
  82. package/dist/helperComponents/hooks.d.ts +28 -0
  83. package/dist/helperComponents/hooks.js +67 -0
  84. package/dist/helperComponents/index.d.ts +7 -0
  85. package/dist/helperComponents/index.js +7 -0
  86. package/dist/helperComponents/types.d.ts +7 -0
  87. package/dist/helperComponents/types.js +1 -0
  88. package/dist/index.d.ts +3 -0
  89. package/dist/index.js +3 -0
  90. package/dist/types.d.ts +38 -0
  91. package/dist/types.js +1 -0
  92. package/dist/utils.d.ts +2 -0
  93. package/dist/utils.js +9 -0
  94. package/package.json +55 -0
  95. package/src/components/Table/Table.tsx +385 -0
  96. package/src/components/Table/constants.ts +18 -0
  97. package/src/components/Table/hooks.tsx +68 -0
  98. package/src/components/Table/index.ts +1 -0
  99. package/src/components/Table/styles.module.scss +55 -0
  100. package/src/components/TableEmptyState/TableEmptyState.tsx +25 -0
  101. package/src/components/TableEmptyState/index.ts +1 -0
  102. package/src/components/TableEmptyState/styles.module.scss +18 -0
  103. package/src/components/TablePagination/TablePagination.tsx +70 -0
  104. package/src/components/TablePagination/index.ts +1 -0
  105. package/src/components/TablePagination/styles.module.scss +13 -0
  106. package/src/components/index.ts +1 -0
  107. package/src/constants.ts +31 -0
  108. package/src/exportTable.ts +82 -0
  109. package/src/helperComponents/Cells/BodyCell/BodyCell.tsx +32 -0
  110. package/src/helperComponents/Cells/BodyCell/index.ts +1 -0
  111. package/src/helperComponents/Cells/BodyCell/styles.module.scss +15 -0
  112. package/src/helperComponents/Cells/Cell.tsx +21 -0
  113. package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +53 -0
  114. package/src/helperComponents/Cells/HeaderCell/helpers.tsx +14 -0
  115. package/src/helperComponents/Cells/HeaderCell/index.ts +1 -0
  116. package/src/helperComponents/Cells/HeaderCell/styles.module.scss +74 -0
  117. package/src/helperComponents/Cells/RowActionsCell/RowActionsCell.tsx +118 -0
  118. package/src/helperComponents/Cells/RowActionsCell/index.ts +1 -0
  119. package/src/helperComponents/Cells/RowActionsCell/styles.module.scss +44 -0
  120. package/src/helperComponents/Cells/SelectionCell/SelectionCell.tsx +54 -0
  121. package/src/helperComponents/Cells/SelectionCell/index.ts +1 -0
  122. package/src/helperComponents/Cells/SelectionCell/styles.module.scss +18 -0
  123. package/src/helperComponents/Cells/StatusCell/StatusCell.tsx +108 -0
  124. package/src/helperComponents/Cells/StatusCell/constants.ts +14 -0
  125. package/src/helperComponents/Cells/StatusCell/index.ts +1 -0
  126. package/src/helperComponents/Cells/StatusCell/styles.module.scss +95 -0
  127. package/src/helperComponents/Cells/index.ts +5 -0
  128. package/src/helperComponents/Cells/styles.module.scss +13 -0
  129. package/src/helperComponents/ExportButton/ExportButton.tsx +70 -0
  130. package/src/helperComponents/ExportButton/index.ts +1 -0
  131. package/src/helperComponents/Rows/BodyRow.tsx +78 -0
  132. package/src/helperComponents/Rows/HeaderRow.tsx +36 -0
  133. package/src/helperComponents/Rows/PinnedCells.tsx +17 -0
  134. package/src/helperComponents/Rows/Row.tsx +20 -0
  135. package/src/helperComponents/Rows/index.ts +2 -0
  136. package/src/helperComponents/Rows/styles.module.scss +127 -0
  137. package/src/helperComponents/contexts.ts +29 -0
  138. package/src/helperComponents/helpers.ts +13 -0
  139. package/src/helperComponents/hooks.ts +84 -0
  140. package/src/helperComponents/index.ts +7 -0
  141. package/src/helperComponents/types.ts +13 -0
  142. package/src/index.ts +3 -0
  143. package/src/types.ts +80 -0
  144. 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,13 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
2
+
3
+ .footer {
4
+ @include composite-var($table-footer);
5
+
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: flex-end;
9
+ }
10
+
11
+ .pagination {
12
+ flex: 1;
13
+ }
@@ -0,0 +1 @@
1
+ export * from './Table';
@@ -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
+ }