Skip to content

Commit 9db7041

Browse files
authored
feat(AnalyticalTable - useOrderedMultiSort): introduce new plugin hook (#4623)
Closes #3897
1 parent bce9d1a commit 9db7041

10 files changed

+307
-2
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,115 @@ describe('AnalyticalTable', () => {
19881988
cy.get('[data-visible-column-index="1"][data-visible-row-index="6"]').should('have.text', 25);
19891989
});
19901990

1991+
it('plugin hook: useOrderedMultiSort', () => {
1992+
const TestComponent = ({ orderedIds }: { orderedIds: string[] }) => {
1993+
const columns = [
1994+
{
1995+
Header: 'Name',
1996+
accessor: 'name',
1997+
enableMultiSort: true
1998+
},
1999+
{
2000+
Header: 'Age',
2001+
accessor: 'age',
2002+
enableMultiSort: true
2003+
},
2004+
{
2005+
Header: 'Name 2',
2006+
accessor: 'name2',
2007+
enableMultiSort: true
2008+
},
2009+
{
2010+
Header: 'Age 2',
2011+
accessor: 'age2',
2012+
enableMultiSort: true
2013+
}
2014+
];
2015+
const data = [
2016+
{ name: 'Peter', age: 40, name2: 'Alissa', age2: 18 },
2017+
{ name: 'Kristen', age: 40, name2: 'Randolph', age2: 21 },
2018+
{ name: 'Peter', age: 30, name2: 'Rose', age2: 90 },
2019+
{ name: 'Peter', age: 70, name2: 'Rose', age2: 22 },
2020+
{ name: 'Kristen', age: 60, name2: 'Willis', age2: 80 },
2021+
{ name: 'Kristen', age: 20, name2: 'Alissa', age2: 80 },
2022+
{ name: 'Graham', age: 40, name2: 'Alissa', age2: 80 },
2023+
{ name: 'Peter', age: 65, name2: 'Rose', age2: 26 },
2024+
{ name: 'Graham', age: 65, name2: 'Rose', age2: 26 },
2025+
{ name: 'Graham', age: 65, name2: 'Willis', age2: 26 },
2026+
{ name: 'Graham', age: 62, name2: 'Willis', age2: 26 }
2027+
];
2028+
return (
2029+
<AnalyticalTable
2030+
columns={columns}
2031+
data={data}
2032+
sortable
2033+
tableHooks={[AnalyticalTableHooks.useOrderedMultiSort(orderedIds)]}
2034+
/>
2035+
);
2036+
};
2037+
2038+
cy.mount(<TestComponent orderedIds={['name', 'name2', 'age', 'age2']} />);
2039+
cy.findByText('Age').click();
2040+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2041+
cy.findByText('Name').click();
2042+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2043+
2044+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Graham');
2045+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '40');
2046+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2047+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '20');
2048+
2049+
cy.findByText('Name 2').click();
2050+
cy.findByText('Sort Descending').shadow().findByRole('listitem').click({ force: true });
2051+
2052+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Graham');
2053+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '62');
2054+
cy.get('[data-visible-row-index="1"][data-visible-column-index="2"]').should('have.text', 'Willis');
2055+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2056+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '60');
2057+
cy.get('[data-visible-row-index="5"][data-visible-column-index="2"]').should('have.text', 'Willis');
2058+
2059+
cy.findByText('Name 2').click();
2060+
cy.findByText('Clear Sorting').shadow().findByRole('listitem').click({ force: true });
2061+
2062+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Graham');
2063+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '40');
2064+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2065+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '20');
2066+
2067+
cy.mount(<TestComponent orderedIds={['name2']} />);
2068+
cy.findByText('Age').click();
2069+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2070+
cy.findByText('Name').click();
2071+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2072+
2073+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2074+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '20');
2075+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Peter');
2076+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '40');
2077+
2078+
cy.findByText('Age 2').click();
2079+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2080+
2081+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2082+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '20');
2083+
cy.get('[data-visible-row-index="1"][data-visible-column-index="3"]').should('have.text', '80');
2084+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Peter');
2085+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '40');
2086+
cy.get('[data-visible-row-index="5"][data-visible-column-index="3"]').should('have.text', '18');
2087+
2088+
cy.findByText('Name 2').click();
2089+
cy.findByText('Sort Ascending').shadow().findByRole('listitem').click({ force: true });
2090+
cy.get('[data-visible-row-index="1"][data-visible-column-index="0"]').should('have.text', 'Kristen');
2091+
cy.get('[data-visible-row-index="1"][data-visible-column-index="1"]').should('have.text', '20');
2092+
cy.get('[data-visible-row-index="1"][data-visible-column-index="2"]').should('have.text', 'Alissa');
2093+
cy.get('[data-visible-row-index="1"][data-visible-column-index="3"]').should('have.text', '80');
2094+
cy.get('[data-visible-row-index="5"][data-visible-column-index="0"]').should('have.text', 'Peter');
2095+
cy.get('[data-visible-row-index="5"][data-visible-column-index="1"]').should('have.text', '30');
2096+
cy.get('[data-visible-row-index="5"][data-visible-column-index="2"]').should('have.text', 'Rose');
2097+
cy.get('[data-visible-row-index="5"][data-visible-column-index="3"]').should('have.text', '90');
2098+
});
2099+
19912100
cypressPassThroughTestsFactory(AnalyticalTable, { data, columns });
19922101
});
19932102

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,66 @@ export const PluginOnColumnResize: Story = {
335335
}
336336
};
337337

