@coveord/plasma-mantine 52.9.0 → 52.10.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 (96) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/.turbo/turbo-test.log +33 -32
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cjs/components/code-editor/CodeEditor.d.ts.map +1 -1
  5. package/dist/cjs/components/code-editor/CodeEditor.js +4 -2
  6. package/dist/cjs/components/code-editor/CodeEditor.js.map +1 -1
  7. package/dist/cjs/components/date-range-picker/DateRangePickerPresetSelect.d.ts.map +1 -1
  8. package/dist/cjs/components/date-range-picker/DateRangePickerPresetSelect.js +2 -1
  9. package/dist/cjs/components/date-range-picker/DateRangePickerPresetSelect.js.map +1 -1
  10. package/dist/cjs/components/table/Table.d.ts.map +1 -1
  11. package/dist/cjs/components/table/Table.js +23 -12
  12. package/dist/cjs/components/table/Table.js.map +1 -1
  13. package/dist/cjs/components/table/Table.styles.d.ts.map +1 -1
  14. package/dist/cjs/components/table/Table.styles.js +1 -10
  15. package/dist/cjs/components/table/Table.styles.js.map +1 -1
  16. package/dist/cjs/components/table/Table.types.d.ts +14 -3
  17. package/dist/cjs/components/table/Table.types.d.ts.map +1 -1
  18. package/dist/cjs/components/table/TableFooter.js +2 -2
  19. package/dist/cjs/components/table/TableFooter.js.map +1 -1
  20. package/dist/cjs/components/table/TableHeader.js +1 -1
  21. package/dist/cjs/components/table/TableHeader.js.map +1 -1
  22. package/dist/cjs/components/table/TableLastUpdated.d.ts +24 -0
  23. package/dist/cjs/components/table/TableLastUpdated.d.ts.map +1 -0
  24. package/dist/cjs/components/table/TableLastUpdated.js +73 -0
  25. package/dist/cjs/components/table/TableLastUpdated.js.map +1 -0
  26. package/dist/cjs/components/table/TablePagination.d.ts.map +1 -1
  27. package/dist/cjs/components/table/TablePagination.js +1 -0
  28. package/dist/cjs/components/table/TablePagination.js.map +1 -1
  29. package/dist/cjs/components/table/TablePerPage.js +3 -1
  30. package/dist/cjs/components/table/TablePerPage.js.map +1 -1
  31. package/dist/cjs/components/table/layouts/RowLayout.d.ts.map +1 -1
  32. package/dist/cjs/components/table/layouts/RowLayout.js +41 -9
  33. package/dist/cjs/components/table/layouts/RowLayout.js.map +1 -1
  34. package/dist/cjs/components/table/useRowSelection.d.ts +2 -2
  35. package/dist/cjs/components/table/useRowSelection.d.ts.map +1 -1
  36. package/dist/cjs/components/table/useRowSelection.js +8 -3
  37. package/dist/cjs/components/table/useRowSelection.js.map +1 -1
  38. package/dist/esm/components/code-editor/CodeEditor.d.ts.map +1 -1
  39. package/dist/esm/components/code-editor/CodeEditor.js +4 -2
  40. package/dist/esm/components/code-editor/CodeEditor.js.map +1 -1
  41. package/dist/esm/components/date-range-picker/DateRangePickerPresetSelect.d.ts.map +1 -1
  42. package/dist/esm/components/date-range-picker/DateRangePickerPresetSelect.js +2 -1
  43. package/dist/esm/components/date-range-picker/DateRangePickerPresetSelect.js.map +1 -1
  44. package/dist/esm/components/table/Table.d.ts.map +1 -1
  45. package/dist/esm/components/table/Table.js +25 -14
  46. package/dist/esm/components/table/Table.js.map +1 -1
  47. package/dist/esm/components/table/Table.styles.d.ts.map +1 -1
  48. package/dist/esm/components/table/Table.styles.js +1 -10
  49. package/dist/esm/components/table/Table.styles.js.map +1 -1
  50. package/dist/esm/components/table/Table.types.d.ts +14 -3
  51. package/dist/esm/components/table/Table.types.d.ts.map +1 -1
  52. package/dist/esm/components/table/Table.types.js.map +1 -1
  53. package/dist/esm/components/table/TableFooter.js +2 -2
  54. package/dist/esm/components/table/TableFooter.js.map +1 -1
  55. package/dist/esm/components/table/TableHeader.js +1 -1
  56. package/dist/esm/components/table/TableHeader.js.map +1 -1
  57. package/dist/esm/components/table/TableLastUpdated.d.ts +24 -0
  58. package/dist/esm/components/table/TableLastUpdated.d.ts.map +1 -0
  59. package/dist/esm/components/table/TableLastUpdated.js +62 -0
  60. package/dist/esm/components/table/TableLastUpdated.js.map +1 -0
  61. package/dist/esm/components/table/TablePagination.d.ts.map +1 -1
  62. package/dist/esm/components/table/TablePagination.js +1 -0
  63. package/dist/esm/components/table/TablePagination.js.map +1 -1
  64. package/dist/esm/components/table/TablePerPage.js +3 -1
  65. package/dist/esm/components/table/TablePerPage.js.map +1 -1
  66. package/dist/esm/components/table/layouts/RowLayout.d.ts.map +1 -1
  67. package/dist/esm/components/table/layouts/RowLayout.js +42 -10
  68. package/dist/esm/components/table/layouts/RowLayout.js.map +1 -1
  69. package/dist/esm/components/table/useRowSelection.d.ts +2 -2
  70. package/dist/esm/components/table/useRowSelection.d.ts.map +1 -1
  71. package/dist/esm/components/table/useRowSelection.js +8 -3
  72. package/dist/esm/components/table/useRowSelection.js.map +1 -1
  73. package/package.json +2 -2
  74. package/src/__tests__/VitestSetup.ts +12 -0
  75. package/src/components/code-editor/CodeEditor.tsx +4 -2
  76. package/src/components/code-editor/__tests__/CodeEditor.spec.tsx +1 -0
  77. package/src/components/date-range-picker/DateRangePickerPresetSelect.tsx +1 -0
  78. package/src/components/date-range-picker/__tests__/DateRangePickerInlineCalendar.spec.tsx +2 -0
  79. package/src/components/date-range-picker/__tests__/DateRangePickerPopoverCalendar.spec.tsx +4 -19
  80. package/src/components/date-range-picker/__tests__/EditableDateRangePicker.spec.tsx +3 -3
  81. package/src/components/modal-wizard/__tests__/ModalWizard.spec.tsx +19 -4
  82. package/src/components/table/Table.styles.ts +0 -9
  83. package/src/components/table/Table.tsx +22 -13
  84. package/src/components/table/Table.types.ts +14 -3
  85. package/src/components/table/TableFooter.tsx +1 -1
  86. package/src/components/table/TableHeader.tsx +1 -1
  87. package/src/components/table/TableLastUpdated.tsx +51 -0
  88. package/src/components/table/TablePagination.tsx +1 -0
  89. package/src/components/table/TablePerPage.tsx +3 -3
  90. package/src/components/table/__tests__/Table.spec.tsx +44 -5
  91. package/src/components/table/__tests__/TableActions.spec.tsx +4 -3
  92. package/src/components/table/__tests__/TableDateRangePicker.spec.tsx +26 -59
  93. package/src/components/table/__tests__/TableLastUpdated.spec.tsx +97 -0
  94. package/src/components/table/__tests__/TablePredicate.spec.tsx +7 -55
  95. package/src/components/table/layouts/RowLayout.tsx +45 -11
  96. package/src/components/table/useRowSelection.ts +13 -6
