Skip to content

Commit

Permalink
DataViews: Adds a control to the views actions to switch layouts (#55311
Browse files Browse the repository at this point in the history
)
  • Loading branch information
youknowriad authored Oct 12, 2023
1 parent 90007e3 commit f4d830b
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 256 deletions.
199 changes: 21 additions & 178 deletions packages/edit-site/src/components/dataviews/dataviews.js
Original file line number Diff line number Diff line change
@@ -1,211 +1,54 @@
/**
* External dependencies
*/
import {
getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
getPaginationRowModel,
useReactTable,
} from '@tanstack/react-table';

/**
* WordPress dependencies
*/
import {
__experimentalVStack as VStack,
__experimentalHStack as HStack,
VisuallyHidden,
DropdownMenu,
MenuGroup,
MenuItem,
} from '@wordpress/components';
import { useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import ListView from './list-view';
import { Pagination } from './pagination';
import ViewList from './view-list';
import Pagination from './pagination';
import ViewActions from './view-actions';
import TextFilter from './text-filter';
import { moreVertical } from '@wordpress/icons';

const EMPTY_OBJECT = {};
import { ViewGrid } from './view-grid';

export default function DataViews( {
actions,
data,
fields,
view,
onChangeView,
isLoading,
fields,
actions,
data,
isLoading = false,
paginationInfo,
options: { pageCount },
} ) {
const columns = useMemo( () => {
const _columns = [ ...fields ];
if ( actions?.length ) {
_columns.push( {
header: <VisuallyHidden>{ __( 'Actions' ) }</VisuallyHidden>,
id: 'actions',
cell: ( props ) => {
return (
<DropdownMenu
icon={ moreVertical }
label={ __( 'Actions' ) }
>
{ () => (
<MenuGroup>
{ actions.map( ( action ) => (
<MenuItem
key={ action.id }
onClick={ () =>
action.perform(
props.row.original
)
}
isDestructive={
action.isDesctructive
}
>
{ action.label }
</MenuItem>
) ) }
</MenuGroup>
) }
</DropdownMenu>
);
},
enableHiding: false,
} );
}

return _columns;
}, [ fields, actions ] );

const columnVisibility = useMemo( () => {
if ( ! view.hiddenFields?.length ) {
return;
}
return view.hiddenFields.reduce(
( accumulator, fieldId ) => ( {
...accumulator,
[ fieldId ]: false,
} ),
{}
);
}, [ view.hiddenFields ] );

const dataView = useReactTable( {
data,
columns,
manualSorting: true,
manualFiltering: true,
manualPagination: true,
enableRowSelection: true,
state: {
sorting: view.sort
? [
{
id: view.sort.field,
desc: view.sort.direction === 'desc',
},
]
: [],
globalFilter: view.search,
pagination: {
pageIndex: view.page,
pageSize: view.perPage,
},
columnVisibility: columnVisibility ?? EMPTY_OBJECT,
},
onSortingChange: ( sortingUpdater ) => {
onChangeView( ( currentView ) => {
const sort =
typeof sortingUpdater === 'function'
? sortingUpdater(
currentView.sort
? [
{
id: currentView.sort.field,
desc:
currentView.sort
.direction === 'desc',
},
]
: []
)
: sortingUpdater;
if ( ! sort.length ) {
return {
...currentView,
sort: {},
};
}
const [ { id, desc } ] = sort;
return {
...currentView,
sort: { field: id, direction: desc ? 'desc' : 'asc' },
};
} );
},
onColumnVisibilityChange: ( columnVisibilityUpdater ) => {
onChangeView( ( currentView ) => {
const hiddenFields = Object.entries(
columnVisibilityUpdater()
).reduce(
( accumulator, [ fieldId, value ] ) => {
if ( value ) {
return accumulator.filter(
( id ) => id !== fieldId
);
}
return [ ...accumulator, fieldId ];
},
[ ...( currentView.hiddenFields || [] ) ]
);
return {
...currentView,
hiddenFields,
};
} );
},
onGlobalFilterChange: ( value ) => {
onChangeView( { ...view, search: value, page: 0 } );
},
onPaginationChange: ( paginationUpdater ) => {
onChangeView( ( currentView ) => {
const { pageIndex, pageSize } = paginationUpdater( {
pageIndex: currentView.page,
pageSize: currentView.perPage,
} );
return { ...view, page: pageIndex, perPage: pageSize };
} );
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
pageCount,
} );
const ViewComponent = view.type === 'list' ? ViewList : ViewGrid;
return (
<div className="dataviews-wrapper">
<VStack spacing={ 4 }>
<HStack justify="space-between">
<TextFilter onChange={ dataView.setGlobalFilter } />
<TextFilter view={ view } onChangeView={ onChangeView } />
<ViewActions
fields={ fields }
view={ view }
onChangeView={ onChangeView }
/>
</HStack>
{ /* This component will be selected based on viewConfigs. Now we only have the list view. */ }
<ListView dataView={ dataView } isLoading={ isLoading } />
<ViewComponent
fields={ fields }
view={ view }
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
actions={ actions }
data={ data }
isLoading={ isLoading }
/>
<Pagination
dataView={ dataView }
totalItems={ paginationInfo?.totalItems }
view={ view }
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
/>
</VStack>
</div>
Expand Down
1 change: 0 additions & 1 deletion packages/edit-site/src/components/dataviews/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { default as DataViews } from './dataviews';
export { PAGE_SIZE_VALUES } from './view-actions';
91 changes: 64 additions & 27 deletions packages/edit-site/src/components/dataviews/pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,54 @@ import {
__experimentalHStack as HStack,
__experimentalText as Text,
__experimentalNumberControl as NumberControl,
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
SelectControl,
} from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { sprintf, __, _x, _n } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { PageSizeControl } from './view-actions';
const PAGE_SIZE_VALUES = [ 5, 20, 50 ];
function PageSizeControl( { view, onChangeView } ) {
const label = __( 'Rows per page:' );
return (
<SelectControl
__nextHasNoMarginBottom
label={ label }
hideLabelFromVision
// TODO: This should probably use a label based on the wanted design
// and we could remove InputControlPrefixWrapper usage.
prefix={
<InputControlPrefixWrapper
as="span"
className="dataviews__per-page-control-prefix"
>
{ label }
</InputControlPrefixWrapper>
}
value={ view.perPage }
options={ PAGE_SIZE_VALUES.map( ( pageSize ) => ( {
value: pageSize,
label: pageSize,
} ) ) }
onChange={ ( value ) =>
onChangeView( { ...view, perPage: value } )
}
/>
);
}

// For now this is copied from the patterns list Pagination component, because
// the datatable pagination starts from index zero(`0`). Eventually all lists will be
// using this one.
export function Pagination( {
dataView,
// If passed, use it, as it's for controlled pagination.
totalItems = 0,
function Pagination( {
view,
onChangeView,
paginationInfo: { totalItems = 0, totalPages },
} ) {
const currentPage = dataView.getState().pagination.pageIndex + 1;
const numPages = dataView.getPageCount();
const _totalItems = totalItems || dataView.getCoreRowModel().rows.length;
const currentPage = view.page + 1;
if ( ! totalItems || ! totalPages ) {
return null;
}
return (
<HStack
expanded={ false }
Expand All @@ -38,25 +66,27 @@ export function Pagination( {
// translators: %s: Total number of entries.
sprintf(
// translators: %s: Total number of entries.
_n( '%s item', '%s items', _totalItems ),
_totalItems
_n( '%s item', '%s items', totalItems ),
totalItems
)
}
</Text>
{ !! _totalItems && (
{ !! totalItems && (
<HStack expanded={ false } spacing={ 1 }>
<Button
variant="tertiary"
onClick={ () => dataView.setPageIndex( 0 ) }
disabled={ ! dataView.getCanPreviousPage() }
onClick={ () => onChangeView( { ...view, page: 0 } ) }
disabled={ view.page === 0 }
aria-label={ __( 'First page' ) }
>
«
</Button>
<Button
variant="tertiary"
onClick={ () => dataView.previousPage() }
disabled={ ! dataView.getCanPreviousPage() }
onClick={ () =>
onChangeView( { ...view, page: view.page - 1 } )
}
disabled={ view.page === 0 }
aria-label={ __( 'Previous page' ) }
>
Expand All @@ -71,17 +101,20 @@ export function Pagination( {
// translators: %1$s: Current page number, %2$s: Total number of pages.
_x( '<CurrenPageControl /> of %2$s', 'paging' ),
currentPage,
numPages
totalPages
),
{
CurrenPageControl: (
<NumberControl
aria-label={ __( 'Current page' ) }
min={ 1 }
max={ numPages }
max={ totalPages }
onChange={ ( value ) => {
if ( value > numPages ) return;
dataView.setPageIndex( value - 1 );
if ( value > totalPages ) return;
onChangeView( {
...view,
page: view.page - 1,
} );
} }
step="1"
value={ currentPage }
Expand All @@ -94,25 +127,29 @@ export function Pagination( {
</HStack>
<Button
variant="tertiary"
onClick={ () => dataView.nextPage() }
disabled={ ! dataView.getCanNextPage() }
onClick={ () =>
onChangeView( { ...view, page: view.page + 1 } )
}
disabled={ view.page >= totalPages - 1 }
aria-label={ __( 'Next page' ) }
>
</Button>
<Button
variant="tertiary"
onClick={ () =>
dataView.setPageIndex( dataView.getPageCount() - 1 )
onChangeView( { ...view, page: totalPages - 1 } )
}
disabled={ ! dataView.getCanNextPage() }
disabled={ view.page >= totalPages - 1 }
aria-label={ __( 'Last page' ) }
>
»
</Button>
</HStack>
) }
<PageSizeControl dataView={ dataView } />
<PageSizeControl view={ view } onChangeView={ onChangeView } />
</HStack>
);
}

export default Pagination;
Loading

1 comment on commit f4d830b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in f4d830b.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6501683686
📝 Reported issues:

Please sign in to comment.