338+
const orderedMultiSortColumns = [
339+
{
340+
Header: 'Name',
341+
accessor: 'name',
342+
enableMultiSort: true
343+
},
344+
{
345+
Header: 'Age',
346+
accessor: 'age',
347+
enableMultiSort: true
348+
},
349+
{
350+
Header: 'Name 2',
351+
accessor: 'name2',
352+
enableMultiSort: true
353+
},
354+
{
355+
Header: 'Age 2',
356+
accessor: 'age2',
357+
enableMultiSort: true
358+
}
359+
];
360+
const orderedMultiSortData = [
361+
{ name: 'Peter', age: 40, name2: 'Alissa', age2: 18 },
362+
{ name: 'Kristen', age: 40, name2: 'Randolph', age2: 21 },
363+
{ name: 'Peter', age: 30, name2: 'Rose', age2: 90 },
364+
{ name: 'Peter', age: 70, name2: 'Rose', age2: 22 },
365+
{ name: 'Kristen', age: 60, name2: 'Willis', age2: 80 },
366+
{ name: 'Kristen', age: 20, name2: 'Alissa', age2: 80 },
367+
{ name: 'Graham', age: 40, name2: 'Alissa', age2: 80 },
368+
{ name: 'Peter', age: 65, name2: 'Rose', age2: 26 },
369+
{ name: 'Graham', age: 65, name2: 'Rose', age2: 26 },
370+
{ name: 'Graham', age: 65, name2: 'Willis', age2: 26 },
371+
{ name: 'Graham', age: 62, name2: 'Willis', age2: 26 }
372+
];
373+
374+
export const PluginOrderedMultiSort = {
375+
name: 'Plugin: useOrderedMultiSort',
376+
args: { orderedIds: ['name', 'name2', 'age', 'age2'] },
377+
argTypes: {
378+
orderedIds: {
379+
control: 'array',
380+
description:
381+
'Defines the sort priority when sorting by multiple columns, starting with the first column ID.\n' +
382+
'\n' +
383+
'**Note:** Column IDs that are not found in the array use the default priority, so the first sorted column has a higher priority than the next sorted column.'
384+
}
385+
},
386+
render(args) {
387+
return (
388+
<AnalyticalTable
389+
columns={orderedMultiSortColumns}
390+
data={orderedMultiSortData}
391+
sortable
392+
tableHooks={[AnalyticalTableHooks.useOrderedMultiSort(args.orderedIds)]}
393+
/>
394+
);
395+
}
396+
};
397+
338398
export const TreeTable: Story = {
339399
args: {
340400
data: dataTree,
@@ -443,7 +503,7 @@ export const DynamicRowCount: Story = {
443503
description:
444504
'Select an option to change the height of the surrounding container of the table (in `px`). <br /> __Note__: This is not an actual prop of the table.'
445505
}
446-
} as unknown,
506+
},
447507
render: (args) => {
448508
return (
449509
<div style={{ height: `${args.containerHeight}px` }}>

packages/main/src/components/AnalyticalTable/PluginDisableRowSelection.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ImportStatement } from '@sb/components/Import';
22
import { Canvas, Meta } from '@storybook/blocks';
3+
import { Footer } from '@sb/components';
34
import * as ComponentStories from './AnalyticalTable.stories';
45

56
<Meta title="Data Display / AnalyticalTable / Plugin: useRowDisableSelection" />
@@ -52,3 +53,5 @@ const data = [
5253
);
5354
};
5455
```
56+
57+
<Footer />

packages/main/src/components/AnalyticalTable/PluginIndeterminateRowSelection.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ImportStatement } from '@sb/components/Import';
22
import { Canvas, Meta } from '@storybook/blocks';
3+
import { Footer } from '@sb/components';
34
import { MessageStrip } from '../../webComponents/MessageStrip/index';
45
import * as ComponentStories from './AnalyticalTable.stories';
56

@@ -48,3 +49,5 @@ The plugin hook allows passing a callback as parameter with the following struct
4849

4950
The callback is fired, every time the internal `indeterminateRows` state is changed.
5051
The event parameter is an object, with all indeterminate rows by id (e.g. `{"0.1":true}`) and the table instance.
52+
53+
<Footer />

packages/main/src/components/AnalyticalTable/PluginManualRowSelect.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ImportStatement } from '@sb/components/Import';
22
import { Canvas, Meta } from '@storybook/blocks';
3+
import { Footer } from '@sb/components';
34
import * as ComponentStories from './AnalyticalTable.stories';
45

56
<Meta title="Data Display / AnalyticalTable / Plugin: useManualRowSelect" />
@@ -24,3 +25,5 @@ If this key is found on the original data row, and it is `true`, this row will b
2425
tableHooks={[AnalyticalTableHooks.useManualRowSelect('isSelected')]}
2526
/>
2627
```
28+
29+
<Footer />

