@snack-uikit/table 0.12.1 → 0.13.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 (60) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +60 -5
  3. package/dist/components/ServerTable/ServerTable.d.ts +48 -0
  4. package/dist/components/ServerTable/ServerTable.js +38 -0
  5. package/dist/components/ServerTable/constants.d.ts +2 -0
  6. package/dist/components/ServerTable/constants.js +2 -0
  7. package/dist/components/ServerTable/index.d.ts +1 -0
  8. package/dist/components/ServerTable/index.js +1 -0
  9. package/dist/components/ServerTable/utils.d.ts +2 -0
  10. package/dist/components/ServerTable/utils.js +7 -0
  11. package/dist/components/Table/Table.d.ts +2 -1
  12. package/dist/components/Table/Table.js +14 -6
  13. package/dist/components/Table/hooks.js +2 -2
  14. package/dist/components/TableEmptyState/TableEmptyState.d.ts +6 -4
  15. package/dist/components/TableEmptyState/TableEmptyState.js +16 -5
  16. package/dist/components/TableEmptyState/styles.module.css +0 -7
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.js +1 -0
  19. package/dist/constants.d.ts +1 -0
  20. package/dist/constants.js +1 -0
  21. package/dist/helperComponents/Cells/CopyCell/CopyCell.d.ts +4 -0
  22. package/dist/helperComponents/Cells/CopyCell/CopyCell.js +7 -0
  23. package/dist/helperComponents/Cells/CopyCell/components/CopyButton/CopyButton.d.ts +5 -0
  24. package/dist/helperComponents/Cells/CopyCell/components/CopyButton/CopyButton.js +23 -0
  25. package/dist/helperComponents/Cells/CopyCell/components/CopyButton/index.d.ts +1 -0
  26. package/dist/helperComponents/Cells/CopyCell/components/CopyButton/index.js +1 -0
  27. package/dist/helperComponents/Cells/CopyCell/components/index.d.ts +1 -0
  28. package/dist/helperComponents/Cells/CopyCell/components/index.js +1 -0
  29. package/dist/helperComponents/Cells/CopyCell/index.d.ts +1 -0
  30. package/dist/helperComponents/Cells/CopyCell/index.js +1 -0
  31. package/dist/helperComponents/Cells/CopyCell/styles.module.css +14 -0
  32. package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.d.ts +1 -1
  33. package/dist/helperComponents/Cells/StatusCell/StatusCell.d.ts +2 -1
  34. package/dist/helperComponents/Cells/index.d.ts +1 -0
  35. package/dist/helperComponents/Cells/index.js +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/types.d.ts +1 -1
  39. package/package.json +7 -4
  40. package/src/components/ServerTable/ServerTable.tsx +113 -0
  41. package/src/components/ServerTable/constants.ts +2 -0
  42. package/src/components/ServerTable/index.ts +1 -0
  43. package/src/components/ServerTable/utils.ts +9 -0
  44. package/src/components/Table/Table.tsx +28 -7
  45. package/src/components/Table/hooks.tsx +2 -4
  46. package/src/components/TableEmptyState/TableEmptyState.tsx +9 -11
  47. package/src/components/TableEmptyState/styles.module.scss +0 -7
  48. package/src/components/index.ts +1 -0
  49. package/src/constants.ts +2 -0
  50. package/src/helperComponents/Cells/CopyCell/CopyCell.tsx +17 -0
  51. package/src/helperComponents/Cells/CopyCell/components/CopyButton/CopyButton.tsx +44 -0
  52. package/src/helperComponents/Cells/CopyCell/components/CopyButton/index.ts +1 -0
  53. package/src/helperComponents/Cells/CopyCell/components/index.ts +1 -0
  54. package/src/helperComponents/Cells/CopyCell/index.ts +1 -0
  55. package/src/helperComponents/Cells/CopyCell/styles.module.scss +21 -0
  56. package/src/helperComponents/Cells/RowActionsCell/RowActionsCell.tsx +2 -2
  57. package/src/helperComponents/Cells/StatusCell/StatusCell.tsx +3 -1
  58. package/src/helperComponents/Cells/index.ts +1 -0
  59. package/src/index.ts +2 -0
  60. package/src/types.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.13.0 (2024-02-07)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **FF-4187:** change local no data to InfoBlock ([542cf0d](https://github.com/cloud-ru-tech/snack-uikit/commit/542cf0de8fed7f4ab5e2bca3afff1318b30a6043))
12
+
13
+
14
+ ### Features
15
+
16
+ * **FF-4241:** add ServerTable; add CopyCell ([2cf16c1](https://github.com/cloud-ru-tech/snack-uikit/commit/2cf16c15e72a93f20d40c1bc54d7708c1a273079))
17
+
18
+
19
+
20
+
21
+
6
22
  ## 0.12.1 (2024-02-07)
7
23
 
8
24
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -1,17 +1,19 @@
1
1
  # Table
2
2
 
3
3
  ## Installation
4
+
4
5
  `npm i @snack-uikit/table`
5
6
 
6
7
  [Changelog](./CHANGELOG.md)
7
8
 
8
9
  ## TODO:
9
- - multiple row selection with Shift key pressed
10
10
 
11
+ - multiple row selection with Shift key pressed
11
12
 
12
13
  ## Примечание
14
+
13
15
  > Для оптимальной работы таблицы используйте `useMemo` для `columnDefinitions`
14
- или выносите их определение за компонент
16
+ > или выносите их определение за компонент
15
17
 
16
18
  ## Example
17
19
 
@@ -88,16 +90,20 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
88
90
  data={data}
89
91
  rowSelection={{
90
92
  enable: true, // false выключает выбор всех строк (состояние disabled)
91
- // или можно передать функцию для вычисления
93
+ // или можно передать функцию для вычисления
92
94
  // enable: row => !['Pending', 'Loading'].includes(row.original.status),
93
95
  onChange: handleRowSelection,
94
96
  }}
95
97
  />
96
98
  ```
97
99
 
98
-
99
100
  [//]: DOCUMENTATION_SECTION_START
100
101
  [//]: THIS_SECTION_IS_AUTOGENERATED_PLEASE_DONT_EDIT_IT
102
+ ## CopyCell
103
+ ### Props
104
+ | name | type | default value | description |
105
+ |------|------|---------------|-------------|
106
+ | value | `string \| number` | - | |
101
107
  ## Table
102
108
  Компонент таблицы
103
109
  ### Props
@@ -118,6 +124,7 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
118
124
  | onDelete | `(selectionState: RowSelectionState, resetRowSelection: (defaultState?: boolean) => void) => void` | - | Колбек удаления выбранных |
119
125
  | outline | `boolean` | - | Внешний бордер для тулбара и таблицы |
120
126
  | columnFilters | `ReactNode` | - | Фильтры |
127
+ | dataFiltered | `boolean` | - | |
121
128
  | exportFileName | `string` | - | Название файла при экспорте CSV/XLSX |
122
129
  | moreActions | `Action[]` | - | Элементы выпадающего списка кнопки с действиями |
123
130
  | noDataState | `TableEmptyStateProps` | - | Экран при отстутствии данных |
@@ -130,7 +137,7 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
130
137
  ### Props
131
138
  | name | type | default value | description |
132
139
  |------|------|---------------|-------------|
133
- | mapStatusToAppearance* | `(value: string \| number) => StatusAppearance` | - | Маппинг значений статуса на цвета |
140
+ | mapStatusToAppearance* | `MapStatusToAppearanceFnType` | - | Маппинг значений статуса на цвета |
134
141
  | accessorKey* | `string` | - | Имя ключа соответствующее полю в data |
135
142
  | enableSorting | `boolean` | - | Включение/выключение сортировки |
136
143
  | renderDescription | `(cellValue: string) => string` | - | Функция для отрисовки текста, если не передана, то будет отрисован только индикатор статуса |
@@ -144,6 +151,54 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
144
151
  |------|------|---------------|-------------|
145
152
  | actionsGenerator* | `ActionsGenerator<TData>` | - | Действия для строки |
146
153
  | pinned | `boolean` | - | Закрепление колонки справа в таблице |
154
+ ## ServerTable
155
+ ### Props
156
+ | name | type | default value | description |
157
+ |------|------|---------------|-------------|
158
+ | search* | `{ initialValue?: string; state: string; placeholder?: string; loading?: boolean; onChange(value: string): void; }` | 'Search...'<br> <strong>loading</strong>: Состояние загрузки в строке поиска <br> <strong>onChange</strong>: Колбэк на изменение данных в строке поиска | Параметры отвечают за глобальный поиск в таблице <br> <strong>initialState</strong>: Начальное состояние строки поиска <br> <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br> <strong>placeholder</strong>: Placeholder строки поиска |
159
+ | onChangePage* | `(offset: number, limit: number) => void` | - | |
160
+ | columnDefinitions* | `ColumnDefinition<TData>[]` | - | Определение внешнего вида и функционала колонок |
161
+ | loading | `boolean` | - | Состояние загрузки |
162
+ | sorting | `{ initialState?: SortingState; state?: SortingState; onChange?(state: SortingState): void; }` | - | Параметры отвечают за возможность сортировки, их стоит использовать если нужно отслеживать состояние <br> <strong>initialState</strong>: Начальное состояние сортировки <br> <strong>state</strong>: Состояние сортировки, жестко устанавливаемое снаружи <br> <strong>onChange</strong>: Колбэк на изменение сортировки |
163
+ | rowSelection | `{ initialState?: RowSelectionState; state?: RowSelectionState; enable?: boolean \| ((row: Row<TData>) => boolean); multiRow?: boolean; onChange?(state: RowSelectionState): void; }` | - | Параметры отвечают за возможность выбора строк <br> <strong>initialState</strong>: Начальное состояние выбора строк <br> <strong>state</strong>: Состояние выбора строк, жестко устанавливаемое снаружи <br> <strong>enable</strong>: Колбэк определяющий можно ли выбрать строку <br> <strong>multiRow</strong>: Мульти-выбор строк (включен по-умолчанию, когда включается выбор) <br> <strong>onChange</strong>: Колбэк на выбор строк |
164
+ | onRowClick | `RowClickHandler<TData>` | - | Колбэк клика по строке |
165
+ | className | `string` | - | CSS-класс |
166
+ | onRefresh | `() => void` | - | Колбек обновления данных |
167
+ | onDelete | `(selectionState: RowSelectionState, resetRowSelection: (defaultState?: boolean) => void) => void` | - | Колбек удаления выбранных |
168
+ | outline | `boolean` | - | Внешний бордер для тулбара и таблицы |
169
+ | columnFilters | `ReactNode` | - | Фильтры |
170
+ | dataFiltered | `boolean` | - | |
171
+ | exportFileName | `string` | - | Название файла при экспорте CSV/XLSX |
172
+ | moreActions | `Action[]` | - | Элементы выпадающего списка кнопки с действиями |
173
+ | noDataState | `TableEmptyStateProps` | - | Экран при отстутствии данных |
174
+ | noResultsState | `TableEmptyStateProps` | - | Экран при отстутствии результатов поиска |
175
+ | suppressToolbar | `boolean` | - | Отключение тулбара |
176
+ | toolbarBefore | `ReactNode` | - | Дополнительный слот в `Toolbar` перед строкой поиска |
177
+ | suppressPagination | `boolean` | - | Отключение пагинации |
178
+ | items | `TData[]` | - | Данные для отрисовки |
179
+ | total | `number` | 10 | Общее кол-во строк |
180
+ | limit | `number` | 10 | Кол-во строк на страницу |
181
+ | offset | `number` | - | Смещение |
182
+ | pagination | `{ options?: number[]; optionsLabel?: string; }` | 'Rows volume' <br> | Параметры отвечают за пагинацию в таблице <br> <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу<br> <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу |
183
+ ## ServerTable.getRowActionsColumnDef
184
+ Вспомогательная функция для создания ячейки с дополнительными действиями у строки
185
+ ### Props
186
+ | name | type | default value | description |
187
+ |------|------|---------------|-------------|
188
+ | actionsGenerator* | `ActionsGenerator<TData>` | - | Действия для строки |
189
+ | pinned | `boolean` | - | Закрепление колонки справа в таблице |
190
+ ## ServerTable.getStatusColumnDef
191
+ Вспомогательная функция для создания ячейки со статусом
192
+ ### Props
193
+ | name | type | default value | description |
194
+ |------|------|---------------|-------------|
195
+ | mapStatusToAppearance* | `MapStatusToAppearanceFnType` | - | Маппинг значений статуса на цвета |
196
+ | accessorKey* | `string` | - | Имя ключа соответствующее полю в data |
197
+ | enableSorting | `boolean` | - | Включение/выключение сортировки |
198
+ | renderDescription | `(cellValue: string) => string` | - | Функция для отрисовки текста, если не передана, то будет отрисован только индикатор статуса |
199
+ | size | `number` | - | Размер ячейки |
200
+ | header | `ColumnDefTemplate<HeaderContext<TData, unknown>> & (string \| ((ctx: HeaderContext<TData, unknown>) => string))` | - | Заголовок колонки |
201
+ | enableResizing | `boolean` | - | Включение/выключение ресайза колонки |
147
202
  ## exportToCSV
148
203
  ### Props
149
204
  | name | type | default value | description |
@@ -0,0 +1,48 @@
1
+ import { TableProps } from '../Table';
2
+ export type ServerTableProps<TData extends object> = Omit<TableProps<TData>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
3
+ /** Данные для отрисовки */
4
+ items?: TData[];
5
+ /**
6
+ * Общее кол-во строк
7
+ * @default 10
8
+ */
9
+ total?: number;
10
+ /**
11
+ * Кол-во строк на страницу
12
+ * @default 10
13
+ */
14
+ limit?: number;
15
+ /**
16
+ * Смещение
17
+ * @default 0
18
+ * */
19
+ offset?: number;
20
+ onChangePage(offset: number, limit: number): void;
21
+ /** Параметры отвечают за глобальный поиск в таблице <br>
22
+ * <strong>initialState</strong>: Начальное состояние строки поиска <br>
23
+ * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
24
+ * <strong>placeholder</strong>: Placeholder строки поиска @default 'Search...'<br>
25
+ * <strong>loading</strong>: Состояние загрузки в строке поиска <br>
26
+ * <strong>onChange</strong>: Колбэк на изменение данных в строке поиска
27
+ * */
28
+ search: {
29
+ initialValue?: string;
30
+ state: string;
31
+ placeholder?: string;
32
+ loading?: boolean;
33
+ onChange(value: string): void;
34
+ };
35
+ /** Параметры отвечают за пагинацию в таблице <br>
36
+ * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу<br>
37
+ * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume' <br>
38
+ * */
39
+ pagination?: {
40
+ options?: number[];
41
+ optionsLabel?: string;
42
+ };
43
+ };
44
+ export declare function ServerTable<TData extends object>({ items, total, limit, offset, onChangePage, search, pagination, columnFilters, ...rest }: ServerTableProps<TData>): import("react/jsx-runtime").JSX.Element;
45
+ export declare namespace ServerTable {
46
+ var getRowActionsColumnDef: typeof import("../../helperComponents").getRowActionsColumnDef;
47
+ var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
48
+ }
@@ -0,0 +1,38 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { useCallback, useMemo, useState } from 'react';
14
+ import { Table } from '../Table';
15
+ import { DEFAULT_PAGINATION_LIMIT } from './constants';
16
+ import { onSearchDebounced } from './utils';
17
+ export function ServerTable(_a) {
18
+ var { items, total = DEFAULT_PAGINATION_LIMIT, limit = DEFAULT_PAGINATION_LIMIT, offset = 0, onChangePage, search, pagination, columnFilters } = _a, rest = __rest(_a, ["items", "total", "limit", "offset", "onChangePage", "search", "pagination", "columnFilters"]);
19
+ const [tempSearch, setTempSearch] = useState(search.initialValue || '');
20
+ const handleSearch = useCallback((newValue) => {
21
+ setTempSearch(newValue);
22
+ onSearchDebounced()(newValue.trim(), search.onChange);
23
+ }, [search.onChange]);
24
+ const handlePageChange = useCallback(({ pageSize, pageIndex }) => onChangePage(pageIndex * pageSize, pageSize), [onChangePage]);
25
+ const pageIndex = useMemo(() => Math.floor(offset / limit), [limit, offset]);
26
+ const pageCount = useMemo(() => Math.ceil(total / limit), [limit, total]);
27
+ return (_jsx(Table, Object.assign({}, rest, { data: items || [], search: {
28
+ state: tempSearch,
29
+ onChange: handleSearch,
30
+ loading: search.loading,
31
+ placeholder: search.placeholder,
32
+ }, columnFilters: columnFilters, pageCount: pageCount, pagination: Object.assign(Object.assign({}, pagination), { state: {
33
+ pageIndex,
34
+ pageSize: limit,
35
+ }, onChange: handlePageChange }), pageSize: limit })));
36
+ }
37
+ ServerTable.getRowActionsColumnDef = Table.getRowActionsColumnDef;
38
+ ServerTable.getStatusColumnDef = Table.getStatusColumnDef;
@@ -0,0 +1,2 @@
1
+ export declare const SEARCH_DELAY = 500;
2
+ export declare const DEFAULT_PAGINATION_LIMIT = 10;
@@ -0,0 +1,2 @@
1
+ export const SEARCH_DELAY = 500;
2
+ export const DEFAULT_PAGINATION_LIMIT = 10;
@@ -0,0 +1 @@
1
+ export * from './ServerTable';
@@ -0,0 +1 @@
1
+ export * from './ServerTable';
@@ -0,0 +1,2 @@
1
+ /// <reference types="lodash" />
2
+ export declare function onSearchDebounced(): import("lodash").DebouncedFunc<(newValue: string, onChange: (newValue: string) => void) => void>;
@@ -0,0 +1,7 @@
1
+ import debounce from 'lodash.debounce';
2
+ import { SEARCH_DELAY } from './constants';
3
+ export function onSearchDebounced() {
4
+ return debounce((newValue, onChange) => {
5
+ onChange(newValue);
6
+ }, SEARCH_DELAY);
7
+ }
@@ -78,6 +78,7 @@ export type TableProps<TData extends object> = WithSupportProps<{
78
78
  outline?: boolean;
79
79
  /** Фильтры */
80
80
  columnFilters?: ReactNode;
81
+ dataFiltered?: boolean;
81
82
  /** Название файла при экспорте CSV/XLSX */
82
83
  exportFileName?: string;
83
84
  /** Элементы выпадающего списка кнопки с действиями */
@@ -94,7 +95,7 @@ export type TableProps<TData extends object> = WithSupportProps<{
94
95
  suppressPagination?: boolean;
95
96
  }>;
96
97
  /** Компонент таблицы */
97
- export declare function Table<TData extends object>({ data, columnDefinitions, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters: columnFiltersProp, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportFileName, noDataState, noResultsState, suppressToolbar, toolbarBefore, suppressPagination, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
98
+ export declare function Table<TData extends object>({ data, columnDefinitions, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters: columnFiltersProp, dataFiltered, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportFileName, noDataState, noResultsState, suppressToolbar, toolbarBefore, suppressPagination, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
98
99
  export declare namespace Table {
99
100
  var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
100
101
  var statusAppearances: {
@@ -19,6 +19,7 @@ import { SkeletonContextProvider } from '@snack-uikit/skeleton';
19
19
  import { Toolbar } from '@snack-uikit/toolbar';
20
20
  import { TruncateString } from '@snack-uikit/truncate-string';
21
21
  import { extractSupportProps } from '@snack-uikit/utils';
22
+ import { DEFAULT_PAGE_SIZE } from '../../constants';
22
23
  import { BodyRow, ExportButton, getColumnId, getRowActionsColumnDef, getSelectionCellColumnDef, getStatusColumnDef, HeaderRow, STATUS_APPEARANCE, TableContext, } from '../../helperComponents';
23
24
  import { fuzzyFilter } from '../../utils';
24
25
  import { TableEmptyState } from '../TableEmptyState';
@@ -27,7 +28,7 @@ import { useLoadingTable, useStateControl, useTableEmptyState } from './hooks';
27
28
  import styles from './styles.module.css';
28
29
  /** Компонент таблицы */
29
30
  export function Table(_a) {
30
- var { data, columnDefinitions, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters: columnFiltersProp, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize = 10, pageCount, loading = false, outline = false, moreActions, exportFileName, noDataState, noResultsState, suppressToolbar = false, toolbarBefore, suppressPagination = false } = _a, rest = __rest(_a, ["data", "columnDefinitions", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportFileName", "noDataState", "noResultsState", "suppressToolbar", "toolbarBefore", "suppressPagination"]);
31
+ var { data, columnDefinitions, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters: columnFiltersProp, dataFiltered, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize = DEFAULT_PAGE_SIZE, pageCount, loading = false, outline = false, moreActions, exportFileName, noDataState, noResultsState, suppressToolbar = false, toolbarBefore, suppressPagination = false } = _a, rest = __rest(_a, ["data", "columnDefinitions", "rowSelection", "search", "sorting", "columnFilters", "dataFiltered", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportFileName", "noDataState", "noResultsState", "suppressToolbar", "toolbarBefore", "suppressPagination"]);
31
32
  const { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl(search, '');
32
33
  const { state: rowSelection, onStateChange: onRowSelectionChange } = useStateControl(rowSelectionProp, {});
33
34
  const defaultPaginationState = useMemo(() => ({
@@ -73,6 +74,9 @@ export function Table(_a) {
73
74
  enableResizing: false,
74
75
  minSize: 40,
75
76
  },
77
+ manualSorting: Boolean(pageCount),
78
+ manualPagination: pageCount !== undefined,
79
+ manualFiltering: Boolean(pageCount),
76
80
  globalFilterFn: fuzzyFilter,
77
81
  onGlobalFilterChange,
78
82
  onRowSelectionChange,
@@ -82,9 +86,7 @@ export function Table(_a) {
82
86
  getFilteredRowModel: getFilteredRowModel(),
83
87
  enableColumnResizing: true,
84
88
  enableSorting: true,
85
- manualSorting: false,
86
- enableMultiSort: false,
87
- manualPagination: pageCount !== undefined,
89
+ enableMultiSort: true,
88
90
  onSortingChange,
89
91
  getSortedRowModel: getSortedRowModel(),
90
92
  onPaginationChange,
@@ -142,12 +144,18 @@ export function Table(_a) {
142
144
  const tablePagination = table.getState().pagination;
143
145
  const [locales] = useLocale('Table');
144
146
  const emptyStates = useTableEmptyState({ noDataState, noResultsState });
145
- return (_jsx(_Fragment, { children: _jsxs("div", Object.assign({ style: { '--page-size': !suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize }, className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [!suppressToolbar && (_jsxs("div", { className: styles.header, children: [_jsx(Toolbar, { search: {
147
+ const cssPageSize = useMemo(() => {
148
+ const tempPageSize = !suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize;
149
+ return !tableRows.length ? Math.min(Math.max(tempPageSize, 5), DEFAULT_PAGE_SIZE) : tempPageSize;
150
+ }, [pageSize, suppressPagination, tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize, tableRows.length]);
151
+ return (_jsx(_Fragment, { children: _jsxs("div", Object.assign({ style: {
152
+ '--page-size': cssPageSize,
153
+ }, className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [!suppressToolbar && (_jsxs("div", { className: styles.header, children: [_jsx(Toolbar, { search: {
146
154
  value: globalFilter,
147
155
  onChange: onGlobalFilterChange,
148
156
  loading: search === null || search === void 0 ? void 0 : search.loading,
149
157
  placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || locales.searchPlaceholder,
150
- }, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', before: toolbarBefore, after: exportFileName ? (_jsx(ExportButton, { fileName: exportFileName, columnDefinitions: columnDefinitions, data: data })) : undefined, moreActions: moreActions }), columnFiltersProp && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFiltersProp, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsx(Scroll, { size: 's', className: styles.table, children: _jsx("div", { className: styles.tableContent, style: columnSizeVars, children: _jsx(TableContext.Provider, { value: { table }, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [tableRows.length ? _jsx(HeaderRow, {}) : null, tableRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))), !tableRows.length && globalFilter && _jsx(TableEmptyState, Object.assign({}, emptyStates.noResultsState)), !tableRows.length && !globalFilter && _jsx(TableEmptyState, Object.assign({}, emptyStates.noDataState))] })) }) }) }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount }))] })) }));
158
+ }, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', before: toolbarBefore, after: exportFileName ? (_jsx(ExportButton, { fileName: exportFileName, columnDefinitions: columnDefinitions, data: data })) : undefined, moreActions: moreActions }), columnFiltersProp && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFiltersProp, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsx(Scroll, { size: 's', className: styles.table, children: _jsx("div", { className: styles.tableContent, style: columnSizeVars, children: _jsx(TableContext.Provider, { value: { table }, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [tableRows.length ? _jsx(HeaderRow, {}) : null, tableRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))), !tableRows.length && (globalFilter || dataFiltered) && (_jsx(TableEmptyState, Object.assign({}, emptyStates.noResultsState))), !tableRows.length && !globalFilter && !dataFiltered && (_jsx(TableEmptyState, Object.assign({}, emptyStates.noDataState)))] })) }) }) }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount }))] })) }));
151
159
  }
152
160
  Table.getStatusColumnDef = getStatusColumnDef;
153
161
  Table.statusAppearances = STATUS_APPEARANCE;
@@ -40,8 +40,8 @@ export function useLoadingTable({ pageSize, columnDefinitions, columnPinning })
40
40
  export function useTableEmptyState({ noDataState: noDataStateProp, noResultsState: noResultsStateProp, }) {
41
41
  const [locales] = useLocale('Table');
42
42
  return useMemo(() => {
43
- const noDataState = noDataStateProp !== null && noDataStateProp !== void 0 ? noDataStateProp : Object.assign({ icon: CrossSVG, appearance: 'red' }, locales.noData);
44
- const noResultsState = noResultsStateProp !== null && noResultsStateProp !== void 0 ? noResultsStateProp : Object.assign({ icon: SearchSVG, appearance: 'neutral' }, locales.noResults);
43
+ const noDataState = noDataStateProp !== null && noDataStateProp !== void 0 ? noDataStateProp : Object.assign({ icon: { icon: CrossSVG, appearance: 'red', decor: true } }, locales.noData);
44
+ const noResultsState = noResultsStateProp !== null && noResultsStateProp !== void 0 ? noResultsStateProp : Object.assign({ icon: { icon: SearchSVG, appearance: 'neutral', decor: true } }, locales.noResults);
45
45
  return {
46
46
  noDataState,
47
47
  noResultsState,
@@ -1,8 +1,10 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { IconPredefinedProps } from '@snack-uikit/icon-predefined';
3
3
  export type TableEmptyStateProps = {
4
- title: string;
5
- description?: ReactNode;
4
+ title?: string;
5
+ description: string;
6
+ icon?: Pick<IconPredefinedProps, 'icon' | 'decor' | 'appearance'>;
7
+ footer?: ReactNode;
6
8
  className?: string;
7
- } & Pick<IconPredefinedProps, 'icon' | 'appearance'>;
8
- export declare function TableEmptyState({ title, description, className, icon, appearance }: TableEmptyStateProps): import("react/jsx-runtime").JSX.Element;
9
+ };
10
+ export declare function TableEmptyState({ className, ...props }: TableEmptyStateProps): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,19 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
2
13
  import cn from 'classnames';
3
- import { IconPredefined } from '@snack-uikit/icon-predefined';
4
- import { Typography } from '@snack-uikit/typography';
14
+ import { InfoBlock } from '@snack-uikit/info-block';
5
15
  import styles from './styles.module.css';
6
- export function TableEmptyState({ title, description, className, icon, appearance }) {
7
- return (_jsxs("div", { className: cn(styles.tableEmptyStateWrapper, className), children: [_jsx(IconPredefined, { icon: icon, size: 'l', appearance: appearance }), _jsxs("div", { className: styles.textWrapper, children: [_jsx(Typography.SansTitleM, { children: title }), description && _jsx(Typography.SansBodyM, { children: description })] })] }));
16
+ export function TableEmptyState(_a) {
17
+ var { className } = _a, props = __rest(_a, ["className"]);
18
+ return (_jsx("div", { className: cn(styles.tableEmptyStateWrapper, className), children: _jsx(InfoBlock, Object.assign({}, props, { size: 'm', align: 'vertical' })) }));
8
19
  }
@@ -5,11 +5,4 @@
5
5
  align-items:center;
6
6
  justify-content:center;
7
7
  height:calc(var(--page-size, 10) * var(--size-table-line-height, 40px) + var(--size-table-line-height, 40px));
8
- }
9
-
10
- .textWrapper{
11
- display:flex;
12
- flex-direction:column;
13
- align-items:center;
14
- justify-content:center;
15
8
  }
@@ -1 +1,2 @@
1
1
  export * from './Table';
2
+ export * from './ServerTable';
@@ -1 +1,2 @@
1
1
  export * from './Table';
2
+ export * from './ServerTable';
@@ -26,3 +26,4 @@ export declare const SORT_FN: {
26
26
  readonly DateTime: "datetime";
27
27
  readonly AlphaNumeric: "alphanumeric";
28
28
  };
29
+ export declare const DEFAULT_PAGE_SIZE = 10;
package/dist/constants.js CHANGED
@@ -26,3 +26,4 @@ export const SORT_FN = {
26
26
  DateTime: 'datetime',
27
27
  AlphaNumeric: 'alphanumeric',
28
28
  };
29
+ export const DEFAULT_PAGE_SIZE = 10;
@@ -0,0 +1,4 @@
1
+ export type CopyCellProps = {
2
+ value?: string | number;
3
+ };
4
+ export declare function CopyCell({ value }: CopyCellProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TruncateString } from '@snack-uikit/truncate-string';
3
+ import { CopyButton } from './components';
4
+ import styles from './styles.module.css';
5
+ export function CopyCell({ value }) {
6
+ return (_jsxs("div", { className: styles.copyCell, children: [_jsx(TruncateString, { text: String(value), maxLines: 1 }), _jsx(CopyButton, { valueToCopy: value, className: styles.copyButton })] }));
7
+ }
@@ -0,0 +1,5 @@
1
+ export type CopyButtonProps = {
2
+ valueToCopy?: string | number;
3
+ className?: string;
4
+ };
5
+ export declare function CopyButton({ valueToCopy, className }: CopyButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import copyToClipboard from 'copy-to-clipboard';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { ButtonFunction } from '@snack-uikit/button';
5
+ import { CheckSVG, CopySVG } from '@snack-uikit/icons';
6
+ export function CopyButton({ valueToCopy, className }) {
7
+ const [isChecked, setIsCheckedOpen] = useState(false);
8
+ const timerId = useRef();
9
+ const openChecked = () => setIsCheckedOpen(true);
10
+ const closeChecked = () => setIsCheckedOpen(false);
11
+ const handleClick = event => {
12
+ event.stopPropagation();
13
+ valueToCopy && copyToClipboard(String(valueToCopy), { format: 'text/plain' });
14
+ openChecked();
15
+ clearTimeout(timerId.current);
16
+ timerId.current = setTimeout(closeChecked, 1000);
17
+ };
18
+ useEffect(() => () => {
19
+ closeChecked();
20
+ clearTimeout(timerId.current);
21
+ }, []);
22
+ return (_jsx(ButtonFunction, { onClick: handleClick, "data-test-id": 'button-copy-value', type: 'button', icon: isChecked ? _jsx(CheckSVG, {}) : _jsx(CopySVG, {}), size: 's', className: className }));
23
+ }
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyCell';
@@ -0,0 +1 @@
1
+ export * from './CopyCell';
@@ -0,0 +1,14 @@
1
+ .copyCell{
2
+ cursor:pointer;
3
+ display:flex;
4
+ gap:var(--dimension-050m, 4px);
5
+ align-items:center;
6
+ min-width:0;
7
+ max-width:100%;
8
+ }
9
+ .copyCell .copyButton{
10
+ display:none;
11
+ }
12
+ .copyCell:hover .copyButton{
13
+ display:inline-flex;
14
+ }
@@ -12,7 +12,7 @@ export type RowActionProps<TData> = Pick<BaseItemProps, 'content' | 'disabled'>
12
12
  tagLabel?: string;
13
13
  id?: string;
14
14
  hidden?: boolean;
15
- onClick(row: RowActionInfo<TData>, e: MouseEvent<HTMLButtonElement>): void;
15
+ onClick(row: RowActionInfo<TData>, e: MouseEvent<HTMLElement>): void;
16
16
  };
17
17
  export type ActionsGenerator<TData> = (cell: CellContext<TData, unknown>) => RowActionProps<TData>[];
18
18
  export type RowActionsColumnDefProps<TData> = {
@@ -3,11 +3,12 @@ import { STATUS_APPEARANCE } from './constants';
3
3
  import { StatusAppearance } from './types';
4
4
  export type { StatusAppearance };
5
5
  export { STATUS_APPEARANCE };
6
+ export type MapStatusToAppearanceFnType = (value: string | number) => StatusAppearance;
6
7
  type BaseStatusColumnDef = {
7
8
  /** Имя ключа соответствующее полю в data */
8
9
  accessorKey: string;
9
10
  /** Маппинг значений статуса на цвета */
10
- mapStatusToAppearance(value: string | number): StatusAppearance;
11
+ mapStatusToAppearance: MapStatusToAppearanceFnType;
11
12
  /** Включение/выключение сортировки */
12
13
  enableSorting?: boolean;
13
14
  };
@@ -3,3 +3,4 @@ export * from './RowActionsCell';
3
3
  export * from './HeaderCell';
4
4
  export * from './SelectionCell';
5
5
  export * from './StatusCell';
6
+ export * from './CopyCell';
@@ -3,3 +3,4 @@ export * from './RowActionsCell';
3
3
  export * from './HeaderCell';
4
4
  export * from './SelectionCell';
5
5
  export * from './StatusCell';
6
+ export * from './CopyCell';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './components';
2
2
  export * from './types';
3
3
  export * from './exportTable';
4
+ export { type CopyCellProps, type ActionsGenerator, CopyCell } from './helperComponents';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './components';
2
2
  export * from './types';
3
3
  export * from './exportTable';
4
+ export { CopyCell } from './helperComponents';
package/dist/types.d.ts CHANGED
@@ -37,5 +37,5 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
37
37
  size: number;
38
38
  };
39
39
  export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
40
- export type { RowActionInfo, RowActionProps, RowActionsColumnDefProps, StatusColumnDefinitionProps, RowInfo, RowClickHandler, ActionsGenerator, } from './helperComponents';
40
+ export type { RowActionInfo, RowActionProps, RowActionsColumnDefProps, StatusColumnDefinitionProps, RowInfo, RowClickHandler, ActionsGenerator, CopyCellProps, MapStatusToAppearanceFnType, } from './helperComponents';
41
41
  export type { PaginationState, SortingState, RowSelectionState, RowSelectionOptions, TableEmptyStateProps, ToolbarProps, HeaderContext, CellContext, };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Table",
7
- "version": "0.12.1",
7
+ "version": "0.13.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -36,22 +36,25 @@
36
36
  "@snack-uikit/chips": "0.11.1",
37
37
  "@snack-uikit/icon-predefined": "0.4.1",
38
38
  "@snack-uikit/icons": "0.20.1",
39
- "@snack-uikit/list": "0.2.1",
39
+ "@snack-uikit/info-block": "0.1.1",
40
+ "@snack-uikit/list": "0.3.0",
40
41
  "@snack-uikit/locale": "0.1.1",
41
42
  "@snack-uikit/pagination": "0.6.3",
42
43
  "@snack-uikit/scroll": "0.5.0",
43
44
  "@snack-uikit/skeleton": "0.3.2",
44
45
  "@snack-uikit/tag": "0.7.2",
45
46
  "@snack-uikit/toggles": "0.9.5",
46
- "@snack-uikit/toolbar": "0.7.6",
47
+ "@snack-uikit/toolbar": "0.7.7",
47
48
  "@snack-uikit/truncate-string": "0.4.7",
48
49
  "@snack-uikit/typography": "0.6.1",
49
50
  "@snack-uikit/utils": "3.2.0",
50
51
  "@tanstack/match-sorter-utils": "8.8.4",
51
52
  "@tanstack/react-table": "8.11.6",
52
53
  "classnames": "2.3.2",
54
+ "copy-to-clipboard": "3.3.3",
55
+ "lodash.debounce": "4.0.8",
53
56
  "uncontrollable": "8.0.0",
54
57
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz"
55
58
  },
56
- "gitHead": "0e3f0ca15d2f0903a25bfa72bd95da9a414bddc2"
59
+ "gitHead": "88a44a8f4c6490f3004f7feac3d50d17ff03f31c"
57
60
  }
@@ -0,0 +1,113 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+
3
+ import { PaginationState } from '../../types';
4
+ import { Table, TableProps } from '../Table';
5
+ import { DEFAULT_PAGINATION_LIMIT } from './constants';
6
+ import { onSearchDebounced } from './utils';
7
+
8
+ export type ServerTableProps<TData extends object> = Omit<
9
+ TableProps<TData>,
10
+ 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'
11
+ > & {
12
+ /** Данные для отрисовки */
13
+ items?: TData[];
14
+ /**
15
+ * Общее кол-во строк
16
+ * @default 10
17
+ */
18
+ total?: number;
19
+ /**
20
+ * Кол-во строк на страницу
21
+ * @default 10
22
+ */
23
+ limit?: number;
24
+ /**
25
+ * Смещение
26
+ * @default 0
27
+ * */
28
+ offset?: number;
29
+
30
+ onChangePage(offset: number, limit: number): void;
31
+
32
+ /** Параметры отвечают за глобальный поиск в таблице <br>
33
+ * <strong>initialState</strong>: Начальное состояние строки поиска <br>
34
+ * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
35
+ * <strong>placeholder</strong>: Placeholder строки поиска @default 'Search...'<br>
36
+ * <strong>loading</strong>: Состояние загрузки в строке поиска <br>
37
+ * <strong>onChange</strong>: Колбэк на изменение данных в строке поиска
38
+ * */
39
+ search: {
40
+ initialValue?: string;
41
+ state: string;
42
+ placeholder?: string;
43
+ loading?: boolean;
44
+ onChange(value: string): void;
45
+ };
46
+
47
+ /** Параметры отвечают за пагинацию в таблице <br>
48
+ * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу<br>
49
+ * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume' <br>
50
+ * */
51
+ pagination?: {
52
+ options?: number[];
53
+ optionsLabel?: string;
54
+ };
55
+ };
56
+
57
+ export function ServerTable<TData extends object>({
58
+ items,
59
+ total = DEFAULT_PAGINATION_LIMIT,
60
+ limit = DEFAULT_PAGINATION_LIMIT,
61
+ offset = 0,
62
+ onChangePage,
63
+ search,
64
+ pagination,
65
+ columnFilters,
66
+ ...rest
67
+ }: ServerTableProps<TData>) {
68
+ const [tempSearch, setTempSearch] = useState(search.initialValue || '');
69
+
70
+ const handleSearch = useCallback(
71
+ (newValue: string) => {
72
+ setTempSearch(newValue);
73
+ onSearchDebounced()(newValue.trim(), search.onChange);
74
+ },
75
+
76
+ [search.onChange],
77
+ );
78
+
79
+ const handlePageChange = useCallback(
80
+ ({ pageSize, pageIndex }: PaginationState) => onChangePage(pageIndex * pageSize, pageSize),
81
+ [onChangePage],
82
+ );
83
+
84
+ const pageIndex = useMemo(() => Math.floor(offset / limit), [limit, offset]);
85
+ const pageCount = useMemo(() => Math.ceil(total / limit), [limit, total]);
86
+
87
+ return (
88
+ <Table
89
+ {...rest}
90
+ data={items || []}
91
+ search={{
92
+ state: tempSearch,
93
+ onChange: handleSearch,
94
+ loading: search.loading,
95
+ placeholder: search.placeholder,
96
+ }}
97
+ columnFilters={columnFilters}
98
+ pageCount={pageCount}
99
+ pagination={{
100
+ ...pagination,
101
+ state: {
102
+ pageIndex,
103
+ pageSize: limit,
104
+ },
105
+ onChange: handlePageChange,
106
+ }}
107
+ pageSize={limit}
108
+ />
109
+ );
110
+ }
111
+
112
+ ServerTable.getRowActionsColumnDef = Table.getRowActionsColumnDef;
113
+ ServerTable.getStatusColumnDef = Table.getStatusColumnDef;
@@ -0,0 +1,2 @@
1
+ export const SEARCH_DELAY = 500;
2
+ export const DEFAULT_PAGINATION_LIMIT = 10;
@@ -0,0 +1 @@
1
+ export * from './ServerTable';
@@ -0,0 +1,9 @@
1
+ import debounce from 'lodash.debounce';
2
+
3
+ import { SEARCH_DELAY } from './constants';
4
+
5
+ export function onSearchDebounced() {
6
+ return debounce((newValue: string, onChange: (newValue: string) => void) => {
7
+ onChange(newValue);
8
+ }, SEARCH_DELAY);
9
+ }
@@ -21,6 +21,7 @@ import { Toolbar, ToolbarProps } from '@snack-uikit/toolbar';
21
21
  import { TruncateString } from '@snack-uikit/truncate-string';
22
22
  import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
23
23
 
24
+ import { DEFAULT_PAGE_SIZE } from '../../constants';
24
25
  import {
25
26
  BodyRow,
26
27
  ExportButton,
@@ -123,6 +124,8 @@ export type TableProps<TData extends object> = WithSupportProps<{
123
124
  /** Фильтры */
124
125
  columnFilters?: ReactNode;
125
126
 
127
+ dataFiltered?: boolean;
128
+
126
129
  /** Название файла при экспорте CSV/XLSX */
127
130
  exportFileName?: string;
128
131
 
@@ -152,6 +155,7 @@ export function Table<TData extends object>({
152
155
  search,
153
156
  sorting: sortingProp,
154
157
  columnFilters: columnFiltersProp,
158
+ dataFiltered,
155
159
 
156
160
  pagination: paginationProp,
157
161
 
@@ -161,7 +165,7 @@ export function Table<TData extends object>({
161
165
  onRefresh,
162
166
  onDelete,
163
167
 
164
- pageSize = 10,
168
+ pageSize = DEFAULT_PAGE_SIZE,
165
169
  pageCount,
166
170
  loading = false,
167
171
  outline = false,
@@ -252,6 +256,10 @@ export function Table<TData extends object>({
252
256
  minSize: 40,
253
257
  },
254
258
 
259
+ manualSorting: Boolean(pageCount),
260
+ manualPagination: pageCount !== undefined,
261
+ manualFiltering: Boolean(pageCount),
262
+
255
263
  globalFilterFn: fuzzyFilter,
256
264
  onGlobalFilterChange,
257
265
 
@@ -265,11 +273,12 @@ export function Table<TData extends object>({
265
273
  enableColumnResizing: true,
266
274
 
267
275
  enableSorting: true,
268
- manualSorting: false,
269
- enableMultiSort: false,
270
- manualPagination: pageCount !== undefined,
276
+
277
+ enableMultiSort: true,
278
+
271
279
  onSortingChange,
272
280
  getSortedRowModel: getSortedRowModel(),
281
+
273
282
  onPaginationChange,
274
283
  getPaginationRowModel: getPaginationRowModel(),
275
284
 
@@ -342,10 +351,18 @@ export function Table<TData extends object>({
342
351
  const [locales] = useLocale('Table');
343
352
  const emptyStates = useTableEmptyState({ noDataState, noResultsState });
344
353
 
354
+ const cssPageSize = useMemo(() => {
355
+ const tempPageSize = !suppressPagination ? tablePagination?.pageSize : pageSize;
356
+
357
+ return !tableRows.length ? Math.min(Math.max(tempPageSize, 5), DEFAULT_PAGE_SIZE) : tempPageSize;
358
+ }, [pageSize, suppressPagination, tablePagination?.pageSize, tableRows.length]);
359
+
345
360
  return (
346
361
  <>
347
362
  <div
348
- style={{ '--page-size': !suppressPagination ? tablePagination?.pageSize : pageSize }}
363
+ style={{
364
+ '--page-size': cssPageSize,
365
+ }}
349
366
  className={cn(styles.wrapper, className)}
350
367
  {...extractSupportProps(rest)}
351
368
  >
@@ -397,8 +414,12 @@ export function Table<TData extends object>({
397
414
  <BodyRow key={row.id} row={row} onRowClick={onRowClick} />
398
415
  ))}
399
416
 
400
- {!tableRows.length && globalFilter && <TableEmptyState {...emptyStates.noResultsState} />}
401
- {!tableRows.length && !globalFilter && <TableEmptyState {...emptyStates.noDataState} />}
417
+ {!tableRows.length && (globalFilter || dataFiltered) && (
418
+ <TableEmptyState {...emptyStates.noResultsState} />
419
+ )}
420
+ {!tableRows.length && !globalFilter && !dataFiltered && (
421
+ <TableEmptyState {...emptyStates.noDataState} />
422
+ )}
402
423
  </>
403
424
  )}
404
425
  </TableContext.Provider>
@@ -80,14 +80,12 @@ export function useTableEmptyState({
80
80
 
81
81
  return useMemo(() => {
82
82
  const noDataState: TableEmptyStateProps = noDataStateProp ?? {
83
- icon: CrossSVG,
84
- appearance: 'red',
83
+ icon: { icon: CrossSVG, appearance: 'red', decor: true },
85
84
  ...locales.noData,
86
85
  };
87
86
 
88
87
  const noResultsState: TableEmptyStateProps = noResultsStateProp ?? {
89
- icon: SearchSVG,
90
- appearance: 'neutral',
88
+ icon: { icon: SearchSVG, appearance: 'neutral', decor: true },
91
89
  ...locales.noResults,
92
90
  };
93
91
 
@@ -1,25 +1,23 @@
1
1
  import cn from 'classnames';
2
2
  import { ReactNode } from 'react';
3
3
 
4
- import { IconPredefined, IconPredefinedProps } from '@snack-uikit/icon-predefined';
5
- import { Typography } from '@snack-uikit/typography';
4
+ import { IconPredefinedProps } from '@snack-uikit/icon-predefined';
5
+ import { InfoBlock } from '@snack-uikit/info-block';
6
6
 
7
7
  import styles from './styles.module.scss';
8
8
 
9
9
  export type TableEmptyStateProps = {
10
- title: string;
11
- description?: ReactNode;
10
+ title?: string;
11
+ description: string;
12
+ icon?: Pick<IconPredefinedProps, 'icon' | 'decor' | 'appearance'>;
13
+ footer?: ReactNode;
12
14
  className?: string;
13
- } & Pick<IconPredefinedProps, 'icon' | 'appearance'>;
15
+ };
14
16
 
15
- export function TableEmptyState({ title, description, className, icon, appearance }: TableEmptyStateProps) {
17
+ export function TableEmptyState({ className, ...props }: TableEmptyStateProps) {
16
18
  return (
17
19
  <div className={cn(styles.tableEmptyStateWrapper, className)}>
18
- <IconPredefined icon={icon} size='l' appearance={appearance} />
19
- <div className={styles.textWrapper}>
20
- <Typography.SansTitleM>{title}</Typography.SansTitleM>
21
- {description && <Typography.SansBodyM>{description}</Typography.SansBodyM>}
22
- </div>
20
+ <InfoBlock {...props} size='m' align='vertical' />
23
21
  </div>
24
22
  );
25
23
  }
@@ -9,10 +9,3 @@
9
9
 
10
10
  height: calc((var(--page-size, 10) * $size-table-line-height) + $size-table-line-height);
11
11
  }
12
-
13
- .textWrapper {
14
- display: flex;
15
- flex-direction: column;
16
- align-items: center;
17
- justify-content: center;
18
- }
@@ -1 +1,2 @@
1
1
  export * from './Table';
2
+ export * from './ServerTable';
package/src/constants.ts CHANGED
@@ -29,3 +29,5 @@ export const SORT_FN = {
29
29
  DateTime: 'datetime',
30
30
  AlphaNumeric: 'alphanumeric',
31
31
  } as const;
32
+
33
+ export const DEFAULT_PAGE_SIZE = 10;
@@ -0,0 +1,17 @@
1
+ import { TruncateString } from '@snack-uikit/truncate-string';
2
+
3
+ import { CopyButton } from './components';
4
+ import styles from './styles.module.scss';
5
+
6
+ export type CopyCellProps = {
7
+ value?: string | number;
8
+ };
9
+
10
+ export function CopyCell({ value }: CopyCellProps) {
11
+ return (
12
+ <div className={styles.copyCell}>
13
+ <TruncateString text={String(value)} maxLines={1} />
14
+ <CopyButton valueToCopy={value} className={styles.copyButton} />
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,44 @@
1
+ import copyToClipboard from 'copy-to-clipboard';
2
+ import { MouseEventHandler, useEffect, useRef, useState } from 'react';
3
+
4
+ import { ButtonFunction } from '@snack-uikit/button';
5
+ import { CheckSVG, CopySVG } from '@snack-uikit/icons';
6
+
7
+ export type CopyButtonProps = {
8
+ valueToCopy?: string | number;
9
+ className?: string;
10
+ };
11
+
12
+ export function CopyButton({ valueToCopy, className }: CopyButtonProps) {
13
+ const [isChecked, setIsCheckedOpen] = useState(false);
14
+ const timerId = useRef<NodeJS.Timeout>();
15
+ const openChecked = () => setIsCheckedOpen(true);
16
+ const closeChecked = () => setIsCheckedOpen(false);
17
+
18
+ const handleClick: MouseEventHandler<HTMLButtonElement> = event => {
19
+ event.stopPropagation();
20
+ valueToCopy && copyToClipboard(String(valueToCopy), { format: 'text/plain' });
21
+ openChecked();
22
+ clearTimeout(timerId.current);
23
+ timerId.current = setTimeout(closeChecked, 1000);
24
+ };
25
+
26
+ useEffect(
27
+ () => () => {
28
+ closeChecked();
29
+ clearTimeout(timerId.current);
30
+ },
31
+ [],
32
+ );
33
+
34
+ return (
35
+ <ButtonFunction
36
+ onClick={handleClick}
37
+ data-test-id='button-copy-value'
38
+ type='button'
39
+ icon={isChecked ? <CheckSVG /> : <CopySVG />}
40
+ size='s'
41
+ className={className}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyButton';
@@ -0,0 +1 @@
1
+ export * from './CopyCell';
@@ -0,0 +1,21 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
2
+
3
+ .copyCell {
4
+ cursor: pointer;
5
+
6
+ display: flex;
7
+ gap: $dimension-050m;
8
+ align-items: center;
9
+
10
+ min-width: 0;
11
+ max-width: 100%;
12
+ .copyButton {
13
+ display: none;
14
+ }
15
+
16
+ &:hover {
17
+ .copyButton {
18
+ display: inline-flex;
19
+ }
20
+ }
21
+ }
@@ -22,7 +22,7 @@ export type RowActionProps<TData> = Pick<BaseItemProps, 'content' | 'disabled'>
22
22
  tagLabel?: string;
23
23
  id?: string;
24
24
  hidden?: boolean;
25
- onClick(row: RowActionInfo<TData>, e: MouseEvent<HTMLButtonElement>): void;
25
+ onClick(row: RowActionInfo<TData>, e: MouseEvent<HTMLElement>): void;
26
26
  };
27
27
 
28
28
  type RowActionsCellProps<TData> = {
@@ -34,7 +34,7 @@ function RowActionsCell<TData>({ row, actions }: RowActionsCellProps<TData>) {
34
34
  const { droplistOpened, setDroplistOpen } = useRowContext();
35
35
  const triggerRef = useRef(null);
36
36
 
37
- const handleItemClick = (item: RowActionProps<TData>) => (e: MouseEvent<HTMLButtonElement>) => {
37
+ const handleItemClick = (item: RowActionProps<TData>) => (e: MouseEvent<HTMLElement>) => {
38
38
  item.onClick({ rowId: row.id, itemId: item.id, data: row.original }, e);
39
39
  };
40
40
 
@@ -16,11 +16,13 @@ type StatusCellProps = {
16
16
  appearance: StatusAppearance;
17
17
  };
18
18
 
19
+ export type MapStatusToAppearanceFnType = (value: string | number) => StatusAppearance;
20
+
19
21
  type BaseStatusColumnDef = {
20
22
  /** Имя ключа соответствующее полю в data */
21
23
  accessorKey: string;
22
24
  /** Маппинг значений статуса на цвета */
23
- mapStatusToAppearance(value: string | number): StatusAppearance;
25
+ mapStatusToAppearance: MapStatusToAppearanceFnType;
24
26
  /** Включение/выключение сортировки */
25
27
  enableSorting?: boolean;
26
28
  };
@@ -3,3 +3,4 @@ export * from './RowActionsCell';
3
3
  export * from './HeaderCell';
4
4
  export * from './SelectionCell';
5
5
  export * from './StatusCell';
6
+ export * from './CopyCell';
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './components';
2
2
  export * from './types';
3
3
  export * from './exportTable';
4
+
5
+ export { type CopyCellProps, type ActionsGenerator, CopyCell } from './helperComponents';
package/src/types.ts CHANGED
@@ -71,6 +71,8 @@ export type {
71
71
  RowInfo,
72
72
  RowClickHandler,
73
73
  ActionsGenerator,
74
+ CopyCellProps,
75
+ MapStatusToAppearanceFnType,
74
76
  } from './helperComponents';
75
77
 
76
78
  export type {