@cloud-ru/uikit-product-mobile-table 0.14.0 → 0.15.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 (53) hide show
  1. package/CHANGELOG.md +11 -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/TableSorting/TableSorting.d.ts +10 -0
  12. package/dist/cjs/helperComponents/TableSorting/TableSorting.js +29 -0
  13. package/dist/cjs/helperComponents/TableSorting/index.d.ts +1 -0
  14. package/dist/cjs/helperComponents/TableSorting/index.js +17 -0
  15. package/dist/cjs/helperComponents/TableSorting/styles.module.css +3 -0
  16. package/dist/cjs/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
  17. package/dist/cjs/helperComponents/TableSorting/useTableSorting.js +189 -0
  18. package/dist/cjs/helperComponents/TableSorting/utils.d.ts +9 -0
  19. package/dist/cjs/helperComponents/TableSorting/utils.js +75 -0
  20. package/dist/cjs/helperComponents/index.d.ts +3 -1
  21. package/dist/cjs/helperComponents/index.js +3 -1
  22. package/dist/esm/components/MobileTable.d.ts +2 -2
  23. package/dist/esm/components/MobileTable.js +84 -8
  24. package/dist/esm/components/index.d.ts +1 -0
  25. package/dist/esm/components/index.js +1 -0
  26. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +7 -0
  27. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.js +12 -0
  28. package/dist/esm/helperComponents/ColumnsSettings/index.d.ts +1 -0
  29. package/dist/esm/helperComponents/ColumnsSettings/index.js +1 -0
  30. package/dist/esm/helperComponents/ColumnsSettings/styles.module.css +3 -0
  31. package/dist/esm/helperComponents/TableSorting/TableSorting.d.ts +10 -0
  32. package/dist/esm/helperComponents/TableSorting/TableSorting.js +26 -0
  33. package/dist/esm/helperComponents/TableSorting/index.d.ts +1 -0
  34. package/dist/esm/helperComponents/TableSorting/index.js +1 -0
  35. package/dist/esm/helperComponents/TableSorting/styles.module.css +3 -0
  36. package/dist/esm/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
  37. package/dist/esm/helperComponents/TableSorting/useTableSorting.js +183 -0
  38. package/dist/esm/helperComponents/TableSorting/utils.d.ts +9 -0
  39. package/dist/esm/helperComponents/TableSorting/utils.js +70 -0
  40. package/dist/esm/helperComponents/index.d.ts +3 -1
  41. package/dist/esm/helperComponents/index.js +3 -1
  42. package/package.json +6 -3
  43. package/src/components/MobileTable.tsx +143 -9
  44. package/src/components/index.ts +1 -0
  45. package/src/helperComponents/ColumnsSettings/ColumnsSettings.tsx +28 -0
  46. package/src/helperComponents/ColumnsSettings/index.ts +1 -0
  47. package/src/helperComponents/ColumnsSettings/styles.module.scss +5 -0
  48. package/src/helperComponents/TableSorting/TableSorting.tsx +60 -0
  49. package/src/helperComponents/TableSorting/index.ts +1 -0
  50. package/src/helperComponents/TableSorting/styles.module.scss +9 -0
  51. package/src/helperComponents/TableSorting/useTableSorting.tsx +248 -0
  52. package/src/helperComponents/TableSorting/utils.ts +89 -0
  53. package/src/helperComponents/index.ts +3 -1
@@ -9,14 +9,21 @@ import {
9
9
  useReactTable,
10
10
  } from '@tanstack/react-table';
11
11
  import cn from 'classnames';
12
- import { useCallback, useMemo } from 'react';
12
+ import { useCallback, useEffect, useMemo } from 'react';
13
13
 
14
14
  import { useLocale } from '@cloud-ru/uikit-product-locale';
15
15
  import { FiltersState, MobileChipChoiceRowProps } from '@cloud-ru/uikit-product-mobile-chips';
16
16
  import { MobileToolbar, MobileToolbarProps } from '@cloud-ru/uikit-product-mobile-toolbar';
17
17
  import { extractSupportProps, WithSupportProps } from '@cloud-ru/uikit-product-utils';
18
18
  import { SkeletonContextProvider } from '@snack-uikit/skeleton';
19
- import { PaginationState, TableProps } from '@snack-uikit/table';
19
+ import {
20
+ getPinnedGroups,
21
+ PaginationState,
22
+ TableProps,
23
+ useColumnOrderByDrag,
24
+ useColumnSettings,
25
+ usePageReset,
26
+ } from '@snack-uikit/table';
20
27
 
