@coveord/plasma-mantine 56.8.1 → 56.9.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/.turbo/turbo-build.log +4 -4
- package/.turbo/turbo-test.log +107 -106
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/components/Table/Table.d.ts +2 -12
- package/dist/cjs/components/Table/Table.d.ts.map +1 -1
- package/dist/cjs/components/Table/Table.js +0 -3
- package/dist/cjs/components/Table/Table.js.map +1 -1
- package/dist/cjs/components/Table/table-column/TableActionsColumn.d.ts +15 -0
- package/dist/cjs/components/Table/table-column/TableActionsColumn.d.ts.map +1 -1
- package/dist/cjs/components/Table/table-column/TableActionsColumn.js +14 -1
- package/dist/cjs/components/Table/table-column/TableActionsColumn.js.map +1 -1
- package/dist/cjs/components/Table/table-columns-selector/TableColumnsSelector.d.ts +11 -32
- package/dist/cjs/components/Table/table-columns-selector/TableColumnsSelector.d.ts.map +1 -1
- package/dist/cjs/components/Table/table-columns-selector/TableColumnsSelector.js +101 -97
- package/dist/cjs/components/Table/table-columns-selector/TableColumnsSelector.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/Table/Table.d.ts +2 -12
- package/dist/esm/components/Table/Table.d.ts.map +1 -1
- package/dist/esm/components/Table/Table.js +0 -3
- package/dist/esm/components/Table/Table.js.map +1 -1
- package/dist/esm/components/Table/table-column/TableActionsColumn.d.ts +15 -0
- package/dist/esm/components/Table/table-column/TableActionsColumn.d.ts.map +1 -1
- package/dist/esm/components/Table/table-column/TableActionsColumn.js +12 -1
- package/dist/esm/components/Table/table-column/TableActionsColumn.js.map +1 -1
- package/dist/esm/components/Table/table-columns-selector/TableColumnsSelector.d.ts +11 -32
- package/dist/esm/components/Table/table-columns-selector/TableColumnsSelector.d.ts.map +1 -1
- package/dist/esm/components/Table/table-columns-selector/TableColumnsSelector.js +94 -84
- package/dist/esm/components/Table/table-columns-selector/TableColumnsSelector.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/Table/Table.tsx +4 -9
- package/src/components/Table/__tests__/TableColumnsSelectorHeader.spec.tsx +325 -0
- package/src/components/Table/table-column/TableActionsColumn.tsx +28 -1
- package/src/components/Table/table-columns-selector/TableColumnsSelector.tsx +96 -125
- package/src/index.ts +1 -0
- package/src/components/Table/__tests__/TableColumnsSelector.spec.tsx +0 -352
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
|
|
2
|
+
import {render, screen, userEvent, waitFor} from '@test-utils';
|
|
3
|
+
|
|
4
|
+
import {Table} from '../Table.js';
|
|
5
|
+
import {TableActionsColumn} from '../table-column/TableActionsColumn.js';
|
|
6
|
+
import {useTable} from '../use-table.js';
|
|
7
|
+
|
|
8
|
+
type RowData = {name: string; age: number; email: string; phone: string};
|
|
9
|
+
const columnHelper = createColumnHelper<RowData>();
|
|
10
|
+
|
|
11
|
+
const mockData: RowData[] = [
|
|
12
|
+
{name: 'John', age: 30, email: 'john@test.com', phone: '123-456'},
|
|
13
|
+
{name: 'Jane', age: 25, email: 'jane@test.com', phone: '789-012'},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const getBaseColumns = (): Array<ColumnDef<RowData>> => [
|
|
17
|
+
columnHelper.accessor('name', {header: 'Name', enableSorting: false}),
|
|
18
|
+
columnHelper.accessor('age', {header: 'Age', enableSorting: false}),
|
|
19
|
+
columnHelper.accessor('email', {header: 'Email', enableSorting: false}),
|
|
20
|
+
columnHelper.accessor('phone', {header: 'Phone', enableSorting: false}),
|
|
21
|
+
TableActionsColumn as ColumnDef<RowData>,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
describe('TableColumnsSelectorHeader', () => {
|
|
25
|
+
it('renders the column selector button in the actions column header when rowConfigurable is true', () => {
|
|
26
|
+
const Fixture = () => {
|
|
27
|
+
const store = useTable<RowData>();
|
|
28
|
+
return (
|
|
29
|
+
<Table
|
|
30
|
+
store={store}
|
|
31
|
+
data={mockData}
|
|
32
|
+
columns={getBaseColumns()}
|
|
33
|
+
options={{meta: {rowConfigurable: true}}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
render(<Fixture />);
|
|
38
|
+
|
|
39
|
+
expect(screen.getByRole('button', {name: 'settings'})).toBeVisible();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders all columns in the dropdown except control columns', async () => {
|
|
43
|
+
const user = userEvent.setup();
|
|
44
|
+
const Fixture = () => {
|
|
45
|
+
const store = useTable<RowData>();
|
|
46
|
+
return (
|
|
47
|
+
<Table
|
|
48
|
+
store={store}
|
|
49
|
+
data={mockData}
|
|
50
|
+
columns={getBaseColumns()}
|
|
51
|
+
options={{meta: {rowConfigurable: true}}}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
render(<Fixture />);
|
|
56
|
+
|
|
57
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
58
|
+
|
|
59
|
+
const columnsCheckboxes = await screen.findAllByRole('checkbox');
|
|
60
|
+
expect(columnsCheckboxes).toHaveLength(4);
|
|
61
|
+
expect(columnsCheckboxes[0]).toHaveAccessibleName('Name');
|
|
62
|
+
expect(columnsCheckboxes[1]).toHaveAccessibleName('Age');
|
|
63
|
+
expect(columnsCheckboxes[2]).toHaveAccessibleName('Email');
|
|
64
|
+
expect(columnsCheckboxes[3]).toHaveAccessibleName('Phone');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders all checkboxes checked by default', async () => {
|
|
68
|
+
const user = userEvent.setup();
|
|
69
|
+
const Fixture = () => {
|
|
70
|
+
const store = useTable<RowData>();
|
|
71
|
+
return (
|
|
72
|
+
<Table
|
|
73
|
+
store={store}
|
|
74
|
+
data={mockData}
|
|
75
|
+
columns={getBaseColumns()}
|
|
76
|
+
options={{meta: {rowConfigurable: true}}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
render(<Fixture />);
|
|
81
|
+
|
|
82
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
83
|
+
|
|
84
|
+
expect(await screen.findByRole('checkbox', {name: 'Name'})).toBeChecked();
|
|
85
|
+
expect(screen.getByRole('checkbox', {name: 'Age'})).toBeChecked();
|
|
86
|
+
expect(screen.getByRole('checkbox', {name: 'Email'})).toBeChecked();
|
|
87
|
+
expect(screen.getByRole('checkbox', {name: 'Phone'})).toBeChecked();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('hides a column when the user unchecks it', async () => {
|
|
91
|
+
const user = userEvent.setup();
|
|
92
|
+
const Fixture = () => {
|
|
93
|
+
const store = useTable<RowData>();
|
|
94
|
+
return (
|
|
95
|
+
<Table
|
|
96
|
+
store={store}
|
|
97
|
+
data={mockData}
|
|
98
|
+
columns={getBaseColumns()}
|
|
99
|
+
options={{meta: {rowConfigurable: true}}}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
render(<Fixture />);
|
|
104
|
+
|
|
105
|
+
expect(screen.getByRole('columnheader', {name: 'Email'})).toBeVisible();
|
|
106
|
+
|
|
107
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
108
|
+
await user.click(await screen.findByRole('checkbox', {name: 'Email'}));
|
|
109
|
+
|
|
110
|
+
expect(screen.queryByRole('columnheader', {name: 'Email'})).not.toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('shows a column when the user checks it', async () => {
|
|
114
|
+
const user = userEvent.setup();
|
|
115
|
+
const Fixture = () => {
|
|
116
|
+
const store = useTable<RowData>({
|
|
117
|
+
initialState: {columnVisibility: {email: false}},
|
|
118
|
+
});
|
|
119
|
+
return (
|
|
120
|
+
<Table
|
|
121
|
+
store={store}
|
|
122
|
+
data={mockData}
|
|
123
|
+
columns={getBaseColumns()}
|
|
124
|
+
options={{meta: {rowConfigurable: true}}}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
render(<Fixture />);
|
|
129
|
+
|
|
130
|
+
expect(screen.queryByRole('columnheader', {name: 'Email'})).not.toBeInTheDocument();
|
|
131
|
+
|
|
132
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
133
|
+
await user.click(await screen.findByRole('checkbox', {name: 'Email'}));
|
|
134
|
+
|
|
135
|
+
expect(screen.getByRole('columnheader', {name: 'Email'})).toBeVisible();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('renders a disabled checked checkbox for columns that are always visible', async () => {
|
|
139
|
+
const user = userEvent.setup();
|
|
140
|
+
const columnsWithNonHideable: Array<ColumnDef<RowData>> = [
|
|
141
|
+
columnHelper.accessor('name', {header: 'Name', enableSorting: false}),
|
|
142
|
+
columnHelper.accessor('age', {header: 'Age', enableSorting: false, enableHiding: false}),
|
|
143
|
+
columnHelper.accessor('email', {header: 'Email', enableSorting: false}),
|
|
144
|
+
TableActionsColumn as ColumnDef<RowData>,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const Fixture = () => {
|
|
148
|
+
const store = useTable<RowData>();
|
|
149
|
+
return (
|
|
150
|
+
<Table
|
|
151
|
+
store={store}
|
|
152
|
+
data={mockData}
|
|
153
|
+
columns={columnsWithNonHideable}
|
|
154
|
+
options={{meta: {rowConfigurable: true}}}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
render(<Fixture />);
|
|
159
|
+
|
|
160
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
161
|
+
|
|
162
|
+
const ageColumn = await screen.findByRole('checkbox', {name: 'Age'});
|
|
163
|
+
expect(ageColumn).toBeChecked();
|
|
164
|
+
expect(ageColumn).toBeDisabled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('renders a tooltip when hovering a disabled always visible column checkbox', async () => {
|
|
168
|
+
const user = userEvent.setup();
|
|
169
|
+
const columnsWithNonHideable: Array<ColumnDef<RowData>> = [
|
|
170
|
+
columnHelper.accessor('name', {header: 'Name', enableSorting: false}),
|
|
171
|
+
columnHelper.accessor('age', {header: 'Age', enableSorting: false, enableHiding: false}),
|
|
172
|
+
TableActionsColumn as ColumnDef<RowData>,
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const Fixture = () => {
|
|
176
|
+
const store = useTable<RowData>();
|
|
177
|
+
return (
|
|
178
|
+
<Table
|
|
179
|
+
store={store}
|
|
180
|
+
data={mockData}
|
|
181
|
+
columns={columnsWithNonHideable}
|
|
182
|
+
options={{meta: {rowConfigurable: true}}}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
render(<Fixture />);
|
|
187
|
+
|
|
188
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
189
|
+
const ageCheckbox = await screen.findByRole('checkbox', {name: 'Age'});
|
|
190
|
+
await user.hover(ageCheckbox.closest('div')!);
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
expect(screen.getByRole('tooltip', {name: 'This column is always visible.'})).toBeVisible();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('renders unchecked checkboxes for columns that are not visible in the initial state', async () => {
|
|
198
|
+
const user = userEvent.setup();
|
|
199
|
+
const Fixture = () => {
|
|
200
|
+
const store = useTable<RowData>({
|
|
201
|
+
initialState: {columnVisibility: {email: false}},
|
|
202
|
+
});
|
|
203
|
+
return (
|
|
204
|
+
<Table
|
|
205
|
+
store={store}
|
|
206
|
+
data={mockData}
|
|
207
|
+
columns={getBaseColumns()}
|
|
208
|
+
options={{meta: {rowConfigurable: true}}}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
render(<Fixture />);
|
|
213
|
+
|
|
214
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
215
|
+
|
|
216
|
+
expect(await screen.findByRole('checkbox', {name: 'Name'})).toBeChecked();
|
|
217
|
+
expect(screen.getByRole('checkbox', {name: 'Age'})).toBeChecked();
|
|
218
|
+
expect(screen.getByRole('checkbox', {name: 'Email'})).not.toBeChecked();
|
|
219
|
+
expect(screen.getByRole('checkbox', {name: 'Phone'})).toBeChecked();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('maxSelectableColumns', () => {
|
|
223
|
+
it('disables unchecked columns when the maximum number of visible columns is reached', async () => {
|
|
224
|
+
const user = userEvent.setup();
|
|
225
|
+
const Fixture = () => {
|
|
226
|
+
const store = useTable<RowData>({
|
|
227
|
+
initialState: {columnVisibility: {email: false, phone: false}},
|
|
228
|
+
});
|
|
229
|
+
return (
|
|
230
|
+
<Table
|
|
231
|
+
store={store}
|
|
232
|
+
data={mockData}
|
|
233
|
+
columns={getBaseColumns()}
|
|
234
|
+
options={{meta: {rowConfigurable: {maxSelectableColumns: 2}}}}
|
|
235
|
+
/>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
render(<Fixture />);
|
|
239
|
+
|
|
240
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
241
|
+
|
|
242
|
+
// Name and Age are visible (2 columns = max)
|
|
243
|
+
expect(await screen.findByRole('checkbox', {name: 'Name'})).toBeChecked();
|
|
244
|
+
expect(screen.getByRole('checkbox', {name: 'Name'})).not.toBeDisabled();
|
|
245
|
+
expect(screen.getByRole('checkbox', {name: 'Age'})).toBeChecked();
|
|
246
|
+
expect(screen.getByRole('checkbox', {name: 'Age'})).not.toBeDisabled();
|
|
247
|
+
|
|
248
|
+
// Email and Phone are hidden and should be disabled
|
|
249
|
+
expect(screen.getByRole('checkbox', {name: 'Email'})).not.toBeChecked();
|
|
250
|
+
expect(screen.getByRole('checkbox', {name: 'Email'})).toBeDisabled();
|
|
251
|
+
expect(screen.getByRole('checkbox', {name: 'Phone'})).not.toBeChecked();
|
|
252
|
+
expect(screen.getByRole('checkbox', {name: 'Phone'})).toBeDisabled();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('renders a footer with the max columns message when maxSelectableColumns is set', async () => {
|
|
256
|
+
const user = userEvent.setup();
|
|
257
|
+
const Fixture = () => {
|
|
258
|
+
const store = useTable<RowData>();
|
|
259
|
+
return (
|
|
260
|
+
<Table
|
|
261
|
+
store={store}
|
|
262
|
+
data={mockData}
|
|
263
|
+
columns={getBaseColumns()}
|
|
264
|
+
options={{meta: {rowConfigurable: {maxSelectableColumns: 5}}}}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
render(<Fixture />);
|
|
269
|
+
|
|
270
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
271
|
+
|
|
272
|
+
expect(await screen.findByText('You can display up to 5 columns.')).toBeVisible();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('does not render a footer when maxSelectableColumns is not set', async () => {
|
|
276
|
+
const user = userEvent.setup();
|
|
277
|
+
const Fixture = () => {
|
|
278
|
+
const store = useTable<RowData>();
|
|
279
|
+
return (
|
|
280
|
+
<Table
|
|
281
|
+
store={store}
|
|
282
|
+
data={mockData}
|
|
283
|
+
columns={getBaseColumns()}
|
|
284
|
+
options={{meta: {rowConfigurable: true}}}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
render(<Fixture />);
|
|
289
|
+
|
|
290
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
291
|
+
|
|
292
|
+
await screen.findByRole('checkbox', {name: 'Name'});
|
|
293
|
+
expect(screen.queryByText(/You can display up to/)).not.toBeInTheDocument();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('enables a disabled column when a visible column is hidden', async () => {
|
|
297
|
+
const user = userEvent.setup();
|
|
298
|
+
const Fixture = () => {
|
|
299
|
+
const store = useTable<RowData>({
|
|
300
|
+
initialState: {columnVisibility: {email: false, phone: false}},
|
|
301
|
+
});
|
|
302
|
+
return (
|
|
303
|
+
<Table
|
|
304
|
+
store={store}
|
|
305
|
+
data={mockData}
|
|
306
|
+
columns={getBaseColumns()}
|
|
307
|
+
options={{meta: {rowConfigurable: {maxSelectableColumns: 2}}}}
|
|
308
|
+
/>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
render(<Fixture />);
|
|
312
|
+
|
|
313
|
+
await user.click(screen.getByRole('button', {name: 'settings'}));
|
|
314
|
+
|
|
315
|
+
// Email is disabled because max is reached
|
|
316
|
+
expect(await screen.findByRole('checkbox', {name: 'Email'})).toBeDisabled();
|
|
317
|
+
|
|
318
|
+
// Hide Name column
|
|
319
|
+
await user.click(screen.getByRole('checkbox', {name: 'Name'}));
|
|
320
|
+
|
|
321
|
+
// Now Email should be enabled
|
|
322
|
+
expect(screen.getByRole('checkbox', {name: 'Email'})).not.toBeDisabled();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -3,7 +3,27 @@ import {useProps} from '@mantine/core';
|
|
|
3
3
|
import {CellContext, ColumnDef} from '@tanstack/table-core';
|
|
4
4
|
import {FunctionComponent} from 'react';
|
|
5
5
|
import {TableActionsList, TableActionsListProps} from '../table-actions/TableActionsList.js';
|
|
6
|
+
|
|
6
7
|
import {useTableContext} from '../TableContext.js';
|
|
8
|
+
import {
|
|
9
|
+
TableColumnsSelectorHeader,
|
|
10
|
+
TableColumnsSelectorOptions,
|
|
11
|
+
} from '../table-columns-selector/TableColumnsSelector.js';
|
|
12
|
+
|
|
13
|
+
export interface TableActionsColumnMeta {
|
|
14
|
+
/**
|
|
15
|
+
* When set to `true` or an options object, displays a column selector button in the actions column header.
|
|
16
|
+
* Allows users to show/hide columns in the table.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Simple usage
|
|
20
|
+
* options={{ meta: { rowConfigurable: true } }}
|
|
21
|
+
*
|
|
22
|
+
* // With options
|
|
23
|
+
* options={{ meta: { rowConfigurable: { maxSelectableColumns: 5 } } }}
|
|
24
|
+
*/
|
|
25
|
+
rowConfigurable?: boolean | TableColumnsSelectorOptions;
|
|
26
|
+
}
|
|
7
27
|
|
|
8
28
|
/**
|
|
9
29
|
* Generic column to use when your table needs actions on rows
|
|
@@ -15,7 +35,14 @@ export const TableActionsColumn: ColumnDef<unknown> = {
|
|
|
15
35
|
meta: {
|
|
16
36
|
controlColumn: true,
|
|
17
37
|
},
|
|
18
|
-
header:
|
|
38
|
+
header: ({table}) => {
|
|
39
|
+
const rowConfigurable = (table.options.meta as TableActionsColumnMeta)?.rowConfigurable;
|
|
40
|
+
if (!rowConfigurable) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const options = typeof rowConfigurable === 'boolean' ? {} : rowConfigurable;
|
|
44
|
+
return <TableColumnsSelectorHeader table={table} options={options} />;
|
|
45
|
+
},
|
|
19
46
|
size: 84, // 16px padding left + 28px ActionIcon + 40px padding right
|
|
20
47
|
cell: (info) => <ActionsMenu info={info} />,
|
|
21
48
|
};
|
|
@@ -1,50 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
CompoundStylesApiProps,
|
|
6
|
-
Divider,
|
|
7
|
-
factory,
|
|
8
|
-
Factory,
|
|
9
|
-
Grid,
|
|
10
|
-
Popover,
|
|
11
|
-
ScrollArea,
|
|
12
|
-
Stack,
|
|
13
|
-
Tooltip,
|
|
14
|
-
useProps,
|
|
15
|
-
} from '@mantine/core';
|
|
16
|
-
import {flexRender, Header} from '@tanstack/react-table';
|
|
17
|
-
import {ReactNode} from 'react';
|
|
18
|
-
import {TableComponentsOrder} from '../Table.js';
|
|
19
|
-
import {useTableContext} from '../TableContext.js';
|
|
1
|
+
import {IconSettings} from '@coveord/plasma-react-icons';
|
|
2
|
+
import {Checkbox, Combobox, Text, Tooltip, useCombobox} from '@mantine/core';
|
|
3
|
+
import {flexRender, Header, Table} from '@tanstack/react-table';
|
|
4
|
+
import {ActionIcon} from '../../ActionIcon/ActionIcon';
|
|
20
5
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
export interface TableColumnsSelectorProps extends BoxProps, CompoundStylesApiProps<TableColumnsSelectorFactory> {
|
|
24
|
-
/**
|
|
25
|
-
* The label of the button
|
|
26
|
-
* @default 'Edit columns'
|
|
27
|
-
*/
|
|
28
|
-
label?: ReactNode;
|
|
29
|
-
/**
|
|
30
|
-
* The style variant of the button
|
|
31
|
-
* @default 'outline'
|
|
32
|
-
*/
|
|
33
|
-
buttonVariant?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Whether the count of visible columns is shown in the button label.
|
|
36
|
-
* @default false
|
|
37
|
-
*/
|
|
38
|
-
showVisibleCountLabel?: boolean;
|
|
6
|
+
export interface TableColumnsSelectorOptions {
|
|
39
7
|
/**
|
|
40
8
|
* The maximum number of columns that can be selected at the same time.
|
|
41
9
|
* If defined a footer will render with the remaining number of columns that can be selected.
|
|
10
|
+
* Must be a positive integer (greater than 0).
|
|
42
11
|
*/
|
|
43
12
|
maxSelectableColumns?: number;
|
|
44
13
|
/**
|
|
45
14
|
* The content to display in the footer when maxSelectableColumns is defined.
|
|
15
|
+
* Can be a string or a function that receives the maxSelectableColumns value.
|
|
16
|
+
* @default (max) => `You can display up to ${max} columns.`
|
|
46
17
|
*/
|
|
47
|
-
footer?:
|
|
18
|
+
footer?: string | ((maxSelectableColumns: number) => string);
|
|
48
19
|
/**
|
|
49
20
|
* The tooltip to display when the user hovers over a disabled checkbox because of the limit.
|
|
50
21
|
* @default 'You have reached the maximum display limit.'
|
|
@@ -57,108 +28,108 @@ export interface TableColumnsSelectorProps extends BoxProps, CompoundStylesApiPr
|
|
|
57
28
|
alwaysVisibleTooltip?: string;
|
|
58
29
|
}
|
|
59
30
|
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
compound: true;
|
|
65
|
-
}>;
|
|
31
|
+
export interface TableColumnsSelectorHeaderProps {
|
|
32
|
+
table: Table<unknown>;
|
|
33
|
+
options?: TableColumnsSelectorOptions;
|
|
34
|
+
}
|
|
66
35
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
36
|
+
const DEFAULT_OPTIONS: Omit<TableColumnsSelectorOptions, 'footer'> & {
|
|
37
|
+
footer: (maxSelectableColumns: number) => string;
|
|
38
|
+
} = {
|
|
39
|
+
footer: (max) => `You can display up to ${max} columns.`,
|
|
70
40
|
limitReachedTooltip: 'You have reached the maximum display limit.',
|
|
71
41
|
alwaysVisibleTooltip: 'This column is always visible.',
|
|
72
|
-
showVisibleCountLabel: false,
|
|
73
42
|
};
|
|
74
43
|
|
|
75
|
-
export const
|
|
76
|
-
const {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
showVisibleCountLabel,
|
|
81
|
-
maxSelectableColumns,
|
|
82
|
-
footer,
|
|
83
|
-
limitReachedTooltip,
|
|
84
|
-
alwaysVisibleTooltip,
|
|
85
|
-
classNames,
|
|
86
|
-
className,
|
|
87
|
-
styles,
|
|
88
|
-
style,
|
|
89
|
-
vars,
|
|
90
|
-
...others
|
|
91
|
-
} = useProps('TableColumnsSelector', defaultProps, props);
|
|
92
|
-
const {table} = useTableContext();
|
|
44
|
+
export const TableColumnsSelectorHeader = ({table, options}: TableColumnsSelectorHeaderProps) => {
|
|
45
|
+
const {maxSelectableColumns, footer, limitReachedTooltip, alwaysVisibleTooltip} = {
|
|
46
|
+
...DEFAULT_OPTIONS,
|
|
47
|
+
...options,
|
|
48
|
+
};
|
|
93
49
|
|
|
94
|
-
const
|
|
50
|
+
const combobox = useCombobox({
|
|
51
|
+
onDropdownClose: () => {
|
|
52
|
+
combobox.resetSelectedOption();
|
|
53
|
+
},
|
|
54
|
+
onDropdownOpen: () => combobox.updateSelectedOptionIndex('active'),
|
|
55
|
+
});
|
|
95
56
|
|
|
57
|
+
const allColumns = table.getAllLeafColumns();
|
|
96
58
|
const filteredColumns = allColumns.filter((column) => !column.columnDef.meta?.controlColumn);
|
|
97
59
|
const selectedColumnsCount = filteredColumns.filter((column) => column.getIsVisible()).length;
|
|
98
60
|
|
|
61
|
+
// Validate maxSelectableColumns - must be a positive integer to be effective
|
|
62
|
+
const effectiveMaxColumns =
|
|
63
|
+
maxSelectableColumns !== undefined && maxSelectableColumns > 0 ? maxSelectableColumns : undefined;
|
|
64
|
+
|
|
99
65
|
if (filteredColumns.length <= 0) {
|
|
100
66
|
return null;
|
|
101
67
|
}
|
|
102
68
|
|
|
103
|
-
const
|
|
69
|
+
const getColumnState = (column: (typeof filteredColumns)[number]) => {
|
|
70
|
+
const alwaysVisible = !column.getCanHide();
|
|
71
|
+
const isDisabled =
|
|
72
|
+
(effectiveMaxColumns !== undefined &&
|
|
73
|
+
selectedColumnsCount >= effectiveMaxColumns &&
|
|
74
|
+
!column.getIsVisible()) ||
|
|
75
|
+
alwaysVisible;
|
|
76
|
+
const isVisible = column.getIsVisible() || alwaysVisible;
|
|
77
|
+
return {alwaysVisible, isDisabled, isVisible};
|
|
78
|
+
};
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<Button variant={buttonVariant}>
|
|
115
|
-
{label}
|
|
116
|
-
{showVisibleCountLabel ? ` (${selectedColumnsCount})` : ''}
|
|
117
|
-
</Button>
|
|
118
|
-
</Popover.Target>
|
|
119
|
-
<Popover.Dropdown miw={240}>
|
|
120
|
-
<ScrollArea.Autosize mah={154}>
|
|
121
|
-
<Stack {...getStyles('columnSelectorWrapper', stylesApiProps)}>
|
|
122
|
-
{filteredColumns.map((column) => {
|
|
123
|
-
const alwaysVisible = !column.getCanHide();
|
|
124
|
-
const isDisabled =
|
|
125
|
-
(selectedColumnsCount >= maxSelectableColumns && !column.getIsVisible()) ||
|
|
126
|
-
alwaysVisible;
|
|
80
|
+
const handleOptionClick = (columnId: string) => {
|
|
81
|
+
const column = filteredColumns.find((col) => col.id === columnId);
|
|
82
|
+
if (column) {
|
|
83
|
+
const {isDisabled} = getColumnState(column);
|
|
84
|
+
if (!isDisabled) {
|
|
85
|
+
column.toggleVisibility();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
127
89
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
disabled={isDisabled}
|
|
146
|
-
onChange={column.getToggleVisibilityHandler()}
|
|
147
|
-
/>
|
|
148
|
-
</div>
|
|
149
|
-
</Tooltip>
|
|
150
|
-
);
|
|
90
|
+
const columnOptions = filteredColumns.map((column) => {
|
|
91
|
+
const {alwaysVisible, isDisabled, isVisible} = getColumnState(column);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Combobox.Option value={column.id} key={column.id} disabled={isDisabled} active={isVisible}>
|
|
95
|
+
<Tooltip
|
|
96
|
+
label={alwaysVisible ? alwaysVisibleTooltip : limitReachedTooltip}
|
|
97
|
+
disabled={!isDisabled}
|
|
98
|
+
position="left"
|
|
99
|
+
>
|
|
100
|
+
<div>
|
|
101
|
+
<Checkbox
|
|
102
|
+
checked={isVisible}
|
|
103
|
+
label={flexRender(column.columnDef.header, {
|
|
104
|
+
table,
|
|
105
|
+
column,
|
|
106
|
+
header: {column} as Header<unknown, unknown>,
|
|
151
107
|
})}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
108
|
+
disabled={isDisabled}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
</Tooltip>
|
|
112
|
+
</Combobox.Option>
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Combobox store={combobox} position="bottom-end" shadow="md" onOptionSubmit={handleOptionClick}>
|
|
118
|
+
<Combobox.Target>
|
|
119
|
+
<ActionIcon.Tertiary onClick={() => combobox.toggleDropdown()} aria-label="settings">
|
|
120
|
+
<IconSettings height={16} />
|
|
121
|
+
</ActionIcon.Tertiary>
|
|
122
|
+
</Combobox.Target>
|
|
123
|
+
<Combobox.Dropdown miw={270}>
|
|
124
|
+
<Combobox.Options>{columnOptions}</Combobox.Options>
|
|
125
|
+
{effectiveMaxColumns && (
|
|
126
|
+
<Combobox.Footer>
|
|
127
|
+
<Text size="sm" c="dimmed">
|
|
128
|
+
{typeof footer === 'function' ? footer(effectiveMaxColumns) : footer}
|
|
129
|
+
</Text>
|
|
130
|
+
</Combobox.Footer>
|
|
131
|
+
)}
|
|
132
|
+
</Combobox.Dropdown>
|
|
133
|
+
</Combobox>
|
|
163
134
|
);
|
|
164
|
-
}
|
|
135
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ export * from './components/StickyFooter/StickyFooter.js';
|
|
|
118
118
|
|
|
119
119
|
// Table - override Mantine Table
|
|
120
120
|
export {flexRender as renderTableCell} from '@tanstack/react-table';
|
|
121
|
+
export {TableActionsColumn} from './components/Table/table-column/TableActionsColumn.js';
|
|
121
122
|
export {type TablePredicateProps} from './components/Table/table-predicate/TablePredicate.js';
|
|
122
123
|
export {Table, TableComponentsOrder, type PlasmaTableFactory} from './components/Table/Table.js';
|
|
123
124
|
export {
|