From 1966da03df6e660ecb907fa1f60a145e6690ad4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Mon, 8 Sep 2025 16:59:10 +0200 Subject: [PATCH 1/3] Implement groupBy for list layout --- .../src/dataviews-layouts/list/index.tsx | 78 ++++++++++++++++++- .../src/dataviews-layouts/list/style.scss | 8 ++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index 94db0c120e28e7..e55f5abe4ad5b8 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -24,7 +24,7 @@ import { useState, useContext, } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; import { useRegistry } from '@wordpress/data'; @@ -517,6 +517,82 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { ); } + // Get the group field if specified + const groupField = view.groupByField + ? fields.find( ( field ) => field.id === view.groupByField ) + : null; + + // Group data by groupByField if specified + const dataByGroup = groupField + ? data.reduce( ( groups: Map< string, typeof data >, item ) => { + const groupName = groupField.getValue( { item } ); + if ( ! groups.has( groupName ) ) { + groups.set( groupName, [] ); + } + groups.get( groupName )?.push( item ); + return groups; + }, new Map< string, typeof data >() ) + : null; + + // Render data grouped by field + if ( hasData && groupField && dataByGroup ) { + return ( + } + className="dataviews-view-list__group" + role="grid" + activeId={ activeCompositeId } + setActiveId={ setActiveCompositeId } + > + + { Array.from( dataByGroup.entries() ).map( + ( [ groupName, groupItems ] ) => ( + +