packages/main/src/components/AnalyticalTable/PluginOnColumnResize.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ImportStatement } from '@sb/components/Import';
22
import { Canvas, Meta } from '@storybook/blocks';
3+
import { Footer } from '@sb/components';
34
import * as ComponentStories from './AnalyticalTable.stories';
45

56
<Meta title="Data Display / AnalyticalTable / Plugin: useOnColumnResize" />
@@ -39,3 +40,5 @@ const TableComponent = (props) => {
3940
);
4041
};
4142
```
43+
44+
<Footer />
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { ImportStatement } from '@sb/components/Import';
2+
import { Canvas, Meta } from '@storybook/blocks';
3+
import { ControlsWithNote, Footer } from '@sb/components';
4+
import * as ComponentStories from './AnalyticalTable.stories';
5+
6+
<Meta title="Data Display / AnalyticalTable / Plugin: useOrderedMultiSort" />
7+
8+
# AnalyticalTable Plugin: useOrderedMultiSort
9+
10+
<ImportStatement moduleName={'AnalyticalTableHooks'} packageName={'@ui5/webcomponents-react'} />
11+
12+
With the `useOrderedMultiSort` it's possible to define the sorting priority if multi-sort is enabled for the respective columns (`enableMultiSort: true`).
13+
14+
It receives one parameter that defines the sorting priority by the column id.
15+
16+
- `orderedIds: string[]`: Defines the sort priority when sorting by multiple columns, starting with the first column ID.
17+
18+
**Note:** Column IDs that are not found in the array use the default priority, so the first sorted column has a higher priority than the next sorted column.
19+
20+
## Example
21+
22+
<Canvas sourceState="none" of={ComponentStories.PluginOrderedMultiSort} />
23+
24+
### Current Parameter
25+
26+
<ControlsWithNote of={ComponentStories.PluginOrderedMultiSort} hideHTMLPropsNote include={['orderedIds']} />
27+
28+
### Code
29+
30+
```jsx
31+
const columns = [
32+
{
33+
Header: 'Name',
34+
accessor: 'name',
35+
enableMultiSort: true
36+
},
37+
{
38+
Header: 'Age',
39+
accessor: 'age',
40+
enableMultiSort: true
41+
},
42+
{
43+
Header: 'Name 2',
44+
accessor: 'name2',
45+
enableMultiSort: true
46+
},
47+
{
48+
Header: 'Age 2',
49+
accessor: 'age2',
50+
enableMultiSort: true
51+
}
52+
];
53+
const data = [
54+
{ name: 'Peter', age: 40, name2: 'Alissa', age2: 18 },
55+
{ name: 'Kristen', age: 40, name2: 'Randolph', age2: 21 },
56+
{ name: 'Peter', age: 30, name2: 'Rose', age2: 90 },
57+
{ name: 'Peter', age: 70, name2: 'Rose', age2: 22 },
58+
{ name: 'Kristen', age: 60, name2: 'Willis', age2: 80 },
59+
{ name: 'Kristen', age: 20, name2: 'Alissa', age2: 80 },
60+
{ name: 'Graham', age: 40, name2: 'Alissa', age2: 80 },
61+
{ name: 'Peter', age: 65, name2: 'Rose', age2: 26 },
62+
{ name: 'Graham', age: 65, name2: 'Rose', age2: 26 },
63+
{ name: 'Graham', age: 65, name2: 'Willis', age2: 26 },
64+
{ name: 'Graham', age: 62, name2: 'Willis', age2: 26 }
65+
];
66+
const orderedIds = ['name', 'name2', 'age', 'age2'];
67+
const TableComponent = () => {
68+
return (
69+
<AnalyticalTable
70+
columns={columns}
71+
data={data}
72+
sortable
73+
tableHooks={[AnalyticalTableHooks.useOrderedMultiSort(orderedIds)]}
74+
/>
75+
);
76+
};
77+
```
78+
79+
<Footer />

packages/main/src/components/AnalyticalTable/Recipes.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TableOfContent } from '@sb/components';
22
import { Meta } from '@storybook/blocks';
3+
import { Footer } from '@sb/components';
34
import { MessageStrip } from '../../webComponents/index';
45

56
<Meta title="Data Display / AnalyticalTable / Recipes" />
@@ -157,3 +158,5 @@ Please note that the internal react-table is resetting its hidden state after hi
157158
}}
158159
/>
159160
```
161+
162+
<Footer />
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { useIndeterminateRowSelection } from './useIndeterminateRowSelection.js';
22
import { useManualRowSelect } from './useManualRowSelect.js';
33
import { useOnColumnResize } from './useOnColumnResize.js';
4+
import { useOrderedMultiSort } from './useOrderedMultiSort.js';
45
import { useRowDisableSelection } from './useRowDisableSelection.js';
56

