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' ;
26import { DialogTrigger } from '@plone/components' ;
37import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar' ;
48import 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 }
0 commit comments