@@ -0,0 +1,51 @@
1
+ import {createStyles, DefaultProps, Group, Selectors, Text} from '@mantine/core';
2
+ import {useDidUpdate} from '@mantine/hooks';
3
+ import dayjs from 'dayjs';
4
+ import {FunctionComponent, useState} from 'react';
5
+ import {useTable} from './TableContext';
6
+
7
+ const useStyles = createStyles((theme) => ({
8
+ root: {
9
+ minHeight: '98px',
10
+ },
11
+ label: {
12
+ color: theme.colors.gray[6],
13
+ },
14
+ }));
15
+
16
+ type TableLastUpdatedStylesNames = Selectors<typeof useStyles>;
17
+
18
+ interface TableLastUpdatedProps extends DefaultProps<TableLastUpdatedStylesNames> {
19
+ /**
20
+ * Label to contextualize the date
21
+ *
22
+ * @default "Last update:"
23
+ */
24
+ label?: string;
25
+ }
26
+
27
+ export const TableLastUpdated: FunctionComponent<TableLastUpdatedProps & {dependencies?: never}> = ({
28
+ label = 'Last update:',
29
+ dependencies,
30
+ classNames,
31
+ styles,
32
+ unstyled,
33
+ ...others
34
+ }) => {
35
+ const {classes} = useStyles(null, {name: 'TableLastUpdated', classNames, styles, unstyled});
36
+ const {state} = useTable();
37
+ const [time, setTime] = useState(new Date());
38
+
39
+ useDidUpdate(() => {
40
+ setTime(new Date());
41
+ }, [state, ...dependencies]);
42
+
43
+ return (
44
+ <Group className={classes.root} px="xl" position="right">
45
+ <Text size="xs" className={classes.label} {...others}>
46
+ {label}
47
+ <span role="timer">{dayjs(time).format('h:mm:ss A')}</span>
48
+ </Text>
49
+ </Group>
50
+ );
51
+ };
@@ -29,6 +29,7 @@ export const TablePagination: FunctionComponent<TablePaginationProps> = ({totalP
29
29
  total={total}
30
30
  boundaries={0}
31
31
  size="md"
32
+ spacing="xs"
32
33
  getControlProps={(control) => {
33
34
  switch (control) {
34
35
  case 'previous':
@@ -32,14 +32,14 @@ export const TablePerPage: FunctionComponent<TablePerPageProps> & {DEFAULT_SIZE:
32
32
  };
33
33
 
34
34
  return (
35
- <Group>
36
- <Text>{label}</Text>
35
+ <Group spacing="sm">
36
+ <Text fw={500}>{label}</Text>
37
37
  <SegmentedControl
38
38
  value={state.pagination.pageSize.toString() ?? values?.[1].toString()}
39
39
  onChange={updatePerPage}
40
40
  data={values.map((value) => value.toString())}
41
41
  color="action"
42
- size="md"
42
+ size="sm"
43
43
  />
44
44
  </Group>
45
45
  );
@@ -1,9 +1,10 @@
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
 
4
+ import {useState} from 'react';
4
5
  import {Table} from '../Table';
5
- import {useTable} from '../TableContext';
6
6
  import {TableLayout} from '../Table.types';
7
+ import {useTable} from '../TableContext';
7
8
 
8
9
  type RowData = {id: string; firstName: string; lastName?: string};
9
10
 
@@ -141,6 +142,44 @@ describe('Table', () => {
141
142
  expect(screen.getByRole('row', {name: 'patate king', selected: false})).toBeInTheDocument();
142
143
  });
143
144
 
145
+ it('does not reset row selection when clicking within one of the specified additionalRootNodes, even if it is outside the table', async () => {
146
+ const user = userEvent.setup();
147
+
148
+ const Fixture = () => {
149
+ const [cousinNode, setCousinNode] = useState<HTMLDivElement>();
150
+
151
+ return (
152
+ <>
153
+ <div key="inside" ref={setCousinNode} data-testid="table-cousin">
154
+ clicking inside here does not clear rows selection
155
+ </div>
156
+ <div key="outside" data-testid="outside-element">
157
+ clicking inside here clears rows selection
158
+ </div>
159
+ <Table
160
+ getRowId={({id}) => id}
161
+ data={[
162
+ {id: '🆔-1', firstName: 'John', lastName: 'Doe'},
163
+ {id: '🆔-2', firstName: 'Jane', lastName: 'Doe'},
164
+ ]}
165
+ columns={columns}
166
+ additionalRootNodes={[cousinNode]}
167
+ />
168
+ </>
169
+ );
170
+ };
171
+ render(<Fixture />);
172
+
173
+ const row = screen.getByRole('row', {name: /John Doe/i, selected: false});
174
+ expect(row).toBeInTheDocument();
175
+ await user.click(row);
176
+ expect(screen.getByRole('row', {name: /John Doe/i, selected: true})).toBeInTheDocument();
177
+ await user.click(screen.getByTestId('table-cousin'));
178
+ expect(screen.getByRole('row', {name: /John Doe/i, selected: true})).toBeInTheDocument();
179
+ await user.click(screen.getByTestId('outside-element'));
180
+ expect(screen.getByRole('row', {name: /John Doe/i, selected: false})).toBeInTheDocument();
181
+ });
182
+
144
183
  describe('with multiple layouts', () => {
145
184
  const layouts: TableLayout[] = [
146
185
  {
@@ -243,13 +282,13 @@ describe('Table', () => {
243
282
  onRowSelectionChange={onRowSelectionChangeSpy}
244
283
  />
245
284
  );
246
- await user.click(screen.getByRole('row', {name: /jane doe/i}));
285
+ await user.click(within(screen.getByRole('row', {name: /jane doe/i})).getByRole('checkbox'));
247
286
  expect(onRowSelectionChangeSpy).toHaveBeenCalledTimes(1);
248
287
  expect(onRowSelectionChangeSpy).toHaveBeenCalledWith([{id: '🆔-2', firstName: 'Jane', lastName: 'Doe'}]);
249
288
 
250
289
  onRowSelectionChangeSpy.mockClear();
251
290
 
252
- await user.click(screen.getByRole('row', {name: /john smith/i}));
291
+ await user.click(within(screen.getByRole('row', {name: /john smith/i})).getByRole('checkbox'));
253
292
  expect(onRowSelectionChangeSpy).toHaveBeenCalledTimes(1);
254
293
  expect(onRowSelectionChangeSpy).toHaveBeenCalledWith([
255
294
  {id: '🆔-2', firstName: 'Jane', lastName: 'Doe'},
@@ -284,7 +323,7 @@ describe('Table', () => {
284
323
 
285
324
  expect(row).toBeInTheDocument();
286
325
 
287
- await user.click(row);
326
+ await user.click(within(row).getByRole('checkbox'));
288
327
 
289
328
  expect(screen.getByRole('row', {name: /patate king/i, selected: true})).toBeInTheDocument();
290
329
 
@@ -1,5 +1,5 @@
1
1
  import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
2
- import {render, screen, userEvent} from '@test-utils';
2
+ import {render, screen, userEvent, within} from '@test-utils';
3
3
 
4
4
  import {Button} from '../../button';
5
5
  import {Table} from '../Table';
@@ -57,6 +57,7 @@ describe('Table.Actions', () => {
57
57
  const renderSpy = vi.fn().mockImplementation(() => <div />);
58
58
  render(
59
59
  <Table<RowData>
60
+ getRowId={(row) => row.name}
60
61
  data={[{name: 'fruit'}, {name: 'vegetable'}, {name: 'bread'}]}
61
62
  columns={columns}
62
63
  multiRowSelectionEnabled
@@ -66,8 +67,8 @@ describe('Table.Actions', () => {
66
67
  </Table.Header>
67
68
  </Table>
68
69
  );
69
- await user.click(screen.getByRole('cell', {name: 'fruit'}));
70
- await user.click(screen.getByRole('cell', {name: 'vegetable'}));
70
+ await user.click(within(screen.getByRole('row', {name: /fruit/})).getByRole('checkbox'));
71
+ await user.click(within(screen.getByRole('row', {name: /vegetable/})).getByRole('checkbox'));
71
72
  expect(renderSpy).toHaveBeenCalledWith([{name: 'fruit'}, {name: 'vegetable'}]);
72
73
  });
73
74
  });
@@ -1,6 +1,5 @@
1
1
  import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
2
- import {render, screen, userEvent, waitFor} from '@test-utils';
3
- import {act} from 'react-dom/test-utils';
2
+ import {render, screen, userEvent, waitFor, within} from '@test-utils';
4
3
 
5
4
  import {Table} from '../Table';
6
5
 
@@ -20,62 +19,32 @@ const basicTableWithDateRangePicker = (
20
19
  </Table>
21
20
  );
22
21
 
23
- // Since we're mocking the date and the animations are timer based we're mocking useReduceMotion to disable all the animations
24
- // I tried wrapping the components in <MantineProvider theme={{components: {Transition: {defaultProps: {duration: 0}}}}}>
25
- // but the animation was still happening. :(
26
- vi.mock('@mantine/hooks', async () => {
27
- const actual = await vi.importActual('@mantine/hooks');
28
- return {
29
- ...actual,
30
- useReduceMotion: () => true,
31
- };
32
- });
33
-
34
22
  describe('Table.DateRangePicker', () => {
35
- beforeEach(() => {
36
- vi.useFakeTimers().setSystemTime(new Date(2022, 0, 15));
37
- });
38
-
39
- afterEach(() => {
40
- vi.useRealTimers();
41
- });
42
-
43
23
  it('displays the initial dates', async () => {
44
24
  render(basicTableWithDateRangePicker);
45
25
 
46
- await waitFor(() => {
47
- expect(screen.getByText('Jan 01, 2022 - Jan 07, 2022')).toBeVisible();
48
- });
26
+ expect(screen.getByText('Jan 01, 2022 - Jan 07, 2022')).toBeVisible();
49
27
  });
50
28
 
51
29
  it('opens the dialog when clicking on the calendar button', async () => {
52
- // Otherwise, css transition is not triggered in Mantine component
53
- vi.useRealTimers();
54
- const user = userEvent.setup({delay: null});
30
+ const user = userEvent.setup();
55
31
  render(basicTableWithDateRangePicker);
56
32
 
57
- await screen.findByRole('button', {name: 'calendar'});
58
- await act(async () => {
59
- await user.click(screen.getByRole('button', {name: 'calendar'}));
60
- });
61
- expect(screen.queryByRole('dialog')).toBeVisible();
33
+ await user.click(screen.getByRole('button', {name: 'calendar'}));
34
+ expect(screen.getByRole('dialog', {name: 'calendar'})).toBeVisible();
62
35
  });
63
36
 
64
37
  it('closes the dialog when clicking back on the calendar button', async () => {
65
- // Otherwise, css transition is not triggered in Mantine component
66
- vi.useRealTimers();
67
- const user = userEvent.setup({delay: null});
38
+ const user = userEvent.setup();
68
39
  render(basicTableWithDateRangePicker);
69
40
 
70
- await screen.findByRole('button', {name: 'calendar'});
71
- await act(async () => {
72
- await user.click(screen.getByRole('button', {name: 'calendar'}));
73
- await user.click(screen.getByRole('button', {name: 'calendar'}));
74
- });
75
- expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
76
- });
41
+ await user.click(screen.getByRole('button', {name: 'calendar'}));
42
+ await user.click(screen.getByRole('button', {name: 'calendar'}));
43
+ expect(screen.queryByRole('dialog', {name: 'calendar'})).not.toBeInTheDocument();
44
+ }, 10000);
77
45
 
78
46
  it('displays the selected date range in the table', async () => {
47
+ vi.useFakeTimers().setSystemTime(new Date(2022, 0, 15));
79
48
  const user = userEvent.setup({delay: null});
80
49
  const onChange = vi.fn();
81
50
  render(
@@ -85,35 +54,33 @@ describe('Table.DateRangePicker', () => {
85
54
  onChange={onChange}
86
55
  initialState={{dateRange: [new Date(2022, 0, 1), new Date(2022, 0, 7)]}}
87
56
  >
88
- <Table.Header>
57
+ <Table.Header data-testid="table-header">
89
58
  <Table.DateRangePicker
90
59
  presets={{preset: {label: 'Preset', range: [new Date(2022, 0, 8), new Date(2022, 0, 14)]}}}
91
60
  />
92
61
  </Table.Header>
93
62
  </Table>
94
63
  );
64
+ const tableHeader = screen.getByTestId('table-header');
95
65
 
96
- await screen.findByText('Jan 01, 2022 - Jan 07, 2022');
97
- await screen.findByRole('button', {name: 'calendar'});
98
-
99
- await user.click(screen.getByRole('button', {name: 'calendar'}));
66
+ expect(within(tableHeader).getByText('Jan 01, 2022 - Jan 07, 2022')).toBeInTheDocument();
67
+ await user.click(within(tableHeader).getByRole('button', {name: 'calendar'}));
100
68
 
101
- await screen.findByRole('dialog');
69
+ const calendar = await screen.findByRole('dialog', {name: 'calendar'});
102
70
 
103
71
  // select a preset
104
- await user.click(
105
- screen.getByRole('searchbox', {
106
- name: 'Date range',
107
- })
108
- );
109
- await user.click(screen.getByRole('option', {name: 'Preset'}));
110
-
111
- await user.click(screen.getByRole('button', {name: 'Apply'}));
112
- vi.advanceTimersByTime(500);
72
+ await user.click(within(calendar).getByRole('searchbox', {name: 'Date range'}));
73
+ await user.click(within(calendar).getByRole('option', {name: 'Preset'}));
74
+ await user.click(within(calendar).getByRole('button', {name: 'Apply'}));
113
75
 
114
- await waitFor(() => expect(screen.queryByText('Jan 08, 2022 - Jan 14, 2022')).toBeVisible());
76
+ await waitFor(() => {
77
+ expect(onChange).toHaveBeenCalledTimes(1);
78
+ });
115
79
  expect(onChange).toHaveBeenCalledWith(
116
80
  expect.objectContaining({dateRange: [new Date(2022, 0, 8), new Date(2022, 0, 14)]})
117
81
  );
118
- });
82
+ expect(within(tableHeader).getByText('Jan 08, 2022 - Jan 14, 2022')).toBeInTheDocument();
83
+
84
+ vi.useRealTimers();
85
+ }, 20000);
119
86
  });
@@ -0,0 +1,97 @@
1
+ import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
2
+ import {render, screen, userEvent} from '@test-utils';
3
+ import {useState} from 'react';
4
+
5
+ import {Table} from '../Table';
6
+
7
+ type RowData = {name: string};
8
+
9
+ const columnHelper = createColumnHelper<RowData>();
10
+ const columns: Array<ColumnDef<RowData>> = [columnHelper.accessor('name', {enableSorting: false})];
11
+
12
+ describe('Table.LastUpdated', () => {
13
+ beforeEach(() => {
14
+ vi.useFakeTimers().setSystemTime(new Date(2022, 0, 15, 13, 5, 50));
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.useRealTimers();
19
+ });
20
+
21
+ it('displays the label and time', () => {
22
+ const {rerender} = render(
23
+ <Table data={[{name: 'fruit'}]} columns={columns}>
24
+ <Table.LastUpdated />
25
+ </Table>
26
+ );
27
+
28
+ expect(screen.getByText('Last update:')).toBeVisible();
29
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
30
+
31
+ rerender(
32
+ <Table data={[{name: 'fruit'}]} columns={columns}>
33
+ <Table.LastUpdated label="CUSTOM label:" />
34
+ </Table>
35
+ );
36
+
37
+ expect(screen.queryByText('Last update:')).not.toBeInTheDocument();
38
+ expect(screen.getByText('CUSTOM label:')).toBeVisible();
39
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
40
+ });
41
+
42
+ it('updates the time when a dependency changes', async () => {
43
+ const user = userEvent.setup({delay: null});
44
+
45
+ // Using a fixture to have a button that will trigger a change of a dependency
46
+ const Fixture = () => {
47
+ const [isClicked, setIsClicked] = useState(false);
48
+ return (
49
+ <>
50
+ <button onClick={() => setIsClicked(true)}>Click me</button>
51
+ <Table data={[{name: 'fruit'}]} columns={columns}>
52
+ <Table.LastUpdated dependencies={[isClicked]} />
53
+ </Table>
54
+ </>
55
+ );
56
+ };
57
+ render(<Fixture />);
58
+
59
+ expect(screen.getByText('Last update:')).toBeVisible();
60
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
61
+
62
+ vi.setSystemTime(new Date(2022, 0, 15, 14, 11, 22));
63
+
64
+ // the date changed but the dependency didn't change yet, so the timer is still the same
65
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
66
+
67
+ // When we click on the button the isClicked switch from false to true which triggers an update
68
+ await user.click(screen.getByRole('button', {name: 'Click me'}));
69
+ expect(screen.getByRole('timer')).toHaveTextContent('2:11:22 PM');
70
+ });
71
+
72
+ it('updates the time when the data changes', async () => {
73
+ const user = userEvent.setup({delay: null});
74
+ const Fixture = () => {
75
+ const [data, setData] = useState([{name: 'fruit'}]);
76
+ return (
77
+ <>
78
+ <button onClick={() => setData([{name: 'vegetable'}])}>Click me</button>
79
+ <Table data={data} columns={columns}>
80
+ <Table.LastUpdated />
81
+ </Table>
82
+ </>
83
+ );
84
+ };
85
+ render(<Fixture />);
86
+
87
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
88
+
89
+ vi.setSystemTime(new Date(2022, 0, 15, 14, 11, 22));
90
+
91
+ expect(screen.getByRole('timer')).toHaveTextContent('1:05:50 PM');
92
+
93
+ await user.click(screen.getByRole('button', {name: 'Click me'}));
94
+
95
+ expect(screen.getByRole('timer')).toHaveTextContent('2:11:22 PM');
96
+ });
97
+ });
@@ -1,5 +1,5 @@
1
1
  import {ColumnDef, createColumnHelper} from '@tanstack/table-core';
2
- import {act, render, screen, userEvent, waitFor} from '@test-utils';
2
+ import {render, screen, userEvent, waitFor} from '@test-utils';
3
3
 
4
4
  import {Table} from '../Table';
5
5
 
@@ -9,40 +9,6 @@ const columnHelper = createColumnHelper<RowData>();
9
9
  const columns: Array<ColumnDef<RowData>> = [columnHelper.accessor('name', {enableSorting: false})];
10
10
 
11
11
  describe('Table.Predicate', () => {
12
- beforeEach(() => {
13
- vi.useFakeTimers();
14
- });
15
- afterEach(() => {
16
- vi.useRealTimers();
17
- });
18
- it('displays the intial value', async () => {
19
- render(
20
- <Table
21
- data={[{name: 'fruit'}, {name: 'vegetable'}]}
22
- columns={columns}
23
- initialState={{predicates: {rank: 'second'}}}
24
- >
25
- <Table.Header>
26
- <Table.Predicate
27
- id="rank"
28
- data={[
29
- {value: 'first', label: 'First'},
30
- {value: 'second', label: 'Second'},
31
- ]}
32
- />
33
- </Table.Header>
34
- </Table>
35
- );
36
-
37
- await waitFor(() => {
38
- expect(
39
- screen.getByRole('searchbox', {
40
- name: 'rank',
41
- })
42
- ).toHaveValue('Second');
43
- });
44
- });
45
-
46
12
  it('calls onMount with the initial value', async () => {
47
13
  const onMount = vi.fn();
48
14
  render(
@@ -65,26 +31,17 @@ describe('Table.Predicate', () => {
65
31
  );
66
32
 
67
33
  await waitFor(() => {
68
- expect(
69
- screen.getByRole('searchbox', {
70
- name: 'rank',
71
- })
72
- ).toHaveValue('Second');
34
+ expect(screen.getByRole('searchbox', {name: 'rank'})).toHaveValue('Second');
73
35
  });
74
36
  expect(onMount).toHaveBeenCalledWith(expect.objectContaining({predicates: {rank: 'second'}}));
75
37
  });
76
38
 
77
39
  it('calls onChange when changing the predicate', async () => {
78
- const user = userEvent.setup({advanceTimers: vi.advanceTimersByTime});
40
+ const user = userEvent.setup();
79
41
  const onChange = vi.fn();
80
42
  render(
81
- <Table
82
- data={[{name: 'fruit'}, {name: 'vegetable'}]}
83
- columns={columns}
84
- onChange={onChange}
85
- initialState={{predicates: {rank: 'second'}}}
86
- >
87
- <Table.Header>
43
+ <Table data={[{name: 'fruit'}, {name: 'vegetable'}]} columns={columns} onChange={onChange}>
44
+ <Table.Header data-testid="table-header">
88
45
  <Table.Predicate
89
46
  id="rank"
90
47
  data={[
@@ -96,18 +53,13 @@ describe('Table.Predicate', () => {
96
53
  </Table>
97
54
  );
98
55
 
99
- expect(screen.getByRole('searchbox', {name: 'rank'})).toHaveValue('Second');
100
-
101
56
  await user.click(screen.getByRole('searchbox', {name: 'rank'}));
102
-
103
57
  await user.click(screen.getByRole('option', {name: 'First'}));
104
58
 
105
59
  expect(screen.getByRole('searchbox', {name: 'rank'})).toHaveValue('First');
106
- act(() => {
107
- vi.advanceTimersByTime(500);
60
+ await waitFor(() => {
61
+ expect(onChange).toHaveBeenCalledTimes(1);
108
62
  });
109
-
110
- expect(onChange).toHaveBeenCalledTimes(1);
111
63
  expect(onChange).toHaveBeenCalledWith(expect.objectContaining({predicates: {rank: 'first'}}));
112
64
  });
113
65
  });
@@ -1,12 +1,13 @@
1
1
  import {ListSize16Px} from '@coveord/plasma-react-icons';
2
- import {Box, Collapse, createStyles} from '@mantine/core';
2
+ import {Box, Collapse, createStyles, rem} from '@mantine/core';
3
3
  import {flexRender} from '@tanstack/react-table';
4
4
  import {defaultColumnSizing} from '@tanstack/table-core';
5
- import {Fragment} from 'react';
5
+ import {Fragment, type MouseEvent} from 'react';
6
6
  import {TableLayout, TableLayoutProps} from '../Table.types';
7
7
  import {TableCollapsibleColumn} from '../TableCollapsibleColumn';
8
8
  import {useTable} from '../TableContext';
9
9
  import {TableLoading} from '../TableLoading';
10
+ import {TableSelectableColumn} from '../TableSelectableColumn';
10
11
  import {Th} from '../Th';
11
12
 
12
13
  interface TableStylesParams {
@@ -16,13 +17,12 @@ interface TableStylesParams {
16
17
 
17
18
  const useStyles = createStyles<string, TableStylesParams>((theme, {multiRowSelectionEnabled, disableRowSelection}) => {
18
19
  const rowBackgroundColor =
19
- theme.colorScheme === 'dark'
20
- ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2)
21
- : theme.colors[theme.primaryColor][0];
20
+ theme.colorScheme === 'dark' ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2) : theme.colors.gray[1];
21
+ const border = `${rem(1)} solid ${theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[3]}`;
22
22
  return {
23
23
  headerColumns: {
24
24
  '& th:first-of-type > *': {
25
- paddingLeft: theme.spacing.xl,
25
+ paddingLeft: '40px',
26
26
  },
27
27
 
28
28
  '& input[type=checkbox]': {
@@ -60,10 +60,26 @@ const useStyles = createStyles<string, TableStylesParams>((theme, {multiRowSelec
60
60
  },
61
61
 
62
62
  row: {
63
+ '& td:first-of-type': {
64
+ paddingLeft: '40px',
65
+ },
63
66
  '&:hover': {
64
67
  backgroundColor: rowBackgroundColor,
65
68
  },
66
69
  },
70
+
71
+ cell: {
72
+ verticalAlign: 'middle',
73
+ // We must use height instead of minHeight here, otherwise it doesn't apply
74
+ height: '56px',
75
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
76
+ borderBottom: border,
77
+ },
78
+
79
+ collapsible: {
80
+ backgroundColor: rowBackgroundColor,
81
+ borderBottom: border,
82
+ },
67
83
  };
68
84
  });
69
85
 
@@ -84,14 +100,27 @@ const RowLayoutBody = <T,>({table, doubleClickAction, getExpandChildren, loading
84
100
  const {multiRowSelectionEnabled, disableRowSelection} = useTable();
85
101
  const {classes, cx} = useStyles({disableRowSelection, multiRowSelectionEnabled});
86
102
 
103
+ const toggleCollapsible = (el: HTMLTableRowElement) => {
104
+ const cell = el.children[el.children.length - 1] as HTMLTableCellElement;
105
+ cell.querySelector('button').click();
106
+ };
107
+
87
108
  const rows = table.getRowModel().rows.map((row) => {
88
109
  const rowChildren = getExpandChildren?.(row.original) ?? null;
89
110
  const isSelected = !!row.getIsSelected();
111
+ const onClick = (event: MouseEvent<HTMLTableRowElement>) => {
112
+ if (rowChildren) {
113
+ toggleCollapsible(event.currentTarget);
114
+ }
115
+ if (!disableRowSelection && !multiRowSelectionEnabled) {
116
+ row.toggleSelected();
117
+ }
118
+ };
90
119
 
91
120
  return (
92
121
  <Fragment key={row.id}>
93
122
  <tr
94
- onClick={() => (disableRowSelection ? undefined : row.toggleSelected())}
123
+ onClick={onClick}
95
124
  onDoubleClick={() => doubleClickAction?.(row.original)}
96
125
  className={cx(classes.row, {
97
126
  [classes.rowSelected]: isSelected,
@@ -102,13 +131,20 @@ const RowLayoutBody = <T,>({table, doubleClickAction, getExpandChildren, loading
102
131
  {row.getVisibleCells().map((cell) => {
103
132
  const size = cell.column.getSize();
104
133
  const width = size !== defaultColumnSizing.size ? size : undefined;
134
+ const onCollapsibleCellClick = (event: MouseEvent<HTMLTableCellElement>) => {
135
+ if (cell.column.id === TableSelectableColumn.id && !disableRowSelection) {
136
+ event.stopPropagation();
137
+ row.getToggleSelectedHandler();
138
+ }
139
+ };
105
140
  return (
106
141
  <td
107
142
  key={cell.id}
108
143
  style={{width}}
109
- className={cx({
144
+ className={cx(classes.cell, {
110
145
  [classes.rowCollapsibleButtonCell]: cell.column.id === TableCollapsibleColumn.id,
111
146
  })}
147
+ onClick={onCollapsibleCellClick}
112
148
  >
113
149
  <TableLoading visible={loading}>
114
150
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
@@ -123,12 +159,10 @@ const RowLayoutBody = <T,>({table, doubleClickAction, getExpandChildren, loading
123
159
  colSpan={table.getAllColumns().length}
124
160
  style={{
125
161
  padding: 0,
126
- borderTop: row.getIsExpanded() ? undefined : 'none',
127
- borderBottom: row.getIsExpanded() ? undefined : 'none',
128
162
  }}
129
163
  >
130
164
  <Collapse in={row.getIsExpanded()}>
131
- <Box px="sm" py="xs">
165
+ <Box className={classes.collapsible} px="sm" py="xs">
132
166
  {rowChildren}
133
167
  </Box>
134
168
  </Collapse>