Skip to content

Conversation

oandregal
Copy link
Member

@oandregal oandregal commented Jul 17, 2025

Related #57967

What

Adds groupByField property to the view API, and implements grouping functionality in grid layout.

When the groupByField is specified, the data is grouped visually by the field value and displayed with group headers. Each group maintains the same grid layout structure while being visually separated.

Why

It's a feature we want to support in different layouts.

How

View API:

{
  type: 'grid',
  sort: {
    field: '...'
  },
  groupByField: '...' // NEW PROPERTY
}
Screen.Recording.2025-07-18.at.11.29.04.mov

Changes:

  • Add groupByField property to ViewBase type.
  • Implement grouping logic in grid layout component.
  • Add styling for group headers.
  • Add GroupedGridLayout story to demonstrate the feature.
  • Implement sorting by groupByField in filterSortAndPaginate.

TODO

  • Define how "sort" and "group by" should work together (see and see).
  • Consider the relationship with fields, titleField, etc. (see).

Potential follow-ups, in case there's any interest:

  • Allow fields to provide a groupBy function. This would enable custom groupings. For example, when grouping by date, some scenarios may want to group by "yesterday/last week/last month" rather than the specific field date. (see).
  • Allow users to change how and whether the data is grouped by.
  • Support "group by" in other layouts: table, list.

Testing Instructions

npm install && npm run storybook:dev

In the opened storybook, visit the "Grouped Grid Layout" story.

@oandregal oandregal self-assigned this Jul 17, 2025
@oandregal oandregal added [Status] In Progress Tracking issues with work in progress [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Type] Feature New feature to highlight in changelogs. labels Jul 17, 2025
@oandregal
Copy link
Member Author

oandregal commented Jul 17, 2025

If the data is not sorted by the same field that creates the groups, multiple groups will be created across pages (see video below). It sounds like we should use the groupByField for sorting if it is present. "group by" is a higher-level "sort by" that also groups the data visually — sort is contained in groupBy. How do we surface this at the UI level?

Screen.Recording.2025-07-17.at.09.43.04.mov

Copy link

github-actions bot commented Jul 17, 2025

Size Change: +351 B (+0.02%)

Total Size: 1.89 MB