6-
export { useIndeterminateRowSelection, useManualRowSelect, useOnColumnResize, useRowDisableSelection };
7+
export {
8+
useIndeterminateRowSelection,
9+
useManualRowSelect,
10+
useOnColumnResize,
11+
useOrderedMultiSort,
12+
useRowDisableSelection
13+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* A plugin hook for defining the sort priority when sorting by multiple columns, starting with the first column ID.
3+
*
4+
* **Note:** Column IDs that are not found in the array use the default priority, so the first sorted column has a higher priority than the next sorted column.
5+
*
6+
* @param {string[]} orderedIds - Array of column IDs, defining the sorting priority.
7+
*/
8+
export const useOrderedMultiSort = (orderedIds: string[]) => {
9+
const useOrderedMultiSortPlugin = (hooks) => {
10+
hooks.stateReducers.push((newState, action) => {
11+
if (action.type === 'toggleSortBy') {
12+
if (newState.sortBy.length <= 1) {
13+
return newState;
14+
} else {
15+
const clonedSortBy = [...newState.sortBy];
16+
const updatedSortBy = orderedIds.flatMap((sortedId) => {
17+
const sortedItemIndex = clonedSortBy.findIndex((item) => item.id === sortedId);
18+
if (sortedItemIndex !== -1) {
19+
const sortedEntry = clonedSortBy[sortedItemIndex];
20+
clonedSortBy.splice(sortedItemIndex, 1);
21+
return [sortedEntry];
22+
}
23+
return [];
24+
});
25+
return { ...newState, sortBy: [...updatedSortBy, ...clonedSortBy] };
26+
}
27+
}
28+
return newState;
29+
});
30+
};
31+
32+
useOrderedMultiSortPlugin.pluginName = 'useOrderedMultiSort';
33+
34+
return useOrderedMultiSortPlugin;
35+
};

0 commit comments

Comments
 (0)