diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index b6b3891e33d9e5..7470ef368044cd 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -15,6 +15,10 @@ - DataViews: Fix incorrect documentation for `defaultLayouts` prop. [#71334](https://github.com/WordPress/gutenberg/pull/71334) - DataViews: Fix mismatched padding on mobile viewports for grid layout [#71455](https://github.com/WordPress/gutenberg/pull/71455) +### Enhancements + +- Add support for hiding the `title` in Grid layouts, with the actions menu rendered over the media preview. [#71369](https://github.com/WordPress/gutenberg/pull/71369) + ## 7.0.0 (2025-08-20) ### Breaking changes diff --git a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx index ba0aeee5661af0..4657ff23c60a15 100644 --- a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx +++ b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx @@ -772,7 +772,7 @@ export const fields: Field< SpaceObject >[] = [ label: 'Title', id: 'title', type: 'text', - enableHiding: false, + enableHiding: true, enableGlobalSearch: true, filterBy: { operators: [ 'contains', 'notContains', 'startsWith' ], diff --git a/packages/dataviews/src/components/dataviews/stories/index.story.tsx b/packages/dataviews/src/components/dataviews/stories/index.story.tsx index 813211c66ca658..1023f9f13d2c9c 100644 --- a/packages/dataviews/src/components/dataviews/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataviews/stories/index.story.tsx @@ -46,6 +46,20 @@ import './style.css'; const meta = { title: 'DataViews/DataViews', component: DataViews, + // Use fullscreen layout and a wrapper div with padding to resolve conflicts + // between Ariakit's Dialog (usePreventBodyScroll) and Storybook's body padding + // (sb-main-padding class). This ensures consistent layout in DataViews stories + // when clicking actions menus. Without this the padding on the body will jump. + parameters: { + layout: 'fullscreen', + }, + decorators: [ + ( Story ) => ( +
+ +
+ ), + ], } as Meta< typeof DataViews >; export default meta; diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index b7d6adb04fc9f2..36284357ea47d2 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -108,6 +108,7 @@ function GridItem< Item >( { showTitle && titleField?.render ? ( ) : null; + const shouldRenderMedia = showMedia && renderedMediaField; let mediaA11yProps; let titleA11yProps; @@ -154,7 +155,7 @@ function GridItem< Item >( { } aria-posinset={ posinset } > - { showMedia && renderedMediaField && ( + { shouldRenderMedia && ( ( { { renderedMediaField } ) } - { hasBulkActions && showMedia && renderedMediaField && ( + { hasBulkActions && shouldRenderMedia && ( ( { disabled={ ! hasBulkAction } /> ) } - - - { renderedTitleField } - - { !! actions?.length && ( + { ! showTitle && shouldRenderMedia && !! actions?.length && ( +
- ) } - +
+ ) } + { showTitle && ( + + + { renderedTitleField } + + { !! actions?.length && ( + + ) } + + ) } { showDescription && descriptionField?.render && ( { await user.keyboard( '{/Control}' ); } ); + it( 'supports tabbing to selection and actions when title is visible', async () => { + render( + true } + actions={ actions } + /> + ); + + // Double check that the title is being rendered. + expect( screen.getByText( data[ 0 ].title ) ).toBeInTheDocument(); + + const viewOptionsButton = screen.getByRole( 'button', { + name: 'View options', + } ); + + const user = userEvent.setup(); + + // Double click to open and then close view options. This is performed + // instead of a direct .focus() so that effects have time to complete. + await user.click( viewOptionsButton ); + await user.click( viewOptionsButton ); + + await user.tab(); + + expect( + screen.getByRole( 'checkbox', { name: data[ 0 ].title } ) + ).toHaveFocus(); + + await user.tab(); + + expect( + screen.getAllByRole( 'button', { name: 'Actions' } )[ 0 ] + ).toHaveFocus(); + } ); + + it( 'supports tabbing to selection and actions when title is not visible', async () => { + render( + true } + actions={ actions } + /> + ); + + // Double check that the title is not being rendered. + expect( + screen.queryByText( data[ 0 ].title ) + ).not.toBeInTheDocument(); + + const viewOptionsButton = screen.getByRole( 'button', { + name: 'View options', + } ); + + const user = userEvent.setup(); + + // Double click to open and then close view options. This is performed + // instead of a direct .focus() so that effects have time to complete. + await user.click( viewOptionsButton ); + await user.click( viewOptionsButton ); + await user.tab(); + + expect( + screen.getByRole( 'checkbox', { name: data[ 0 ].title } ) + ).toHaveFocus(); + + await user.tab(); + + expect( + screen.getAllByRole( 'button', { name: 'Actions' } )[ 0 ] + ).toHaveFocus(); + } ); + it( 'accepts an invalid previewSize and the preview size picker falls back to another size', async () => { render( = { placeholder: __( 'No title' ), getValue: ( { item } ) => getItemTitle( item ), render: TitleView, - enableHiding: false, + enableHiding: true, enableGlobalSearch: true, filterBy: false, };