Skip to content

Commit 1434903

Browse files
committed
use Table component from react-aria-components (from 'catalog:') & sorting update
1 parent 41a1c8e commit 1434903

4 files changed

Lines changed: 138 additions & 99 deletions

File tree

backend/src/kitconcept/keywordmanager/services/get.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def reply(self):
2424
reverse = sort_order == "descending"
2525

2626
sort_keys = {
27-
"alphabetical": lambda x: x[0].lower(),
28-
"frequency": lambda x: x[1],
27+
"keyword": lambda x: x[0].lower(),
28+
"occurrence": lambda x: x[1],
2929
}
3030

3131
if sort_on := self.request.form.get("sort_on"):

frontend/packages/volto-keywordmanager/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
},
2929
"addons": [],
3030
"theme": "",
31-
"dependencies": {},
31+
"dependencies": {
32+
"react-aria-components": "catalog:"
33+
},
3234
"peerDependencies": {
3335
"@plone/components": "^4.1.0",
3436
"react": "^18.2.0",

frontend/packages/volto-keywordmanager/src/components/KeywordManager.tsx

Lines changed: 110 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { Spinner, Table, Button, Select } from '@plone/components';
1+
import type { SortDescriptor, Selection } from 'react-aria-components';
2+
import { Table, TableHeader, TableBody } from 'react-aria-components';
3+
import { Column, Row, Cell, Collection } from 'react-aria-components';
4+
import { Checkbox } from 'react-aria-components';
5+
import { Spinner, Button, Select } from '@plone/components';
26
import { DialogTrigger } from '@plone/components';
37
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
48
import Error from '@plone/volto/components/theme/Error/Error';
@@ -61,9 +65,7 @@ const KeywordManager = (props) => {
6165
const pathname = location.pathname;
6266
// selectedKeys can become the string 'all' when the user selects all rows
6367
// (e.g. via the header checkbox), rather than a Set of individual keys
64-
const [selectedKeys, setSelectedKeys] = useState<string | Set<string>>(
65-
new Set(),
66-
);
68+
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set());
6769
const selectionCount =
6870
selectedKeys === 'all' ? keywords.items?.length : selectedKeys?.size;
6971
const [keywordIndex, setKeywordIndex] = useState<string>('Subject');
@@ -72,8 +74,9 @@ const KeywordManager = (props) => {
7274
const [pageSize, setPageSize] = useState<number>(25);
7375
const pageSizes = [25, 50, 100];
7476
// Sorting
75-
const [sortOn, setSortOn] = useState<string>('');
76-
const [sortOrder, setSortOrder] = useState<string>('');
77+
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>();
78+
const sortOn = sortDescriptor?.column;
79+
const sortOrder = sortDescriptor?.direction;
7780
const [isLoading, setIsLoading] = useState<boolean>(false);
7881

7982
const options = useMemo(
@@ -92,21 +95,62 @@ const KeywordManager = (props) => {
9295
dispatch(getKeywordIndexes());
9396
}, [dispatch, options]);
9497

95-
const handleSorting = (value: string) => {
96-
if (sortOn !== value) {
97-
setSortOn(value);
98-
setSortOrder('ascending');
99-
return;
100-
}
101-
if (value === 'alphabetical') setSortOn(value);
102-
if (value === 'frequency') setSortOn(value);
103-
if (!sortOrder) {
104-
setSortOrder('ascending');
105-
} else if (sortOrder === 'ascending') {
106-
setSortOrder('descending');
107-
} else if (sortOrder === 'descending') {
108-
setSortOrder('');
109-
setSortOn('');
98+
const columns = [
99+
{
100+
id: 'keyword',
101+
name: (
102+
<>
103+
{intl.formatMessage(messages.keyword)}
104+
<Icon name={sortDownSVG} size="16px" ariaHidden="true" />
105+
</>
106+
),
107+
isRowHeader: true,
108+
allowsSorting: true,
109+
},
110+
{
111+
id: 'occurrence',
112+
name: (
113+
<>
114+
{intl.formatMessage(messages.occurrence)}
115+
<Icon name={sortDownSVG} size="16px" ariaHidden="true" />
116+
</>
117+
),
118+
allowsSorting: true,
119+
},
120+
{
121+
id: 'actions',
122+
name: intl.formatMessage(messages.actions),
123+
},
124+
];
125+
126+
const rows = keywords.items?.map((kw) => ({
127+
id: kw.name,
128+
textValue: kw.name,
129+
keyword: kw.name,
130+
occurrence: kw.total,
131+
actions: (
132+
<div>
133+
<UniversalLink
134+
href={`${pathname}/${keywordIndex}/${kw.name}`}
135+
openLinkInNewTab={true}
136+
>
137+
<Icon name={showSVG} size="20px" />
138+
</UniversalLink>
139+
<Button onPress={() => handleDeleteKeywords(kw.name)}>
140+
<Icon name={trashSVG} size="20px" />
141+
</Button>
142+
</div>
143+
),
144+
}));
145+
146+
const handleSortChange = (newDescriptor: SortDescriptor) => {
147+
if (
148+
newDescriptor.column === sortDescriptor?.column &&
149+
sortDescriptor?.direction === 'descending'
150+
) {
151+
setSortDescriptor(undefined);
152+
} else {
153+
setSortDescriptor(newDescriptor);
110154
}
111155
};
112156

@@ -242,77 +286,51 @@ const KeywordManager = (props) => {
242286
</div>
243287
</div>
244288
</div>
245-
{keywords.loaded ? (
246-
<Table
247-
className="react-aria-Table cmsui-table"
248-
columns={[
249-
{
250-
id: 'keyword',
251-
name: (
252-
<div>
253-
<p>{intl.formatMessage(messages.keyword)}</p>
254-
<Button onPress={() => handleSorting('alphabetical')}>
255-
<Icon
256-
name={
257-
sortOn === 'alphabetical' && sortOrder === 'ascending'
258-
? sortDownSVG
259-
: sortUpSVG
260-
}
261-
size="20px"
262-
/>
263-
</Button>
264-
</div>
265-
),
266-
isRowHeader: true,
267-
},
268-
{
269-
id: 'occurrence',
270-
name: (
271-
<div>
272-
<p>{intl.formatMessage(messages.occurrence)}</p>
273-
<Button onPress={() => handleSorting('frequency')}>
274-
<Icon
275-
name={
276-
sortOn === 'frequency' && sortOrder === 'ascending'
277-
? sortDownSVG
278-
: sortUpSVG
279-
}
280-
size="20px"
281-
/>
282-
</Button>
283-
</div>
284-
),
285-
},
286-
{
287-
id: 'actions',
288-
name: <p>{intl.formatMessage(messages.actions)}</p>,
289-
},
290-
]}
291-
rows={keywords.items?.map((kw) => ({
292-
id: kw.name,
293-
textValue: kw.name,
294-
keyword: kw.name,
295-
occurrence: kw.total,
296-
actions: (
297-
<div>
298-
<UniversalLink
299-
href={`${pathname}/${keywordIndex}/${kw.name}`}
300-
openLinkInNewTab={true}
301-
>
302-
<Icon name={showSVG} size="20px" />
303-
</UniversalLink>
304-
<Button onPress={() => handleDeleteKeywords(kw.name)}>
305-
<Icon name={trashSVG} size="20px" />
306-
</Button>
307-
</div>
308-
),
309-
}))}
310-
selectionMode="multiple"
311-
onSelectionChange={setSelectedKeys}
312-
/>
313-
) : (
314-
<Spinner label={intl.formatMessage(messages.loading)} />
315-
)}
289+
<Table
290+
className="react-aria-Table cmsui-table"
291+
selectionMode="multiple"
292+
selectedKeys={selectedKeys}
293+
onSelectionChange={setSelectedKeys}
294+
sortDescriptor={sortDescriptor}
295+
onSortChange={handleSortChange}
296+
>
297+
<TableHeader columns={columns}>
298+
<Column>
299+
<Checkbox slot="selection" />
300+
</Column>
301+
<Collection items={columns}>
302+
{(column) => (
303+
<Column
304+
isRowHeader={column.isRowHeader}
305+
allowsSorting={column.allowsSorting}
306+
>
307+
{column.name}
308+
</Column>
309+
)}
310+
</Collection>
311+
</TableHeader>
312+
<TableBody
313+
items={rows}
314+
renderEmptyState={() =>
315+
keywords.loading ? (
316+
<Spinner aria-label={intl.formatMessage(messages.loading)} />
317+
) : (
318+
'No results found.'
319+
)
320+
}
321+
>
322+
{(item) => (
323+
<Row columns={columns} textValue={item.textValue}>
324+
<Cell>
325+
<Checkbox slot="selection" />
326+
</Cell>
327+
<Collection items={columns}>
328+
{(column) => <Cell>{item[column.id]}</Cell>}
329+
</Collection>
330+
</Row>
331+
)}
332+
</TableBody>
333+
</Table>
316334
{keywords?.items_total > Math.min(...pageSizes) && (
317335
<Pagination
318336
current={currentPage}

frontend/pnpm-lock.yaml

Lines changed: 23 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)