21
28
  import {
22
29
  getRowActionsColumnDef,
@@ -24,8 +31,10 @@ import {
24
31
  TableCard,
25
32
  TableEmptyState,
26
33
  TablePagination,
34
+ TableSorting,
27
35
  useEmptyState,
28
36
  } from '../helperComponents';
37
+ import { ColumnsSettings } from '../helperComponents/ColumnsSettings';
29
38
  import { DEFAULT_PAGE_SIZE } from './constants';
30
39
  import { useLoadingTable, useStateControl } from './hooks';
31
40
  import styles from './styles.module.scss';
@@ -40,7 +49,6 @@ export type MobileTableProps<TData extends object, TFilters extends FiltersState
40
49
  | 'suppressSearch'
41
50
  | 'search'
42
51
  | 'onRefresh'
43
- | 'toolbarAfter'
44
52
  | 'moreActions'
45
53
  | 'className'
46
54
  | 'enableFuzzySearch'
@@ -59,6 +67,10 @@ export type MobileTableProps<TData extends object, TFilters extends FiltersState
59
67
  | 'getRowId'
60
68
  | 'rowSelection'
61
69
  | 'bulkActions'
70
+ | 'columnsSettings'
71
+ | 'savedState'
72
+ | 'autoResetPageIndex'
73
+ | 'toolbarAfter'
62
74
  > &
63
75
  WithSupportProps<{
64
76
  headlineId?: string;
@@ -93,9 +105,12 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
93
105
  manualSorting = false,
94
106
  manualPagination = false,
95
107
  manualFiltering = false,
108
+ autoResetPageIndex = false,
96
109
  getRowId,
97
110
  rowSelection: rowSelectionProp,
98
111
  bulkActions: bulkActionsProp,
112
+ columnsSettings: columnsSettingsProp,
113
+ savedState,
99
114
  ...rest
100
115
  }: MobileTableProps<TData, TFilters>) {
101
116
  const defaultPaginationState = useMemo(() => ({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE }), []);
@@ -126,18 +141,96 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
126
141
  [rowSelectionProp],
127
142
  );
128
143
 
144
+ const enableSelection = Boolean(rowSelectionProp?.enable);
145
+
146
+ const pinnedGroups = useMemo(() => getPinnedGroups(columnDefinitions), [columnDefinitions]);
147
+
148
+ const {
149
+ enabledColumns,
150
+ setEnabledColumns,
151
+ getColumnsSettings,
152
+ enabledTableColumns,
153
+ enabledColumnsDefinitions,
154
+ areColumnsSettingsEnabled,
155
+ } = useColumnSettings({
156
+ columnDefinitions,
157
+ pinnedGroups,
158
+ savedState,
159
+ columnsSettings: columnsSettingsProp,
160
+ rowSelection: undefined,
161
+ enableSelectPinned: false,
162
+ expanding: undefined,
163
+ });
164
+
165
+ const { columnOrder, setColumnOrder, enableColumnsOrderSortByDrag } = useColumnOrderByDrag<TData>({
166
+ tableColumns: columnDefinitions,
167
+ savedState,
168
+ columnSettings: columnsSettingsProp,
169
+ });
170
+
171
+ const columnsSettings = useMemo(() => getColumnsSettings(columnOrder), [columnOrder, getColumnsSettings]);
172
+
173
+ // Получаем список колонок с mode: 'hidden', которые всегда доступны для сортировки
174
+ const hiddenColumnsBySettings = useMemo(() => {
175
+ if (!areColumnsSettingsEnabled) return new Set<string>();
176
+
177
+ const hidden = new Set<string>();
178
+ columnDefinitions.forEach(colDef => {
179
+ let columnId: string | undefined;
180
+ if ('id' in colDef && colDef.id) {
181
+ columnId = colDef.id;
182
+ } else if ('accessorKey' in colDef && colDef.accessorKey) {
183
+ columnId = String(colDef.accessorKey);
184
+ }
185
+
186
+ if (columnId) {
187
+ const colDefWithSettings = colDef as typeof colDef & {
188
+ columnSettings?: { mode?: 'hidden' | string };
189
+ };
190
+ if (colDefWithSettings.columnSettings?.mode === 'hidden') {
191
+ hidden.add(columnId);
192
+ }
193
+ }
194
+ });
195
+ return hidden;
196
+ }, [areColumnsSettingsEnabled, columnDefinitions]);
197
+
198
+ // Сбрасываем сортировку, если колонка с активной сортировкой была скрыта
199
+ // Колонки с mode: 'hidden' всегда доступны для сортировки
200
+ useEffect(() => {
201
+ if (areColumnsSettingsEnabled && enabledColumns && sorting.length > 0) {
202
+ const activeSortColumnId = sorting[0]?.id;
203
+ if (activeSortColumnId) {
204
+ const isHiddenColumn = hiddenColumnsBySettings.has(activeSortColumnId);
205
+ const isEnabledColumn = enabledColumns.includes(activeSortColumnId);
206
+ // Сбрасываем сортировку только если колонка не скрыта через mode: 'hidden' и не включена
207
+ if (!isHiddenColumn && !isEnabledColumn) {
208
+ // Колонка с активной сортировкой скрыта - сбрасываем сортировку
209
+ onSortingChange([]);
210
+ }
211
+ }
212
+ }
213
+ }, [areColumnsSettingsEnabled, enabledColumns, sorting, onSortingChange, hiddenColumnsBySettings]);
214
+
129
215
  const table = useReactTable<TData>({
130
216
  data,
131
- columns: columnDefinitions,
217
+ columns: enabledTableColumns,
132
218
  getCoreRowModel: getCoreRowModel(),
133
219
  getPaginationRowModel: getPaginationRowModel(),
134
220
  getFilteredRowModel: getFilteredRowModel(),
135
221
  getSortedRowModel: getSortedRowModel(),
136
222
 
137
- state: { pagination, globalFilter, sorting, rowSelection },
223
+ state: {
224
+ pagination,
225
+ globalFilter,
226
+ sorting,
227
+ rowSelection,
228
+ columnOrder: enableColumnsOrderSortByDrag ? columnOrder : undefined,
229
+ },
138
230
  pageCount,
139
231
  onPaginationChange,
140
232
  onSortingChange,
233
+ onColumnOrderChange: enableColumnsOrderSortByDrag ? setColumnOrder : undefined,
141
234
  globalFilterFn: enableFuzzySearch ? fuzzyFilter : 'includesString',
142
235
 
143
236
  enableFilters: true,
@@ -146,6 +239,8 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
146
239
  manualFiltering,
147
240
  getRowId,
148
241
 
242
+ autoResetPageIndex,
243
+
149
244
  onRowSelectionChange,
150
245
  enableGrouping: true,
151
246
  enableRowSelection,
@@ -155,7 +250,7 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
155
250
 
156
251
  const { loadingTable } = useLoadingTable<TData, TFilters>({
157
252
  pageSize: DEFAULT_PAGE_SIZE,
158
- columnDefinitions,
253
+ columnDefinitions: enabledColumnsDefinitions,
159
254
  });
160
255
 
161
256
  const tableRows = table.getRowModel().rows;
@@ -169,8 +264,6 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
169
264
  onRefresh?.();
170
265
  }, [onRefresh, table]);
