@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.
- package/CHANGELOG.md +11 -0
- package/dist/cjs/components/MobileTable.d.ts +2 -2
- package/dist/cjs/components/MobileTable.js +82 -6
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +1 -0
- package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +7 -0
- package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.js +18 -0
- package/dist/cjs/helperComponents/ColumnsSettings/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ColumnsSettings/index.js +17 -0
- package/dist/cjs/helperComponents/ColumnsSettings/styles.module.css +3 -0
- package/dist/cjs/helperComponents/TableSorting/TableSorting.d.ts +10 -0
- package/dist/cjs/helperComponents/TableSorting/TableSorting.js +29 -0
- package/dist/cjs/helperComponents/TableSorting/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TableSorting/index.js +17 -0
- package/dist/cjs/helperComponents/TableSorting/styles.module.css +3 -0
- package/dist/cjs/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
- package/dist/cjs/helperComponents/TableSorting/useTableSorting.js +189 -0
- package/dist/cjs/helperComponents/TableSorting/utils.d.ts +9 -0
- package/dist/cjs/helperComponents/TableSorting/utils.js +75 -0
- package/dist/cjs/helperComponents/index.d.ts +3 -1
- package/dist/cjs/helperComponents/index.js +3 -1
- package/dist/esm/components/MobileTable.d.ts +2 -2
- package/dist/esm/components/MobileTable.js +84 -8
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +7 -0
- package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.js +12 -0
- package/dist/esm/helperComponents/ColumnsSettings/index.d.ts +1 -0
- package/dist/esm/helperComponents/ColumnsSettings/index.js +1 -0
- package/dist/esm/helperComponents/ColumnsSettings/styles.module.css +3 -0
- package/dist/esm/helperComponents/TableSorting/TableSorting.d.ts +10 -0
- package/dist/esm/helperComponents/TableSorting/TableSorting.js +26 -0
- package/dist/esm/helperComponents/TableSorting/index.d.ts +1 -0
- package/dist/esm/helperComponents/TableSorting/index.js +1 -0
- package/dist/esm/helperComponents/TableSorting/styles.module.css +3 -0
- package/dist/esm/helperComponents/TableSorting/useTableSorting.d.ts +23 -0
- package/dist/esm/helperComponents/TableSorting/useTableSorting.js +183 -0
- package/dist/esm/helperComponents/TableSorting/utils.d.ts +9 -0
- package/dist/esm/helperComponents/TableSorting/utils.js +70 -0
- package/dist/esm/helperComponents/index.d.ts +3 -1
- package/dist/esm/helperComponents/index.js +3 -1
- package/package.json +6 -3
- package/src/components/MobileTable.tsx +143 -9
- package/src/components/index.ts +1 -0
- package/src/helperComponents/ColumnsSettings/ColumnsSettings.tsx +28 -0
- package/src/helperComponents/ColumnsSettings/index.ts +1 -0
- package/src/helperComponents/ColumnsSettings/styles.module.scss +5 -0
- package/src/helperComponents/TableSorting/TableSorting.tsx +60 -0
- package/src/helperComponents/TableSorting/index.ts +1 -0
- package/src/helperComponents/TableSorting/styles.module.scss +9 -0
- package/src/helperComponents/TableSorting/useTableSorting.tsx +248 -0
- package/src/helperComponents/TableSorting/utils.ts +89 -0
- 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 {
|
|
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:
|
|
217
|
+
columns: enabledTableColumns,
|
|
132
218
|
getCoreRowModel: getCoreRowModel(),
|
|
133
219
|
getPaginationRowModel: getPaginationRowModel(),
|
|
134
220
|
getFilteredRowModel: getFilteredRowModel(),
|
|
135
221
|
getSortedRowModel: getSortedRowModel(),
|
|
136
222
|
|
|
137
|
-
state: {
|
|
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={
|
|
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}
|
package/src/components/index.ts
CHANGED
|
@@ -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,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,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
|
+
}
|