+ { sprintf( + // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022". + __( '%1$s: %2$s' ), + groupField.label, + groupName + ) } +

+ { groupItems.map( ( item ) => { + const id = + generateCompositeItemIdPrefix( item ); + return ( + + ); + } ) } +
+ ) + ) } +
+
+ ); + } + + // Render ungrouped data return ( <> Date: Mon, 8 Sep 2025 17:07:13 +0200 Subject: [PATCH 2/3] Update changelog --- packages/dataviews/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 0d3ad10ecc801d..b44bcc945de345 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -14,6 +14,7 @@ - Dataform: Add new `url` field type and field control. [#71518](https://github.com/WordPress/gutenberg/pull/71518) - Dataform: Add new `password` field type and field control. [#71545](https://github.com/WordPress/gutenberg/pull/71545) - DataForm: Add a textarea control for use with the `text` field type ([#71495](https://github.com/WordPress/gutenberg/pull/71495)) +- DataViews: support groupBy in the list layout. [#71548](https://github.com/WordPress/gutenberg/pull/71548) ### Bug Fixes From 2a948eddb031850b04e5fcd6c7ef173cd9ba9f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:06:00 +0200 Subject: [PATCH 3/3] Extract to getDataByGroup --- .../src/dataviews-layouts/grid/index.tsx | 15 ++------------- .../src/dataviews-layouts/list/index.tsx | 15 ++------------- .../dataviews-layouts/picker-grid/index.tsx | 14 ++------------ .../src/dataviews-layouts/table/index.tsx | 15 ++------------- .../utils/get-data-by-group.ts | 18 ++++++++++++++++++ 5 files changed, 26 insertions(+), 51 deletions(-) create mode 100644 packages/dataviews/src/dataviews-layouts/utils/get-data-by-group.ts diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index 23f4265fd58c21..6970146e145dcb 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -42,6 +42,7 @@ import type { SetSelection } from '../../private-types'; import { ItemClickWrapper } from '../utils/item-click-wrapper'; import { GridItems } from '../utils/grid-items'; const { Badge } = unlock( componentsPrivateApis ); +import getDataByGroup from '../utils/get-data-by-group'; interface GridItemProps< Item > { view: ViewGridType; @@ -338,19 +339,7 @@ function ViewGrid< Item >( { const groupField = view.groupByField ? fields.find( ( f ) => f.id === view.groupByField ) : null; - - // Group data by groupByField if specified - const dataByGroup = groupField - ? data.reduce( ( groups: Map< string, typeof data >, item ) => { - const groupName = groupField.getValue( { item } ); - if ( ! groups.has( groupName ) ) { - groups.set( groupName, [] ); - } - groups.get( groupName )?.push( item ); - return groups; - }, new Map< string, typeof data >() ) - : null; - + const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null; const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup; return ( diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index e55f5abe4ad5b8..6951cf5b6f1b59 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -44,6 +44,7 @@ import type { ViewListProps, ActionModal as ActionModalType, } from '../../types'; +import getDataByGroup from '../utils/get-data-by-group'; interface ListViewItemProps< Item > { view: ViewListType; @@ -517,22 +518,10 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { ); } - // Get the group field if specified const groupField = view.groupByField ? fields.find( ( field ) => field.id === view.groupByField ) : null; - - // Group data by groupByField if specified - const dataByGroup = groupField - ? data.reduce( ( groups: Map< string, typeof data >, item ) => { - const groupName = groupField.getValue( { item } ); - if ( ! groups.has( groupName ) ) { - groups.set( groupName, [] ); - } - groups.get( groupName )?.push( item ); - return groups; - }, new Map< string, typeof data >() ) - : null; + const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null; // Render data grouped by field if ( hasData && groupField && dataByGroup ) { diff --git a/packages/dataviews/src/dataviews-layouts/picker-grid/index.tsx b/packages/dataviews/src/dataviews-layouts/picker-grid/index.tsx index 1e645822ef7823..823a46c729fcb4 100644 --- a/packages/dataviews/src/dataviews-layouts/picker-grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/picker-grid/index.tsx @@ -35,6 +35,7 @@ import type { import type { SetSelection } from '../../private-types'; import { GridItems } from '../utils/grid-items'; const { Badge } = unlock( componentsPrivateApis ); +import getDataByGroup from '../utils/get-data-by-group'; interface GridItemProps< Item > { view: ViewPickerGridType; @@ -301,18 +302,7 @@ function ViewPickerGrid< Item >( { const groupField = view.groupByField ? fields.find( ( f ) => f.id === view.groupByField ) : null; - - // Group data by groupByField if specified - const dataByGroup = groupField - ? data.reduce( ( groups: Map< string, typeof data >, item ) => { - const groupName = groupField.getValue( { item } ); - if ( ! groups.has( groupName ) ) { - groups.set( groupName, [] ); - } - groups.get( groupName )?.push( item ); - return groups; - }, new Map< string, typeof data >() ) - : null; + const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null; const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup; diff --git a/packages/dataviews/src/dataviews-layouts/table/index.tsx b/packages/dataviews/src/dataviews-layouts/table/index.tsx index 0497737eacf4e8..1e8b7ea9953541 100644 --- a/packages/dataviews/src/dataviews-layouts/table/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/index.tsx @@ -40,6 +40,7 @@ import type { SetSelection } from '../../private-types'; import ColumnHeaderMenu from './column-header-menu'; import ColumnPrimary from './column-primary'; import { useIsHorizontalScrollEnd } from './use-is-horizontal-scroll-end'; +import getDataByGroup from '../utils/get-data-by-group'; interface TableColumnFieldProps< Item > { fields: NormalizedField< Item >[]; @@ -336,22 +337,10 @@ function ViewTable< Item >( { ( field ) => field.id === view.descriptionField ); - // Get group field if groupByField is specified const groupField = view.groupByField ? fields.find( ( f ) => f.id === view.groupByField ) : null; - - // Group data by groupByField if specified - const dataByGroup = groupField - ? data.reduce( ( groups: Map< string, typeof data >, item ) => { - const groupName = groupField.getValue( { item } ); - if ( ! groups.has( groupName ) ) { - groups.set( groupName, [] ); - } - groups.get( groupName )?.push( item ); - return groups; - }, new Map< string, typeof data >() ) - : null; + const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null; const { showTitle = true, showMedia = true, showDescription = true } = view; const hasPrimaryColumn = ( titleField && showTitle ) || diff --git a/packages/dataviews/src/dataviews-layouts/utils/get-data-by-group.ts b/packages/dataviews/src/dataviews-layouts/utils/get-data-by-group.ts new file mode 100644 index 00000000000000..22c5bf741c261a --- /dev/null +++ b/packages/dataviews/src/dataviews-layouts/utils/get-data-by-group.ts @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import type { NormalizedField } from '../../types'; + +export default function getDataByGroup< Item >( + data: any[], + groupByField: NormalizedField< Item > +): Map< string, any[] > { + return data.reduce( ( groups: Map< string, typeof data >, item ) => { + const groupName = groupByField.getValue( { item } ); + if ( ! groups.has( groupName ) ) { + groups.set( groupName, [] ); + } + groups.get( groupName )?.push( item ); + return groups; + }, new Map< string, typeof data >() ); +}