Filename Size Change
build/edit-site/index.min.js 227 kB +290 B (+0.13%)
build/edit-site/posts-rtl.css 8.5 kB +18 B (+0.21%)
build/edit-site/posts.css 8.51 kB +17 B (+0.2%)
build/edit-site/style-rtl.css 14.6 kB +14 B (+0.1%)
build/edit-site/style.css 14.6 kB +12 B (+0.08%)
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 447 B
build-module/block-library/form/view.min.js 533 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.19 kB
build-module/block-library/query/view.min.js 767 B
build-module/block-library/search/view.min.js 639 B
build-module/interactivity-router/full-page.min.js 565 B
build-module/interactivity-router/index.min.js 11.4 kB
build-module/interactivity/debug.min.js 17.5 kB
build-module/interactivity/index.min.js 13.9 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.26 kB
build/api-fetch/index.min.js 2.4 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.18 kB
build/block-directory/style-rtl.css 1.03 kB
build/block-directory/style.css 1.03 kB
build/block-editor/content-rtl.css 4.43 kB
build/block-editor/content.css 4.42 kB
build/block-editor/default-editor-styles-rtl.css 392 B
build/block-editor/default-editor-styles.css 392 B
build/block-editor/index.min.js 264 kB
build/block-editor/style-rtl.css 16 kB
build/block-editor/style.css 16 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 61 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 554 B
build/block-library/blocks/button/style.css 554 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 349 B
build/block-library/blocks/buttons/style.css 349 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 191 B
build/block-library/blocks/comment-template/style.css 191 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 168 B
build/block-library/blocks/comments-pagination/editor.css 168 B
build/block-library/blocks/comments-pagination/style-rtl.css 201 B
build/block-library/blocks/comments-pagination/style.css 201 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 842 B
build/block-library/blocks/comments/editor.css 842 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 278 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 349 B
build/block-library/blocks/form-input/style.css 349 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/freeform/editor-rtl.css 2.59 kB
build/block-library/blocks/freeform/editor.css 2.59 kB
build/block-library/blocks/gallery/editor-rtl.css 615 B
build/block-library/blocks/gallery/editor.css 616 B
build/block-library/blocks/gallery/style-rtl.css 1.84 kB
build/block-library/blocks/gallery/style.css 1.84 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 353 B
build/block-library/blocks/html/editor.css 354 B
build/block-library/blocks/image/editor-rtl.css 763 B
build/block-library/blocks/image/editor.css 763 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 520 B
build/block-library/blocks/latest-posts/style.css 520 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 543 B
build/block-library/blocks/media-text/style.css 542 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 566 B
build/block-library/blocks/navigation-link/editor.css 568 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.23 kB
build/block-library/blocks/navigation/editor.css 2.24 kB
build/block-library/blocks/navigation/style-rtl.css 2.27 kB
build/block-library/blocks/navigation/style.css 2.26 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 378 B
build/block-library/blocks/page-list/editor.css 378 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 251 B
build/block-library/blocks/paragraph/editor.css 251 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-count/style-rtl.css 72 B
build/block-library/blocks/post-comments-count/style.css 72 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-comments-link/style-rtl.css 71 B
build/block-library/blocks/post-comments-link/style.css 71 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 722 B
build/block-library/blocks/post-featured-image/editor.css 720 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/style-rtl.css 414 B
build/block-library/blocks/post-template/style.css 414 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 133 B
build/block-library/blocks/pullquote/editor.css 133 B
build/block-library/blocks/pullquote/style-rtl.css 365 B
build/block-library/blocks/pullquote/style.css 365 B
build/block-library/blocks/pullquote/theme-rtl.css 176 B
build/block-library/blocks/pullquote/theme.css 176 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query-total/style-rtl.css 64 B
build/block-library/blocks/query-total/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 404 B
build/block-library/blocks/query/editor.css 404 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 131 B
build/block-library/blocks/read-more/style.css 131 B
build/block-library/blocks/rss/editor-rtl.css 126 B
build/block-library/blocks/rss/editor.css 126 B
build/block-library/blocks/rss/style-rtl.css 284 B
build/block-library/blocks/rss/style.css 283 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 674 B
build/block-library/blocks/search/style.css 671 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 773 B
build/block-library/blocks/site-logo/editor.css 770 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 314 B
build/block-library/blocks/social-link/editor.css 314 B
build/block-library/blocks/social-links/editor-rtl.css 339 B
build/block-library/blocks/social-links/editor.css 338 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.51 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 92 B
build/block-library/blocks/tag-cloud/editor.css 92 B
build/block-library/blocks/tag-cloud/style-rtl.css 248 B
build/block-library/blocks/tag-cloud/style.css 248 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 441 B
build/block-library/blocks/video/editor.css 442 B
build/block-library/blocks/video/style-rtl.css 192 B
build/block-library/blocks/video/style.css 192 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.2 kB
build/block-library/editor.css 11.2 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 229 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 715 B
build/block-library/theme.css 719 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.7 kB
build/commands/index.min.js 16.2 kB
build/commands/style-rtl.css 956 B
build/commands/style.css 952 B
build/components/index.min.js 249 kB
build/components/style-rtl.css 13.7 kB
build/components/style.css 13.7 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.4 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.43 kB
build/customize-widgets/style.css 1.43 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.67 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 577 B
build/edit-post/classic.css 578 B
build/edit-post/index.min.js 13.5 kB
build/edit-post/style-rtl.css 2.77 kB
build/edit-post/style.css 2.77 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.05 kB
build/edit-widgets/style.css 4.06 kB
build/editor/index.min.js 119 kB
build/editor/style-rtl.css 9.06 kB
build/editor/style.css 9.06 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.17 kB
build/format-library/style-rtl.css 472 B
build/format-library/style.css 472 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 467 B
build/i18n/index.min.js 2.23 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 847 B
build/list-reusable-blocks/style.css 848 B
build/media-utils/index.min.js 3.69 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 767 B
build/nux/style.css 763 B
build/patterns/index.min.js 7.36 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 560 B
build/preferences/style.css 561 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 978 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.53 kB
build/reusable-blocks/style-rtl.css 255 B
build/reusable-blocks/style.css 255 B
build/rich-text/index.min.js 10.3 kB
build/router/index.min.js 5.44 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.97 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@oandregal oandregal requested a review from jameskoster July 17, 2025 08:17
@oandregal
Copy link
Member Author

