@coveord/plasma-mantine 48.22.5 → 48.23.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 (84) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/.turbo/turbo-test.log +10 -10
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cjs/components/code-editor/CodeEditor.js +1 -3
  5. package/dist/cjs/components/code-editor/CodeEditor.js.map +1 -1
  6. package/dist/cjs/components/collection/Collection.js +15 -17
  7. package/dist/cjs/components/collection/Collection.js.map +1 -1
  8. package/dist/cjs/components/table/Table.js +48 -45
  9. package/dist/cjs/components/table/Table.js.map +1 -1
  10. package/dist/cjs/components/table/TableActions.js +4 -4
  11. package/dist/cjs/components/table/TableActions.js.map +1 -1
  12. package/dist/cjs/components/table/TableCollapsibleColumn.js +1 -1
  13. package/dist/cjs/components/table/TableCollapsibleColumn.js.map +1 -1
  14. package/dist/cjs/components/table/TableContext.js.map +1 -1
  15. package/dist/cjs/components/table/TableFilter.js +4 -0
  16. package/dist/cjs/components/table/TableFilter.js.map +1 -1
  17. package/dist/cjs/components/table/TableHeader.js +36 -4
  18. package/dist/cjs/components/table/TableHeader.js.map +1 -1
  19. package/dist/cjs/components/table/TableSelectableColumn.js +46 -0
  20. package/dist/cjs/components/table/TableSelectableColumn.js.map +1 -0
  21. package/dist/cjs/components/table/Th.js +5 -15
  22. package/dist/cjs/components/table/Th.js.map +1 -1
  23. package/dist/cjs/components/table/useRowSelection.js +58 -0
  24. package/dist/cjs/components/table/useRowSelection.js.map +1 -0
  25. package/dist/cjs/theme/Theme.js +15 -10
  26. package/dist/cjs/theme/Theme.js.map +1 -1
  27. package/dist/definitions/components/code-editor/CodeEditor.d.ts.map +1 -1
  28. package/dist/definitions/components/collection/Collection.d.ts.map +1 -1
  29. package/dist/definitions/components/table/Table.d.ts +12 -2
  30. package/dist/definitions/components/table/Table.d.ts.map +1 -1
  31. package/dist/definitions/components/table/TableActions.d.ts +3 -3
  32. package/dist/definitions/components/table/TableActions.d.ts.map +1 -1
  33. package/dist/definitions/components/table/TableCollapsibleColumn.d.ts +1 -1
  34. package/dist/definitions/components/table/TableContext.d.ts +6 -1
  35. package/dist/definitions/components/table/TableContext.d.ts.map +1 -1
  36. package/dist/definitions/components/table/TableFilter.d.ts.map +1 -1
  37. package/dist/definitions/components/table/TableHeader.d.ts.map +1 -1
  38. package/dist/definitions/components/table/TableSelectableColumn.d.ts +6 -0
  39. package/dist/definitions/components/table/TableSelectableColumn.d.ts.map +1 -0
  40. package/dist/definitions/components/table/Th.d.ts.map +1 -1
  41. package/dist/definitions/components/table/useRowSelection.d.ts +7 -0
  42. package/dist/definitions/components/table/useRowSelection.d.ts.map +1 -0
  43. package/dist/definitions/components/table/useTable.d.ts +2 -0
  44. package/dist/definitions/components/table/useTable.d.ts.map +1 -1
  45. package/dist/definitions/theme/Theme.d.ts.map +1 -1
  46. package/dist/esm/components/code-editor/CodeEditor.js +1 -3
  47. package/dist/esm/components/code-editor/CodeEditor.js.map +1 -1
  48. package/dist/esm/components/collection/Collection.js +15 -17
  49. package/dist/esm/components/collection/Collection.js.map +1 -1
  50. package/dist/esm/components/table/Table.js +48 -45
  51. package/dist/esm/components/table/Table.js.map +1 -1
  52. package/dist/esm/components/table/TableActions.js +4 -4
  53. package/dist/esm/components/table/TableActions.js.map +1 -1
  54. package/dist/esm/components/table/TableCollapsibleColumn.js +2 -2
  55. package/dist/esm/components/table/TableCollapsibleColumn.js.map +1 -1
  56. package/dist/esm/components/table/TableContext.js.map +1 -1
  57. package/dist/esm/components/table/TableFilter.js +4 -0
  58. package/dist/esm/components/table/TableFilter.js.map +1 -1
  59. package/dist/esm/components/table/TableHeader.js +38 -6
  60. package/dist/esm/components/table/TableHeader.js.map +1 -1
  61. package/dist/esm/components/table/TableSelectableColumn.js +38 -0
  62. package/dist/esm/components/table/TableSelectableColumn.js.map +1 -0
  63. package/dist/esm/components/table/Th.js +5 -15
  64. package/dist/esm/components/table/Th.js.map +1 -1
  65. package/dist/esm/components/table/useRowSelection.js +48 -0
  66. package/dist/esm/components/table/useRowSelection.js.map +1 -0
  67. package/dist/esm/theme/Theme.js +15 -10
  68. package/dist/esm/theme/Theme.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/components/code-editor/CodeEditor.tsx +1 -3
  71. package/src/components/collection/Collection.tsx +8 -10
  72. package/src/components/table/Table.tsx +91 -62
  73. package/src/components/table/TableActions.tsx +7 -7
  74. package/src/components/table/TableCollapsibleColumn.tsx +2 -2
  75. package/src/components/table/TableContext.tsx +6 -1
  76. package/src/components/table/TableFilter.tsx +7 -1
  77. package/src/components/table/TableHeader.tsx +24 -4
  78. package/src/components/table/TableSelectableColumn.tsx +33 -0
  79. package/src/components/table/Th.tsx +6 -19
  80. package/src/components/table/__tests__/Table.spec.tsx +100 -7
  81. package/src/components/table/__tests__/TableActions.spec.tsx +21 -0
  82. package/src/components/table/__tests__/TableFilter.spec.tsx +48 -1
  83. package/src/components/table/useRowSelection.ts +45 -0
  84. package/src/theme/Theme.tsx +14 -7