171
266
 
172
- const enableSelection = Boolean(rowSelectionProp?.enable);
173
-
174
267
  const bulkActions: MobileToolbarProps<TFilters>['bulkActions'] = useMemo(
175
268
  () =>
176
269
  enableSelection
@@ -206,6 +299,26 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
206
299
  ? 'multiple'
207
300
  : 'single';
208
301
 
302
+ const hasSortableColumns = useMemo(
303
+ () => columnDefinitions.some(column => column.enableSorting !== false),
304
+ [columnDefinitions],
305
+ );
306
+
307
+ const shouldShowSorting = useMemo(
308
+ () => Boolean(sortingProp) || hasSortableColumns,
309
+ [sortingProp, hasSortableColumns],
310
+ );
311
+
312
+ const tableFilteredRows = table.getFilteredRowModel().rows;
313
+
314
+ usePageReset({
315
+ manualPagination,
316
+ maximumAvailablePage: pageCount || tableFilteredRows.length / pagination.pageSize,
317
+ pagination,
318
+ onPaginationChange,
319
+ autoResetPageIndex,
320
+ });
321
+
209
322
  return (
210
323
  <div className={cn(styles.tableWrapper, className)} {...extractSupportProps(rest)}>
211
324
  {(!suppressToolbar || columnFilters) && (
@@ -224,7 +337,28 @@ export function MobileTable<TData extends object, TFilters extends FiltersState
224
337
  onRefresh={onRefresh ? handleOnRefresh : undefined}
225
338
  outline
226
339
  filterRow={columnFilters}
227
- after={toolbarAfter}
340
+ after={
341
+ toolbarAfter || shouldShowSorting || (areColumnsSettingsEnabled && columnsSettings) ? (
342
+ <>
343
+ {toolbarAfter}
344
+ {shouldShowSorting && (
345
+ <TableSorting
346
+ table={table}
347
+ columnDefinitions={columnDefinitions}
348
+ enabledColumns={areColumnsSettingsEnabled ? enabledColumns : undefined}
349
+ areColumnsSettingsEnabled={areColumnsSettingsEnabled}
350
+ />
351
+ )}
352
+ {areColumnsSettingsEnabled && columnsSettings && (
353
+ <ColumnsSettings
354
+ columnsSettings={columnsSettings}
355
+ enabledColumns={enabledColumns}
356
+ setEnabledColumns={setEnabledColumns}
357
+ />
358
+ )}
359
+ </>
360
+ ) : undefined
361
+ }
228
362
  moreActions={moreActions}
229
363
  bulkActions={bulkActions}
230
364
  selectedCount={table.getSelectedRowModel().rows.length}
@@ -1,3 +1,4 @@
1
1
  export * from './AdaptiveTable';
2
2
  export * from './AdaptiveServerTable';
3
3
  export * from './MobileTable';
4
+ export * from '../helperComponents';
@@ -0,0 +1,28 @@
1
+ import { MobileDroplist } from '@cloud-ru/uikit-product-mobile-dropdown';
2
+ import { ButtonFunction } from '@snack-uikit/button';
3
+ import { FunctionSettingsSVG } from '@snack-uikit/icons';
4
+ import { GroupSelectItemProps } from '@snack-uikit/list';
5
+
6
+ import styles from './styles.module.scss';
7
+ export type ColumnsSettingsProps = {
8
+ enabledColumns: string[];
9
+ setEnabledColumns(enabledColumns: string[]): void;
10
+ columnsSettings: [GroupSelectItemProps];
11
+ };
12
+
13
+ export function ColumnsSettings({ columnsSettings, enabledColumns, setEnabledColumns }: ColumnsSettingsProps) {
14
+ return (
15
+ <MobileDroplist
16
+ className={styles.columnsSettings}
17
+ items={columnsSettings}
18
+ selection={{
19
+ value: enabledColumns,
20
+ onChange: setEnabledColumns,
21
+ mode: 'multiple',
22
+ }}
23
+ data-test-id='table__column-settings-droplist'
24
+ >
25
+ <ButtonFunction size='m' data-test-id='table__column-settings' icon={<FunctionSettingsSVG />} />
26
+ </MobileDroplist>
27
+ );
28
+ }
@@ -0,0 +1 @@
1
+ export * from './ColumnsSettings';
@@ -0,0 +1,5 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table' as table;
2
+
3
+ .columnsSettings {
4
+ min-width: 256px;
5
+ }
@@ -0,0 +1,60 @@
1
+ import { SortingState, Table } from '@tanstack/react-table';
2
+ import { useState } from 'react';
3
+
4
+ import { ArrowDownSVG, ArrowUpSVG, SortSVG } from '@cloud-ru/uikit-product-icons';
5
+ import { MobileDroplist } from '@cloud-ru/uikit-product-mobile-dropdown';
6
+ import { ButtonFunction } from '@snack-uikit/button';
7
+ import { ColumnDefinition } from '@snack-uikit/table';
8
+
9
+ import { useTableSorting } from './useTableSorting';
10
+
11
+ export type TableSortingProps<TData extends object> = {
12
+ table: Table<TData>;
13
+ sorting?: SortingState;
14
+ columnDefinitions: ColumnDefinition<TData>[];
15
+ enabledColumns?: string[];
16
+ areColumnsSettingsEnabled?: boolean;
17
+ };
18
+
19
+ export function TableSorting<TData extends object>({
20
+ table,
21
+ sorting,
22
+ columnDefinitions,
23
+ enabledColumns,
24
+ areColumnsSettingsEnabled = false,
25
+ }: TableSortingProps<TData>) {
26
+ const [open, setOpen] = useState(false);
27
+
28
+ const { items, pinBottom, selection, currentSort, selectedSortId, handleClearSort } = useTableSorting({
29
+ table,
30
+ sorting,
31
+ columnDefinitions,
32
+ enabledColumns,
33
+ areColumnsSettingsEnabled,
34
+ });
35
+
36
+ const handleClear = () => {
37
+ handleClearSort();
38
+ setOpen(false);
39
+ };
40
+
41
+ const clearItem = pinBottom?.[0] ? [{ ...pinBottom[0], onClick: handleClear }] : undefined;
42
+
43
+ let SortIcon = SortSVG;
44
+ if (currentSort) {
45
+ SortIcon = currentSort.desc ? ArrowDownSVG : ArrowUpSVG;
46
+ }
47
+
48
+ return (
49
+ <MobileDroplist
50
+ items={items}
51
+ selection={selection}
52
+ virtualized={items.length > 10}
53
+ pinBottom={clearItem}
54
+ open={open}
55
+ onOpenChange={setOpen}
56
+ >
57
+ <ButtonFunction size='m' icon={<SortIcon />} appearance={selectedSortId ? 'primary' : 'neutral'} />
58
+ </MobileDroplist>
59
+ );
60
+ }
@@ -0,0 +1 @@
1
+ export * from './TableSorting';
@@ -0,0 +1,9 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .clearSortItem {
4
+ &[data-disabled='true'] {
5
+ svg {
6
+ color: stv.$sys-neutral-text-disabled;
7
+ }
8
+ }
9
+ }
@@ -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
+ }