"group by" is a higher-level "sort by" that also groups the data visually — sort is contained in groupBy. How do we surface this at the UI level?

Considering this thought, there are two potential implementations. The current implementation adds a new property to the view:

{
  type: 'grid',
  sort: {
    field: 'fieldIdSort'
    direction: 'asc'
  },
  groupByField: 'fieldIdGroupBy'
}

But we could also consider:

{
  type: 'grid',
  sort: {
    field: 'fieldIdSort'
    direction: 'asc',
    grouped: true
  }
}

In the second suggestion, I'd expect the field used to sort the data request to be sort.field, and the UI for grouping to be part of the sort affordance. In the first one, we need to decide if the data request is sorted by sort.field or groupByField, and the UI for grouping can be independent of sorting.

@oandregal
Copy link
Member Author

oandregal commented Jul 17, 2025

Consider the relationship with fields, titleField, etc.

It doesn't look like we need to do anything here. Consumers can do this if that makes sense to them:

{
  fields: [ 'someField', 'groupByField' ],
  groupByField: 'groupByField'
}

It's the same that happens with titleField or mediaField. I don't see any use case where consumers would want to display twice the title, media, or groupBy fields. We don't have any guardrails for title & media that prevent them from being used in the fields array, so none should be added for groupBy either in this PR.

@youknowriad
Copy link
Contributor

"group by" is a higher-level "sort by" that also groups the data visually — sort is contained in groupBy. How do we surface this at the UI level?

For me that is something that the DataViews package should actually do internally. Have some kind of function that takes the "data" and group it no?

I do see a need to translate a view to a REST API query when there's grouping applied (and in this case, it might mean sorting) but this is a bit separate from the rendering itself. I expect the DataViews to group things properly if it receives the Data not in the right order.

@oandregal
Copy link
Member Author

okay, I've updated the code to use this approach instead:

{
  type: 'grid',
  sort: {
    field: 'fieldIdSort'
    direction: 'asc',
    grouped: true
  }
}

Also prepended the field label, so values that aren't self-explanatory work better (boolean, numbers):

Screen.Recording.2025-07-17.at.12.47.27.mov

@youknowriad
Copy link
Contributor

What do you think about my previous commentS?

@oandregal
Copy link
Member Author

What do you think about my previous commentS?

Sorry, GitHub didn't display them to me when I posted the update. Commenting now.

I've been looking at other examples:

Finder (MacOS) Google Drive
Screenshot 2025-07-17 at 13 20 22 Screenshot 2025-07-17 at 13 17 47
  • Finder: sorting is applied within the group. Groups remain in the same position (they have an order set by MacOs and can't be ordered by the user).
  • Google Drive: the group acts as a sorting mechanism ("Recent screen").
  • In both cases, the group is not exactly the field. For example, Google Drive docs have a date, but the grouping is human-understandable dates (yesterday, last month, earlier this year). Finder does the same in some categories (sizes are grouped by ranges). This is interesting and something that can be a follow-up (added in the description): for example, fields could provide a custom groupBy function.

The way Finder works seems the more versatile for our needs, and so making groupBy part of the sorting doesn't help to implement that. I'll revert that commit and will go back to the first approach using a separate prop.

One other aspect to consider is data fetching. The Finder behavior works by sorting the data twice: first by group, secondly by the "sort by" element. It has all the data available to mangle it. The same expectation holds true for the DataViews component: it should receive the data sorted (by group, and then by sort), otherwise we'll run into behaviors like these.

There are two things here:

  • The filterSortAndPaginate utility needs to implement that (sort by "group by", then by "sort by"). I'll do that in this PR.
  • Requests to the WordPress REST Endpoints. I understand the REST endpoints don't support sorting by multiple fields (see). I'm not sure how feasible it is to implement that, but I consider it separate from this PR. One option to circumvent that limitation is to make the request using the "group by" as the order. Then, client-side, we'll sort within each group with the "sort by" key. This logic is the consumer responsibility, not the DataViews component's, which is agnostic about WordPress REST endpoints.

I'll look at updating this PR tomorrow.

@youknowriad
Copy link
Contributor

I'm not sure how feasible it is to implement that, but I consider it separate from this PR. One option to circumvent that limitation is to make the request using the "group by" as the order. Then, client-side, we'll sort within each group with the "sort by" key. This logic is the consumer responsibility, not the DataViews component's, which is agnostic about WordPress REST endpoints.

From my perspective, that's a request issue and not a DataViews issue. In other words, we shouldn't be adapting the DataViews implementation to the REST API, instead we should update the REST API endpoint to support either "group by" or "multiple sorts"

@youknowriad
Copy link
Contributor

I don't think sorting in the client after sorting with the REST API works though, it gives broken results when you have pagination.

@oandregal
Copy link
Member Author

From my perspective, that's a request issue and not a DataViews issue.

Yeah, agreed. This needs to be looked at separately at the consumer level.

Pushed the changes and this is how it works now:

Screen.Recording.2025-07-18.at.11.29.04.mov

@oandregal oandregal marked this pull request as ready for review July 18, 2025 09:34
Copy link

github-actions bot commented Jul 18, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@oandregal oandregal removed the [Status] In Progress Tracking issues with work in progress label Jul 18, 2025
<h3 className="dataviews-view-grid__group-header">
{ groupField?.label }: { groupName }
</h3>
<Grid
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels like this just duplicates what we have in the bottom of the component, only diff seems to be the data used no?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it is. I didn't think this would benefit from a further abstraction.

Copy link
Contributor

Choose a reason for hiding this comment

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

No need for something big really here to be honest, just a function or subcomponent.

( [ groupName, groupItems ] ) => (
<VStack key={ groupName } spacing={ 2 }>
<h3 className="dataviews-view-grid__group-header">
{ groupField?.label }: { groupName }
Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to combine the label and the value, we need to use sprintf and __ I think. Not all the languages combine similarly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, good catch! Done this 1a733ad

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

This is looking good, I wonder how much of this is "layout specific". Like what if this is done at a higher level than that layout, would that work? Maybe not

/**
* The field to group by.
*/
groupByField?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any thoughts on a UI to configure this? (not necessary for this PR)

Also would be good to document this in the README.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done at d97929e

Copy link
Member Author

Choose a reason for hiding this comment

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

Any thoughts on a UI to configure this? (not necessary for this PR)

The obvious way is to add a new select in the view config somewhere. Alternatively, we could explore making it part of the field list (similarly to what we have for the media field).

oandregal and others added 6 commits July 21, 2025 12:39
Add groupByField property to ViewBase interface and implement grouping functionality in grid layout. When groupByField is specified, data is grouped by the field value and displayed with group headers. Each group maintains the same grid layout structure while being visually separated.

Changes:
- Add groupByField property to ViewBase type
- Implement grouping logic in grid layout component
- Add styling for group headers
- Add GroupedGridLayout story to demonstrate the feature

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@oandregal oandregal force-pushed the try/group-by-dataviews branch from 55d8886 to 1a733ad Compare July 21, 2025 10:40
Comment on lines +346 to +348
const { data: shownData, paginationInfo } = useMemo( () => {
return filterSortAndPaginate( data, view, fields );
}, [ view ] );
Copy link
Member

Choose a reason for hiding this comment

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

What if just the data changes? Don't we risk showing obsolete memoized data that way?

I know this is in a story, but folks tend to copy code from stories, so we need to be mindful about it.

Copy link
Member Author

Choose a reason for hiding this comment

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

In this case, data and fields won't change because they are static arrays.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure I was clear enough - my worry is about people copying the code from stories and it potentially leading to the issues I mentioned.

Copy link
Member Author

Choose a reason for hiding this comment

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

I understand that viewpoint, but I'm reluctant to add unnecessary code to cover misuse — that's not something I've seen us doing.

@oandregal
Copy link
Member Author

This is looking good, I wonder how much of this is "layout specific". Like what if this is done at a higher level than that layout, would that work? Maybe not

This depends on the layout. For example, for grid, perhaps it could have worked as something higher-level, but not for table, for example (we want the groups within the table body).

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

I wonder if we intend to use this with the table view as well. I think it could work nicely. Maybe not something for this PR though.

@tyxla tyxla mentioned this pull request Jul 21, 2025
6 tasks
@oandregal
Copy link
Member Author

I wonder if we intend to use this with the table view as well. I think it could work nicely. Maybe not something for this PR though.

That's the idea, it's part of the follow-ups. Responded there, but I'd think the original issue may need a design refresh.

? data.reduce( ( groups: { [ key: string ]: typeof data }, item ) => {
const groupName = groupField.getValue( { item } );
if ( ! groups[ groupName ] ) {
groups[ groupName ] = [];
Copy link
Member

Choose a reason for hiding this comment

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

Theoretically, keys could conflict with the existing properties of object. Isn't it safer to use a Map (and potentially more semantic) rather than a plain object?

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated to use a Map at 8ac0e2c

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

Changelog needs updating?

@oandregal
Copy link
Member Author

Changelog needs updating?

🤦 It does! Done at 4aef87a

@oandregal oandregal merged commit 362e12d into trunk Jul 21, 2025
60 checks passed
@oandregal oandregal deleted the try/group-by-dataviews branch July 21, 2025 12:06
@github-actions github-actions bot added this to the Gutenberg 21.3 milestone Jul 21, 2025
Comment on lines +381 to +396
const groupByField = _fields.find( ( field ) => {
return field.id === view.groupByField;
} );
}
if ( view.groupByField && groupByField ) {
const groupCompare = groupByField.sort( a, b, 'asc' );

// If items are in different groups, return the group comparison result.
// Otherwise, fall back to sorting by the sort field.
if ( groupCompare !== 0 ) {
return groupCompare;
}
}

// Sort by the sort field.
const sortByField = _fields.find( ( field ) => {
return field.id === view?.sort?.field;
Copy link
Contributor

Choose a reason for hiding this comment

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

This change moved the computation of sortByField into the sorting function and added groupByField, but both are independent of comparison elements a and b. Similarly, they are computed before checking whether they are needed (view.groupByField and view.sort). Lastly, the sorting is now always performed, even if there aren't the right fields for it.

This should be:

sortByField = view.sort && find()
groupByField = view.groupByField && find()

if (sortByField || groupByField) {
  filteredData.sort((a, b) => {
    if (groupByField) {  }
    if (sortByField) {  }
    return 0
  } )
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Addressed this at #70822

Theoretically, this could have a performance hit in situations where the field to sort/group by wasn't found. In practice, that field is defined by the consumer, or it's the result of users clicking around — unless there's a bug, the provided field will always exist. Is that your assessment as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

unless there's a bug, the provided field will always exist. Is that your assessment as well?

I think so, but I didn't think much of that scenario. I was drawn more to the unnecessary recomputing and sorting. :)

adamsilverstein pushed a commit to adamsilverstein/gutenberg that referenced this pull request Jul 21, 2025
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
@t-hamano t-hamano added the [Package] DataViews /packages/dataviews label Jul 23, 2025
USERSATOSHI pushed a commit to USERSATOSHI/gutenberg that referenced this pull request Jul 23, 2025
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Package] DataViews /packages/dataviews [Type] Feature New feature to highlight in changelogs.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants