@cloud-ru/uikit-product-mobile-table 0.14.0 → 0.16.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 (58) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/components/MobileTable.d.ts +2 -2
  3. package/dist/cjs/components/MobileTable.js +82 -6
  4. package/dist/cjs/components/index.d.ts +1 -0
  5. package/dist/cjs/components/index.js +1 -0
  6. package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +7 -0
  7. package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.js +18 -0
  8. package/dist/cjs/helperComponents/ColumnsSettings/index.d.ts +1 -0
  9. package/dist/cjs/helperComponents/ColumnsSettings/index.js +17 -0
  10. package/dist/cjs/helperComponents/ColumnsSettings/styles.module.css +3 -0
  11. package/dist/cjs/helperComponents/TableCard/TableCard.d.ts +3 -1
  12. package/dist/cjs/helperComponents/TableCard/TableCard.js +10 -3
  13. package/dist/cjs/helperComponents/TableSorting/TableSorting.d.ts +10 -0
  14. package/dist/cjs/helperComponents/TableSorting/TableSorting.js +29 -0
  15. package/dist/cjs/helperComponents/TableSorting/index.d.ts +1 -0
  16. package/dist/cjs/helperComponents/TableSorting/index.js +17 -0
  17. package/dist/cjs/helperComponents/TableSorting/styles.module.css +3 -0
  18. package/dist/cjs/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
  19. package/dist/cjs/helperComponents/TableSorting/useTableSorting.js +189 -0
  20. package/dist/cjs/helperComponents/TableSorting/utils.d.ts +9 -0
  21. package/dist/cjs/helperComponents/TableSorting/utils.js +75 -0
  22. package/dist/cjs/helperComponents/index.d.ts +3 -1
  23. package/dist/cjs/helperComponents/index.js +3 -1
  24. package/dist/esm/components/MobileTable.d.ts +2 -2
  25. package/dist/esm/components/MobileTable.js +84 -8
  26. package/dist/esm/components/index.d.ts +1 -0
  27. package/dist/esm/components/index.js +1 -0
  28. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +7 -0
  29. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.js +12 -0
  30. package/dist/esm/helperComponents/ColumnsSettings/index.d.ts +1 -0
  31. package/dist/esm/helperComponents/ColumnsSettings/index.js +1 -0
  32. package/dist/esm/helperComponents/ColumnsSettings/styles.module.css +3 -0
  33. package/dist/esm/helperComponents/TableCard/TableCard.d.ts +3 -1
  34. package/dist/esm/helperComponents/TableCard/TableCard.js +10 -3
  35. package/dist/esm/helperComponents/TableSorting/TableSorting.d.ts +10 -0
  36. package/dist/esm/helperComponents/TableSorting/TableSorting.js +26 -0
  37. package/dist/esm/helperComponents/TableSorting/index.d.ts +1 -0
  38. package/dist/esm/helperComponents/TableSorting/index.js +1 -0
  39. package/dist/esm/helperComponents/TableSorting/styles.module.css +3 -0
  40. package/dist/esm/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
  41. package/dist/esm/helperComponents/TableSorting/useTableSorting.js +183 -0
  42. package/dist/esm/helperComponents/TableSorting/utils.d.ts +9 -0
  43. package/dist/esm/helperComponents/TableSorting/utils.js +70 -0
  44. package/dist/esm/helperComponents/index.d.ts +3 -1
  45. package/dist/esm/helperComponents/index.js +3 -1
  46. package/package.json +6 -3
  47. package/src/components/MobileTable.tsx +144 -9
  48. package/src/components/index.ts +1 -0
  49. package/src/helperComponents/ColumnsSettings/ColumnsSettings.tsx +28 -0
  50. package/src/helperComponents/ColumnsSettings/index.ts +1 -0
  51. package/src/helperComponents/ColumnsSettings/styles.module.scss +5 -0
  52. package/src/helperComponents/TableCard/TableCard.tsx +21 -4
  53. package/src/helperComponents/TableSorting/TableSorting.tsx +60 -0
  54. package/src/helperComponents/TableSorting/index.ts +1 -0
  55. package/src/helperComponents/TableSorting/styles.module.scss +9 -0
  56. package/src/helperComponents/TableSorting/useTableSorting.tsx +248 -0
  57. package/src/helperComponents/TableSorting/utils.ts +89 -0
  58. package/src/helperComponents/index.ts +3 -1
@@ -0,0 +1,248 @@
1
+ import { Header, SortingState, Table } from '@tanstack/react-table';
2
+ import { ReactNode, useCallback, useMemo } from 'react';
3
+
4
+ import { ArrowDownSVG, ArrowUpSVG, UpdateSVG } from '@cloud-ru/uikit-product-icons';
5
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
6
+ import { MobileDroplistProps, SelectionSingleState } from '@cloud-ru/uikit-product-mobile-dropdown';
7
+ import { ColumnDefinition } from '@snack-uikit/table';
8
+
9
+ import styles from './styles.module.scss';
10
+ import { createColumnDefMap, getHeaderLabel, groupHeadersByPinned } from './utils';
11
+
12
+ type UseTableSortingReturn = {
13
+ items: MobileDroplistProps['items'];
14
+ pinBottom: MobileDroplistProps['items'];
15
+ selection?: SelectionSingleState;
16
+ currentSort: { id: string; label: string; desc: boolean } | null;
17
+ selectedSortId?: string;
18
+ handleClearSort(): void;
19
+ };
20
+
21
+ export function useTableSorting<TData extends object>({
22
+ table,
23
+ sorting: sortingProp,
24
+ columnDefinitions,
25
+ enabledColumns,
26
+ areColumnsSettingsEnabled = false,
27
+ }: {
28
+ table: Table<TData>;
29
+ sorting?: SortingState;
30
+ columnDefinitions: ColumnDefinition<TData>[];
31
+ enabledColumns?: string[];
32
+ areColumnsSettingsEnabled?: boolean;
33
+ }): UseTableSortingReturn {
34
+ const { t } = useLocale('Table');
35
+ const sorting = sortingProp ?? table.getState().sorting;
36
+
37
+ const columnDefMap = useMemo(() => createColumnDefMap(columnDefinitions), [columnDefinitions]);
38
+
39
+ const hiddenColumnsBySettings = useMemo(() => {
40
+ if (!areColumnsSettingsEnabled) return new Set<string>();
41
+
42
+ const hidden = new Set<string>();
43
+ columnDefMap.forEach((colDef, columnId) => {
44
+ const colDefWithSettings = colDef as ColumnDefinition<TData> & {
45
+ columnSettings?: { mode?: 'hidden' | string };
46
+ };
47
+ if (colDefWithSettings.columnSettings?.mode === 'hidden') {
48
+ hidden.add(columnId);
49
+ }
50
+ });
51
+ return hidden;
52
+ }, [areColumnsSettingsEnabled, columnDefMap]);
53
+
54
+ const sortableHeaders = useMemo(() => {
55
+ let headers = table
56
+ .getFlatHeaders()
57
+ .filter(header => header.column.getCanSort() && header.id !== 'select' && header.id !== 'actions');
58
+
59
+ if (areColumnsSettingsEnabled && enabledColumns) {
60
+ headers = headers.filter(header => hiddenColumnsBySettings.has(header.id) || enabledColumns.includes(header.id));
61
+ }
62
+
63
+ return headers;
64
+ }, [table, areColumnsSettingsEnabled, enabledColumns, hiddenColumnsBySettings]);
65
+
66
+ const currentSort = useMemo(() => {
67
+ if (sorting.length === 0) return null;
68
+ const firstSort = sorting[0];
69
+
70
+ if (areColumnsSettingsEnabled && enabledColumns) {
71
+ const isHiddenColumn = hiddenColumnsBySettings.has(firstSort.id);
72
+ const isEnabledColumn = enabledColumns.includes(firstSort.id);
73
+ if (!isHiddenColumn && !isEnabledColumn) {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ const header = table.getFlatHeaders().find(h => h.id === firstSort.id);
79
+ if (!header) return null;
80
+
81
+ const headerLabel = getHeaderLabel(header);
82
+
83
+ return {
84
+ id: firstSort.id,
85
+ label: headerLabel || firstSort.id,
86
+ desc: firstSort.desc,
87
+ };
88
+ }, [sorting, table, areColumnsSettingsEnabled, enabledColumns, hiddenColumnsBySettings]);
89
+
90
+ const handleColumnSortToggle = useCallback(
91
+ (columnId: string) => {
92
+ const currentSorting = table.getState().sorting;
93
+ const currentColumnSort = currentSorting.find(s => s.id === columnId);
94
+
95
+ let newSorting: SortingState;
96
+ if (!currentColumnSort) {
97
+ newSorting = [{ id: columnId, desc: false }];
98
+ } else if (!currentColumnSort.desc) {
99
+ newSorting = [{ id: columnId, desc: true }];
100
+ } else {
101
+ newSorting = [];
102
+ }
103
+
104
+ table.setSorting(newSorting);
105
+ },
106
+ [table],
107
+ );
108
+
109
+ const handleClearSort = useCallback(() => {
110
+ table.setSorting([]);
111
+ }, [table]);
112
+
113
+ const selectedSortId = useMemo(() => {
114
+ if (sorting.length === 0) return undefined;
115
+ const firstSort = sorting[0];
116
+
117
+ if (areColumnsSettingsEnabled && enabledColumns) {
118
+ const isHiddenColumn = hiddenColumnsBySettings.has(firstSort.id);
119
+ const isEnabledColumn = enabledColumns.includes(firstSort.id);
120
+ if (!isHiddenColumn && !isEnabledColumn) {
121
+ return undefined;
122
+ }
123
+ }
124
+
125
+ return `sort-${firstSort.id}`;
126
+ }, [sorting, areColumnsSettingsEnabled, enabledColumns, hiddenColumnsBySettings]);
127
+
128
+ const createSortItem = useCallback(
129
+ (header: Header<TData, unknown>) => {
130
+ const columnId = header.id;
131
+ const currentColumnSort = sorting.find(s => s.id === columnId);
132
+ const isAsc = currentColumnSort && !currentColumnSort.desc;
133
+ const isDesc = currentColumnSort && currentColumnSort.desc;
134
+
135
+ const headerLabel = getHeaderLabel(header) || columnId;
136
+
137
+ let SortIcon: ReactNode = undefined;
138
+ if (isAsc) {
139
+ SortIcon = <ArrowUpSVG />;
140
+ } else if (isDesc) {
141
+ SortIcon = <ArrowDownSVG />;
142
+ }
143
+
144
+ return {
145
+ id: `sort-${columnId}`,
146
+ content: {
147
+ option: headerLabel,
148
+ },
149
+ afterContent: SortIcon,
150
+ onClick: () => handleColumnSortToggle(columnId),
151
+ };
152
+ },
153
+ [sorting, handleColumnSortToggle],
154
+ );
155
+
156
+ const groupSortableHeadersByPinned = useCallback(
157
+ () => groupHeadersByPinned(sortableHeaders, columnDefMap),
158
+ [sortableHeaders, columnDefMap],
159
+ );
160
+
161
+ const { items, pinBottom } = useMemo(() => {
162
+ const { leftHeaders, unpinnedHeaders, rightHeaders } = groupSortableHeadersByPinned();
163
+
164
+ const groups: MobileDroplistProps['items'] = [];
165
+
166
+ if (leftHeaders.length > 0) {
167
+ groups.push({
168
+ type: 'group',
169
+ divider: false,
170
+ items: leftHeaders.map(createSortItem),
171
+ });
172
+ }
173
+
174
+ if (unpinnedHeaders.length > 0) {
175
+ groups.push({
176
+ type: 'group',
177
+ divider: leftHeaders.length > 0 || rightHeaders.length > 0,
178
+ items: unpinnedHeaders.map(createSortItem),
179
+ });
180
+ }
181
+
182
+ if (rightHeaders.length > 0) {
183
+ groups.push({
184
+ type: 'group',
185
+ divider: leftHeaders.length > 0 || unpinnedHeaders.length > 0,
186
+ items: rightHeaders.map(createSortItem),
187
+ });
188
+ }
189
+
190
+ const clearItem: MobileDroplistProps['items'] = [
191
+ {
192
+ id: 'snack-internal-clear-id',
193
+ content: {
194
+ option: t('clearSort'),
195
+ },
196
+ afterContent: <UpdateSVG />,
197
+ onClick: handleClearSort,
198
+ disabled: sorting.length === 0,
199
+ className: styles.clearSortItem,
200
+ },
201
+ ];
202
+
203
+ const mainGroup: MobileDroplistProps['items'][0] = {
204
+ type: 'group',
205
+ label: t('sort'),
206
+ items: groups,
207
+ mode: 'primary',
208
+ };
209
+
210
+ return {
211
+ items: [mainGroup],
212
+ pinBottom: clearItem,
213
+ };
214
+ }, [groupSortableHeadersByPinned, createSortItem, sorting, handleClearSort, t]);
215
+
216
+ const handleSelectionChange = useCallback(
217
+ (selectedId: string | number) => {
218
+ const id = String(selectedId);
219
+ if (id === 'sort-clear') {
220
+ return;
221
+ }
222
+ const match = id.match(/^sort-(.+)$/);
223
+ if (match) {
224
+ const [, columnId] = match;
225
+ handleColumnSortToggle(columnId);
226
+ }
227
+ },
228
+ [handleColumnSortToggle],
229
+ );
230
+
231
+ const selection = useMemo((): SelectionSingleState | undefined => {
232
+ if (!selectedSortId) return undefined;
233
+ return {
234
+ mode: 'single',
235
+ value: selectedSortId,
236
+ onChange: handleSelectionChange,
237
+ };
238
+ }, [selectedSortId, handleSelectionChange]);
239
+
240
+ return {
241
+ items,
242
+ pinBottom,
243
+ selection,
244
+ currentSort,
245
+ selectedSortId,
246
+ handleClearSort,
247
+ };
248
+ }
@@ -0,0 +1,89 @@
1
+ import { Header } from '@tanstack/react-table';
2
+
3
+ import { ColumnDefinition } from '@snack-uikit/table';
4
+
5
+ export function getHeaderLabel<TData extends object>(header: Header<TData, unknown>): string {
6
+ const headerDef = header.column.columnDef.header;
7
+
8
+ if (typeof headerDef === 'string') {
9
+ return headerDef;
10
+ }
11
+
12
+ if (typeof headerDef === 'function') {
13
+ try {
14
+ const context = header.getContext();
15
+ const result = headerDef(context);
16
+
17
+ if (typeof result === 'string' || typeof result === 'number') {
18
+ return String(result);
19
+ }
20
+
21
+ if (result != null && typeof result === 'object' && 'props' in result && result.props) {
22
+ const children = result.props.children;
23
+
24
+ if (typeof children === 'string' || typeof children === 'number') {
25
+ return String(children);
26
+ }
27
+
28
+ if (Array.isArray(children)) {
29
+ const textChild = children.find(child => typeof child === 'string' || typeof child === 'number');
30
+ if (textChild != null) {
31
+ return String(textChild);
32
+ }
33
+ }
34
+ }
35
+ } catch (error) {
36
+ console.error('Error getting header label:', error);
37
+ }
38
+ }
39
+
40
+ return header.id || header.column.id || '';
41
+ }
42
+
43
+ export function createColumnDefMap<TData extends object>(
44
+ columnDefinitions: ColumnDefinition<TData>[],
45
+ ): Map<string, ColumnDefinition<TData>> {
46
+ const map = new Map<string, ColumnDefinition<TData>>();
47
+ columnDefinitions.forEach(colDef => {
48
+ let id: string | undefined;
49
+ if ('id' in colDef && colDef.id) {
50
+ id = colDef.id;
51
+ } else if ('accessorKey' in colDef && colDef.accessorKey) {
52
+ id = String(colDef.accessorKey);
53
+ }
54
+ if (id) {
55
+ map.set(id, colDef);
56
+ }
57
+ });
58
+ return map;
59
+ }
60
+
61
+ export function groupHeadersByPinned<TData extends object>(
62
+ headers: Header<TData, unknown>[],
63
+ columnDefMap: Map<string, ColumnDefinition<TData>>,
64
+ ) {
65
+ const leftHeaders: Header<TData, unknown>[] = [];
66
+ const unpinnedHeaders: Header<TData, unknown>[] = [];
67
+ const rightHeaders: Header<TData, unknown>[] = [];
68
+
69
+ headers.forEach(header => {
70
+ const columnDef = columnDefMap.get(header.id);
71
+ if (!columnDef) {
72
+ unpinnedHeaders.push(header);
73
+ return;
74
+ }
75
+
76
+ switch (columnDef.pinned) {
77
+ case 'left':
78
+ leftHeaders.push(header);
79
+ break;
80
+ case 'right':
81
+ rightHeaders.push(header);
82
+ break;
83
+ default:
84
+ unpinnedHeaders.push(header);
85
+ }
86
+ });
87
+
88
+ return { leftHeaders, unpinnedHeaders, rightHeaders };
89
+ }
@@ -1,5 +1,7 @@
1
+ export * from './ColumnsSettings';
1
2
  export * from './RowActionsCell';
2
3
  export * from './StatusColumnDef';
3
4
  export * from './TableCard';
4
- export * from './TablePagination';
5
5
  export * from './TableEmptyState';
6
+ export * from './TablePagination';
7
+ export * from './TableSorting';