@@ -10,7 +10,7 @@ import {
10
10
  TableState,
11
11
  useReactTable,
12
12
  } from '@tanstack/react-table';
13
- import {InitialTableState} from '@tanstack/table-core';
13
+ import {CoreOptions, InitialTableState} from '@tanstack/table-core';
14
14
  import defaultsDeep from 'lodash.defaultsdeep';
15
15
  import {Children, Fragment, ReactElement, ReactNode, useCallback, useEffect, useState} from 'react';
16
16
 
@@ -24,58 +24,74 @@ import {TableHeader} from './TableHeader';
24
24
  import {TablePagination} from './TablePagination';
25
25
  import {TablePerPage} from './TablePerPage';
26
26
  import {TablePredicate} from './TablePredicate';
27
+ import {TableSelectableColumn} from './TableSelectableColumn';
27
28
  import {Th} from './Th';
29
+ import {useRowSelection} from './useRowSelection';
28
30
 
29
- const useStyles = createStyles<string, {hasHeader: boolean}>((theme, {hasHeader}, getRef) => ({
30
- table: {
31
- width: '100%',
32
- '& td:first-of-type': {
33
- paddingLeft: theme.spacing.xl,
31
+ interface TableStylesParams {
32
+ hasHeader: boolean;
33
+ multiRowSelectionEnabled: boolean;
34
+ }
35
+
36
+ const useStyles = createStyles<string, TableStylesParams>((theme, {hasHeader, multiRowSelectionEnabled}) => {
37
+ const rowBackgroundColor =
38
+ theme.colorScheme === 'dark'
39
+ ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2)
40
+ : theme.colors[theme.primaryColor][0];
41
+ return {
42
+ table: {
43
+ width: '100%',
44
+ '& td:first-of-type, th:first-of-type > *': {
45
+ paddingLeft: theme.spacing.xl,
46
+ },
34
47
  },
35
- },
36
48
 
37
- header: {
38
- position: 'sticky',
39
- top: hasHeader ? 69 : 0,
40
- backgroundColor: theme.colorScheme === 'dark' ? theme.black : theme.white,
41
- transition: 'box-shadow 150ms ease',
42
- zIndex: 12, // skeleton is 11
43
- '& tr th:first-of-type button': {
44
- paddingLeft: theme.spacing.xl,
49
+ header: {
50
+ position: 'sticky',
51
+ top: hasHeader ? 69 : 0,
52
+ backgroundColor: theme.colorScheme === 'dark' ? theme.black : theme.white,
53
+ transition: 'box-shadow 150ms ease',
54
+ zIndex: 12, // skeleton is 11
55
+
56
+ '&::after': {
57
+ content: '""',
58
+ position: 'absolute',
59
+ left: 0,
60
+ right: 0,
61
+ bottom: 0,
62
+ borderBottom: `1px solid ${theme.colors.gray[2]}`,
63
+ },
45
64
  },
46
- '& tr th:first-of-type div': {
47
- paddingLeft: theme.spacing.xl,
65
+
66
+ rowSelected: {
67
+ backgroundColor: multiRowSelectionEnabled ? undefined : rowBackgroundColor,
48
68
  },
49
69
 
50
- '&::after': {
51
- content: '""',
52
- position: 'absolute',
53
- left: 0,
54
- right: 0,
55
- bottom: 0,
56
- borderBottom: `1px solid ${theme.colors.gray[2]}`,
70
+ rowSelectionCheckboxCell: {
71
+ verticalAlign: 'middle',
57
72
  },
58
- },
59
73
 
60
- rowSelected: {
61
- ref: getRef('rowSelected'),
62
- },
74
+ rowCollapsibleButtonCell: {
75
+ textAlign: 'right',
76
+ },
63
77
 
64
- row: {
65
- [`&:hover, &.${getRef('rowSelected')}`]: {
66
- backgroundColor:
67
- theme.colorScheme === 'dark'
68
- ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2)
69
- : theme.colors[theme.primaryColor][0],
78
+ row: {
79
+ '&:hover': {
80
+ backgroundColor: rowBackgroundColor,
81
+ },
70
82
  },
71
- },
72
- }));
83
+ };
84
+ });
73
85
 
74
86
  interface TableProps<T> {
75
87
  /**
76
88
  * Data to display in the table
77
89
  */
78
90
  data: T[];
91
+ /**
92
+ * Defines how each row is uniquely identified. It is highly recommended that you specify this prop to an ID that makes sense.
93
+ */
94
+ getRowId?: CoreOptions<T>['getRowId'];
79
95
  /**
80
96
  * Columns to display in the table.
81
97
  *
@@ -131,6 +147,12 @@ interface TableProps<T> {
131
147
  * Action passed when user double clicks on a row
132
148
  */
133
149
  doubleClickAction?: (datum: T) => void;
150
+ /**
151
+ * Whether the user can select multiple rows in order to perform actions in bulk
152
+ *
153
+ * @default false
154
+ */
155
+ multiRowSelectionEnabled?: boolean;
134
156
  }
135
157
 
136
158
  interface TableType {
@@ -142,12 +164,13 @@ interface TableType {
142
164
  Pagination: typeof TablePagination;
143
165
  PerPage: typeof TablePerPage;
144
166
  Predicate: typeof TablePredicate;
145
- CollapsibleColumn: typeof TableCollapsibleColumn;
146
167
  DateRangePicker: typeof TableDateRangePicker;
168
+ CollapsibleColumn: typeof TableCollapsibleColumn;
147
169
  }
148
170
 
149
171
  export const Table: TableType = <T,>({
150
172
  data,
173
+ getRowId,
151
174
  noDataChildren,
152
175
  getExpandChildren,
153
176
  initialState = {},
@@ -157,6 +180,7 @@ export const Table: TableType = <T,>({
157
180
  children,
158
181
  loading = false,
159
182
  doubleClickAction,
183
+ multiRowSelectionEnabled,
160
184
  }: TableProps<T>) => {
161
185
  const convertedChildren = Children.toArray(children) as ReactElement[];
162
186
  const header = convertedChildren.find((child) => child.type === TableHeader);
@@ -166,15 +190,16 @@ export const Table: TableType = <T,>({
166
190
  const form = useForm<TableFormType>({
167
191
  initialValues: {predicates: initialState?.predicates ?? {}, dateRange: initialState?.dateRange ?? [null, null]},
168
192
  });
169
-
170
- const {cx, classes} = useStyles({hasHeader: !!header});
193
+ const {cx, classes} = useStyles({hasHeader: !!header, multiRowSelectionEnabled});
171
194
 
172
195
  const table = useReactTable({
173
196
  initialState: defaultsDeep(initialStateWithoutForm, {pagination: {pageSize: TablePerPage.DEFAULT_SIZE}}),
174
197
  data,
175
- columns,
198
+ columns: multiRowSelectionEnabled ? [TableSelectableColumn as ColumnDef<T>].concat(columns) : columns,
176
199
  getCoreRowModel: getCoreRowModel(),
177
200
  manualPagination: true,
201
+ enableMultiRowSelection: !!multiRowSelectionEnabled,
202
+ getRowId,
178
203
  getRowCanExpand: (row: Row<T>) => !!getExpandChildren?.(row.original) ?? false,
179
204
  });
180
205
  const [state, setState] = useState<TableState>(table.initialState);
@@ -183,6 +208,7 @@ export const Table: TableType = <T,>({
183
208
  state,
184
209
  onStateChange: setState,
185
210
  }));
211
+ const {clearSelection, getSelectedRow, getSelectedRows} = useRowSelection(table);
186
212
 
187
213
  const triggerChange = () => onChange?.({...state, ...form.values});
188
214
 
@@ -190,13 +216,11 @@ export const Table: TableType = <T,>({
190
216
  onMount?.({...state, ...form.values});
191
217
  }, []);
192
218
 
193
- const outsideClickRef = useClickOutside(() => {
194
- table.resetRowSelection(true);
195
- });
196
-
197
219
  useDidUpdate(() => {
198
220
  triggerChange();
199
- clearSelection();
221
+ if (!multiRowSelectionEnabled) {
222
+ clearSelection();
223
+ }
200
224
  }, [state.globalFilter, state.sorting, state.pagination, form.values]);
201
225
 
202
226
  const clearFilters = useCallback(() => {
@@ -204,14 +228,11 @@ export const Table: TableType = <T,>({
204
228
  setState((prevState) => ({...prevState, globalFilter: ''}));
205
229
  }, []);
206
230
 
207
- const clearSelection = () => {
208
- setState((prevState) => ({...prevState, rowSelection: {}}));
209
- };
210
-
211
- const getSelectedRow = useCallback(
212
- () => table.getSelectedRowModel().flatRows?.[0]?.original ?? null,
213
- [state.rowSelection]
214
- );
231
+ const outsideClickRef = useClickOutside(() => {
232
+ if (!multiRowSelectionEnabled) {
233
+ clearSelection();
234
+ }
235
+ });
215
236
 
216
237
  if (!data) {
217
238
  return (
@@ -221,25 +242,29 @@ export const Table: TableType = <T,>({
221
242
  );
222
243
  }
223
244
 
224
- const toggleRowSelection = (row: Row<T>) => {
225
- table.setRowSelection(() => ({[row.id]: !row.getIsSelected()}));
226
- };
227
-
228
245
  const rows = table.getRowModel().rows.map((row) => {
229
246
  const rowChildren = getExpandChildren?.(row.original) ?? null;
230
247
 
231
248
  return (
232
249
  <Fragment key={row.id}>
233
250
  <tr
234
- onClick={() => toggleRowSelection(row)}
251
+ onClick={() => row.toggleSelected()}
235
252
  onDoubleClick={() => doubleClickAction?.(row.original)}
236
253
  className={cx(classes.row, {[classes.rowSelected]: row.getIsSelected()})}
254
+ aria-selected={row.getIsSelected()}
237
255
  >
238
256
  {row.getVisibleCells().map((cell) => {
239
257
  const size = cell.column.getSize();
240
258
  const width = size !== defaultColumnSizing.size ? size : undefined;
241
259
  return (
242
- <td key={cell.id} style={{width}}>
260
+ <td
261
+ key={cell.id}
262
+ style={{width}}
263
+ className={cx({
264
+ [classes.rowSelectionCheckboxCell]: cell.column.id === TableSelectableColumn.id,
265
+ [classes.rowCollapsibleButtonCell]: cell.column.id === TableCollapsibleColumn.id,
266
+ })}
267
+ >
243
268
  <Skeleton visible={loading} sx={!loading ? {borderRadius: 0} : undefined}>
244
269
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
245
270
  </Skeleton>
@@ -251,10 +276,12 @@ export const Table: TableType = <T,>({
251
276
  <tr>
252
277
  <td
253
278
  colSpan={columns.length + 1}
254
- style={{padding: 0, borderBottomColor: row.getIsExpanded() ? undefined : 'transparent'}}
279
+ style={{padding: 0, borderTop: row.getIsExpanded() ? undefined : 'none'}}
255
280
  >
256
- <Collapse in={row.getIsExpanded()} px="sm" py="xs">
257
- {rowChildren}
281
+ <Collapse in={row.getIsExpanded()}>
282
+ <Box px="sm" py="xs">
283
+ {rowChildren}
284
+ </Box>
258
285
  </Collapse>
259
286
  </td>
260
287
  </tr>
@@ -272,9 +299,11 @@ export const Table: TableType = <T,>({
272
299
  setState,
273
300
  clearFilters,
274
301
  getSelectedRow,
302
+ getSelectedRows,
275
303
  clearSelection,
276
304
  form,
277
305
  containerRef: outsideClickRef,
306
+ multiRowSelectionEnabled,
278
307
  }}
279
308
  >
280
309
  {header}
@@ -3,9 +3,9 @@ import {useTable} from './useTable';
3
3
 
4
4
  interface TableActionsProps<T> {
5
5
  /**
6
- * Function that return components for the selected row
6
+ * Function that return components for the selected row or selected rows when multi row selection is enabled
7
7
  *
8
- * @param datum the data of the selected row
8
+ * @param datum the data of the selected row(s)
9
9
  * @example
10
10
  * <Table.Actions<MyType>>
11
11
  * {(datum: MyType) => (
@@ -21,16 +21,16 @@ interface TableActionsProps<T> {
21
21
  * )}
22
22
  * </Table.Actions>
23
23
  */
24
- children: (datum: T) => ReactNode;
24
+ children: ((datum: T) => ReactNode) | ((data: T[]) => ReactNode);
25
25
  }
26
26
 
27
27
  export const TableActions = <T,>({children}: TableActionsProps<T>): ReactElement => {
28
- const {getSelectedRow} = useTable();
29
- const selectedRow = getSelectedRow();
28
+ const {getSelectedRows, multiRowSelectionEnabled} = useTable();
29
+ const selectedRows = getSelectedRows();
30
30
 
31
- if (!selectedRow) {
31
+ if (selectedRows.length <= 0) {
32
32
  return null;
33
33
  }
34
34
 
35
- return <>{children(selectedRow)}</>;
35
+ return <>{children(multiRowSelectionEnabled ? selectedRows : selectedRows[0])}</>;
36
36
  };
@@ -4,12 +4,12 @@ import {ColumnDef} from '@tanstack/table-core';
4
4
  import {MouseEvent as ReactMouseEvent} from 'react';
5
5
 
6
6
  /**
7
- * Generic column to use when your table need collapsible rows
7
+ * Generic column to use when your table needs collapsible rows
8
8
  */
9
9
  export const TableCollapsibleColumn: ColumnDef<unknown> = {
10
10
  id: 'collapsible',
11
- header: '',
12
11
  enableSorting: false,
12
+ header: '',
13
13
  cell: (info) => {
14
14
  const handler = info.row.getToggleExpandedHandler();
15
15
  const onClick = (e: ReactMouseEvent<HTMLButtonElement>) => {
@@ -40,9 +40,13 @@ type TableContextType = {
40
40
  */
41
41
  clearFilters: () => void;
42
42
  /**
43
- * Function that returns the selected row if any
43
+ * Function that returns the selected row if any.
44
44
  */
45
45
  getSelectedRow: () => any | null;
46
+ /**
47
+ * Function that returns an array of the selected rows. Most useful when multi row selection is enabled.
48
+ */
49
+ getSelectedRows: () => any[];
46
50
  /**
47
51
  * Function that clear the selected row
48
52
  */
@@ -55,6 +59,7 @@ type TableContextType = {
55
59
  * Table container ref
56
60
  */
57
61
  containerRef: RefObject<HTMLDivElement>;
62
+ multiRowSelectionEnabled: boolean;
58
63
  };
59
64
 
60
65
  export const TableContext = createContext<TableContextType | null>(null);
@@ -35,7 +35,13 @@ export const TableFilter: FunctionComponent<TableFilterProps> = ({
35
35
 
36
36
  const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
37
37
  const {value} = event.currentTarget;
38
- setState((prevState: TableState) => ({...prevState, globalFilter: value}));
38
+ setState((prevState: TableState) => ({
39
+ ...prevState,
40
+ pagination: prevState.pagination
41
+ ? {pageIndex: 0, pageSize: prevState.pagination.pageSize}
42
+ : prevState.pagination,
43
+ globalFilter: value,
44
+ }));
39
45
  };
40
46
 
41
47
  return (
@@ -1,13 +1,16 @@
1
- import {createStyles, DefaultProps, Group, Selectors} from '@mantine/core';
1
+ import {CrossSize16Px} from '@coveord/plasma-react-icons';
2
+ import {Button, createStyles, DefaultProps, Group, Selectors, Space, Tooltip} from '@mantine/core';
2
3
  import {FunctionComponent, ReactNode} from 'react';
3
4
 
5
+ import {useTable} from './useTable';
6
+
4
7
  const useStyles = createStyles((theme) => ({
5
8
  root: {
6
9
  position: 'sticky',
7
10
  top: 0,
8
11
  zIndex: 13, // skeleton is 11
9
12
  backgroundColor: theme.colors.gray[1],
10
- borderBottom: `1px solid ${theme.colors.gray[4]}`,
13
+ borderBottom: `1px solid ${theme.colors.gray[3]}`,
11
14
  },
12
15
  }));
13
16
 
@@ -23,9 +26,26 @@ export const TableHeader: FunctionComponent<TableHeaderProps> = ({
23
26
  children,
24
27
  ...others
25
28
  }) => {
29
+ const {getSelectedRows, multiRowSelectionEnabled, clearSelection} = useTable();
26
30
  const {classes} = useStyles(null, {name: 'TableHeader', classNames, styles, unstyled});
27
- return (
28
- <Group position="right" spacing="lg" className={classes.root} px="md" py="sm" {...others}>
31
+ const selectedRows = getSelectedRows();
32
+ return multiRowSelectionEnabled ? (
33
+ <Group position="apart" className={classes.root}>
34
+ {selectedRows.length > 0 ? (
35
+ <Tooltip label="Unselect all">
36
+ <Button onClick={clearSelection} ml="lg" variant="subtle" leftIcon={<CrossSize16Px height={16} />}>
37
+ {selectedRows.length} selected
38
+ </Button>
39
+ </Tooltip>
40
+ ) : (
41
+ <Space />
42
+ )}
43
+ <Group position="right" spacing="lg" px="md" py="sm" {...others}>
44
+ {children}
45
+ </Group>
46
+ </Group>
47
+ ) : (
48
+ <Group position="right" spacing="lg" px="md" py="sm" className={classes.root} {...others}>
29
49
  {children}
30
50
  </Group>
31
51
  );
@@ -0,0 +1,33 @@
1
+ import {Checkbox, Tooltip} from '@mantine/core';
2
+ import {ColumnDef} from '@tanstack/table-core';
3
+
4
+ /**
5
+ * Generic column to use when your table needs multi selection of rows
6
+ */
7
+ export const TableSelectableColumn: ColumnDef<unknown> = {
8
+ id: 'select',
9
+ enableSorting: false,
10
+ header: ({table}) => {
11
+ const label = table.getIsAllRowsSelected() ? 'Unselect all from this page' : 'Select all from this page';
12
+ return (
13
+ <Tooltip label={label}>
14
+ <Checkbox
15
+ checked={table.getIsAllPageRowsSelected()}
16
+ indeterminate={table.getIsSomePageRowsSelected()}
17
+ onChange={table.getToggleAllPageRowsSelectedHandler()}
18
+ sx={{display: 'flex'}}
19
+ aria-label={label}
20
+ />
21
+ </Tooltip>
22
+ );
23
+ },
24
+ cell: ({row}) => (
25
+ <Checkbox
26
+ checked={row.getIsSelected()}
27
+ indeterminate={row.getIsSomeSelected()}
28
+ onChange={row.getToggleSelectedHandler()}
29
+ sx={{display: 'flex'}}
30
+ aria-label="Select row"
31
+ />
32
+ ),
33
+ };
@@ -4,27 +4,16 @@ import {defaultColumnSizing, flexRender, Header} from '@tanstack/react-table';
4
4
 
5
5
  const useStyles = createStyles((theme) => ({
6
6
  th: {
7
- padding: '0 !important',
8
7
  fontWeight: '400 !important' as any,
8
+ padding: '0 !important',
9
9
  color: theme.black + '!important',
10
- button: {
11
- padding: '8px 16px',
12
- div: {
13
- padding: '0px !important',
14
- },
15
- },
16
- div: {
17
- padding: '8px 16px',
18
- },
19
- },
20
-
21
- noSort: {
22
- padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
10
+ verticalAlign: 'middle',
23
11
  },
24
12
 
25
13
  control: {
26
14
  width: '100%',
27
- padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
15
+ padding: `${theme.spacing.xs}px ${theme.spacing.sm}px`,
16
+ whiteSpace: 'nowrap',
28
17
 
29
18
  '&:hover': {
30
19
  backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[6] : theme.colors.gray[2],
@@ -58,9 +47,7 @@ export const Th = <T,>({header}: ThProps<T>) => {
58
47
  if (!header.column.getCanSort()) {
59
48
  return (
60
49
  <th className={classes.th} style={{width}}>
61
- <Text className={classes.noSort} size="xs">
62
- {flexRender(header.column.columnDef.header, header.getContext())}
63
- </Text>
50
+ <Text size="xs">{flexRender(header.column.columnDef.header, header.getContext())}</Text>
64
51
  </th>
65
52
  );
66
53
  }
@@ -72,7 +59,7 @@ export const Th = <T,>({header}: ThProps<T>) => {
72
59
  return (
73
60
  <th className={classes.th} style={{width}} aria-sort={sortingOrder ? SortingLabels[sortingOrder] : 'none'}>
74
61
  <UnstyledButton onClick={onSort} className={classes.control}>
75
- <Group position="apart">
62
+ <Group position="apart" noWrap>
76
63
  <Text size="xs">{flexRender(header.column.columnDef.header, header.getContext())}</Text>
77
64
  <Center sx={(theme) => ({color: sortingOrder ? theme.colors.action[8] : undefined})}>
78
65
  <Icon height={14} />
@@ -1,5 +1,5 @@
1
1
  import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
2
- import {render, screen, userEvent, waitFor} from '@test-utils';
2
+ import {render, screen, userEvent, waitFor, within} from '@test-utils';
3
3
  import {FunctionComponent} from 'react';
4
4
 
5
5
  import {Table} from '../Table';
@@ -75,7 +75,7 @@ describe('Table', () => {
75
75
  columnHelper.accessor('firstName', {
76
76
  enableSorting: false,
77
77
  }),
78
- Table.CollapsibleColumn,
78
+ Table.CollapsibleColumn as ColumnDef<RowData>,
79
79
  ];
80
80
  render(
81
81
  <Table
@@ -106,7 +106,7 @@ describe('Table', () => {
106
106
  columnHelper.accessor('firstName', {
107
107
  enableSorting: false,
108
108
  }),
109
- Table.CollapsibleColumn,
109
+ Table.CollapsibleColumn as ColumnDef<RowData>,
110
110
  ];
111
111
  render(
112
112
  <Table
@@ -157,16 +157,109 @@ describe('Table', () => {
157
157
  </div>
158
158
  );
159
159
 
160
- const row = screen.getByRole('row', {name: 'patate king'});
160
+ const row = screen.getByRole('row', {name: 'patate king', selected: false});
161
161
 
162
- expect(row).not.toHaveClass('__mantine-ref-rowSelected');
162
+ expect(row).toBeInTheDocument();
163
163
 
164
164
  await user.click(row);
165
165
 
166
- expect(row).toHaveClass('__mantine-ref-rowSelected');
166
+ expect(screen.getByRole('row', {name: 'patate king', selected: true})).toBeInTheDocument();
167
167
 
168
168
  await user.click(screen.getByText(/i'm a header/i));
169
169
 
170
- expect(row).not.toHaveClass('__mantine-ref-rowSelected');
170
+ expect(screen.getByRole('row', {name: 'patate king', selected: false})).toBeInTheDocument();
171
+ });
172
+
173
+ describe('when multi row selection is enabled', () => {
174
+ it('displays a checkbox as the first cell of each row', () => {
175
+ render(
176
+ <Table
177
+ data={[
178
+ {firstName: 'John', lastName: 'Smith'},
179
+ {firstName: 'Jane', lastName: 'Doe'},
180
+ ]}
181
+ columns={columns}
182
+ multiRowSelectionEnabled
183
+ />
184
+ );
185
+
186
+ expect(screen.getByRole('columnheader', {name: /select all from this page/i})).toBeInTheDocument();
187
+
188
+ const rows = screen.getAllByRole('row');
189
+ rows.forEach((row) => {
190
+ expect(within(row).getByRole('checkbox', {name: /select/i})).toBeInTheDocument();
191
+ });
192
+ });
193
+
194
+ it('selects all rows of the current page when clicking on the checkbox that is in the column header', async () => {
195
+ const user = userEvent.setup({delay: null});
196
+ render(
197
+ <Table
198
+ data={[
199
+ {firstName: 'John', lastName: 'Smith'},
200
+ {firstName: 'Jane', lastName: 'Doe'},
201
+ ]}
202
+ columns={columns}
203
+ multiRowSelectionEnabled
204
+ />
205
+ );
206
+
207
+ const selectAll = screen.getByRole('checkbox', {name: /select all from this page/i});
208
+ await user.click(selectAll);
209
+
210
+ expect(screen.getAllByRole('row', {selected: true})).toHaveLength(2);
211
+ await user.click(selectAll);
212
+
213
+ expect(screen.queryAllByRole('row', {selected: true})).toEqual([]);
214
+ });
215
+
216
+ it('does not clear the row selection when clicking outside the table', async () => {
217
+ const user = userEvent.setup({delay: null});
218
+ render(
219
+ <div>
220
+ <div>I'm a header</div>
221
+ <Table
222
+ data={[
223
+ {firstName: 'first', lastName: 'last'},
224
+ {firstName: 'patate', lastName: 'king'},
225
+ ]}
226
+ columns={columns}
227
+ multiRowSelectionEnabled
228
+ />
229
+ </div>
230
+ );
231
+
232
+ const row = screen.getByRole('row', {name: /patate king/i, selected: false});
233
+
234
+ expect(row).toBeInTheDocument();
235
+
236
+ await user.click(row);
237
+
238
+ expect(screen.getByRole('row', {name: /patate king/i, selected: true})).toBeInTheDocument();
239
+
240
+ await user.click(screen.getByText(/i'm a header/i));
241
+
242
+ expect(screen.getByRole('row', {name: /patate king/i, selected: true})).toBeInTheDocument();
243
+ });
244
+
245
+ it('unselects all the selected rows when clicking on the the unselect button from the table header', async () => {
246
+ const user = userEvent.setup({delay: null});
247
+ render(
248
+ <Table
249
+ data={[
250
+ {firstName: 'John', lastName: 'Smith'},
251
+ {firstName: 'Jane', lastName: 'Doe'},
252
+ ]}
253
+ columns={columns}
254
+ multiRowSelectionEnabled
255
+ >
256
+ <Table.Header />
257
+ </Table>
258
+ );
259
+
260
+ await user.click(screen.getByRole('checkbox', {name: /select all/i}));
261
+ await user.click(screen.getByRole('button', {name: /2 selected/i}));
262
+ expect(screen.queryAllByRole('row', {selected: true})).toEqual([]);
263
+ });
171
264
  });
172
265
  });
@@ -34,4 +34,25 @@ describe('Table.Actions', () => {
34
34
  expect(screen.queryByRole('button', {name: 'Eat fruit'})).not.toBeInTheDocument();
35
35
  expect(screen.getByRole('button', {name: 'Eat vegetable'})).toBeVisible();
36
36
  });
37
+
38
+ describe('when multi row selection is enabled', () => {
39
+ it('passes down an array of selected rows', async () => {
40
+ const user = userEvent.setup({delay: null});
41
+ const renderSpy = jest.fn().mockImplementation(() => <div />);
42
+ render(
43
+ <Table<RowData>
44
+ data={[{name: 'fruit'}, {name: 'vegetable'}, {name: 'bread'}]}
45
+ columns={columns}
46
+ multiRowSelectionEnabled
47
+ >
48
+ <Table.Header>
49
+ <Table.Actions>{renderSpy}</Table.Actions>
50
+ </Table.Header>
51
+ </Table>
52
+ );
53
+ await user.click(screen.getByRole('cell', {name: 'fruit'}));
54
+ await user.click(screen.getByRole('cell', {name: 'vegetable'}));
55
+ expect(renderSpy).toHaveBeenCalledWith([{name: 'fruit'}, {name: 'vegetable'}]);
56
+ });
57
+ });
37
58
  });