diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 1afae5f0de0877..28e25145f15f13 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -332,7 +332,8 @@ export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { return ( isSearchMatch( blockType.title ) || some( blockType.keywords, isSearchMatch ) || - isSearchMatch( blockType.category ) + isSearchMatch( blockType.category ) || + isSearchMatch( blockType.description ) ); } diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 47b521f05bc30e..32ebe238373582 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -586,6 +586,7 @@ describe( 'selectors', () => { title: 'Paragraph', category: 'text', keywords: [ 'body' ], + description: 'writing flow', }; const state = { @@ -598,6 +599,10 @@ describe( 'selectors', () => { [ 'name', name ], [ 'block type', blockType ], [ 'block type without category', omit( blockType, 'category' ) ], + [ + 'block type without description', + omit( blockType, 'description' ), + ], ] )( 'by %s', ( label, nameOrType ) => { it( 'should return false if not match', () => { const result = isMatchingSearchTerm( @@ -670,6 +675,18 @@ describe( 'selectors', () => { expect( result ).toBe( true ); } ); } + + if ( nameOrType.description ) { + it( 'should return true if match using the description', () => { + const result = isMatchingSearchTerm( + state, + nameOrType, + 'flow' + ); + + expect( result ).toBe( true ); + } ); + } } ); } ); diff --git a/packages/e2e-tests/specs/site-editor/global-styles-sidebar.test.js b/packages/e2e-tests/specs/site-editor/global-styles-sidebar.test.js new file mode 100644 index 00000000000000..36b615d202926b --- /dev/null +++ b/packages/e2e-tests/specs/site-editor/global-styles-sidebar.test.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { + deleteAllTemplates, + activateTheme, + visitSiteEditor, + toggleGlobalStyles, + openGlobalStylesPanel, +} from '@wordpress/e2e-test-utils'; + +describe( 'Global styles sidebar', () => { + beforeAll( async () => { + await activateTheme( 'emptytheme' ); + await Promise.all( [ + deleteAllTemplates( 'wp_template' ), + deleteAllTemplates( 'wp_template_part' ), + ] ); + } ); + afterAll( async () => { + await Promise.all( [ + deleteAllTemplates( 'wp_template' ), + deleteAllTemplates( 'wp_template_part' ), + ] ); + await activateTheme( 'twentytwentyone' ); + } ); + beforeEach( async () => { + await visitSiteEditor(); + } ); + describe( 'blocks list', () => { + it( 'should filter results properly', async () => { + await toggleGlobalStyles(); + await openGlobalStylesPanel( 'Blocks' ); + await page.focus( '.edit-site-block-types-search input' ); + await page.keyboard.type( 'heading' ); + const results = await page.$$( + '.edit-site-block-types-item-list div[role="listitem"]' + ); + expect( results.length ).toEqual( 1 ); + } ); + } ); +} ); diff --git a/packages/edit-site/src/components/global-styles/screen-block-list.js b/packages/edit-site/src/components/global-styles/screen-block-list.js index ac50ce5f672ee3..194192c8b113bf 100644 --- a/packages/edit-site/src/components/global-styles/screen-block-list.js +++ b/packages/edit-site/src/components/global-styles/screen-block-list.js @@ -2,13 +2,17 @@ * WordPress dependencies */ import { store as blocksStore } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf, _n } from '@wordpress/i18n'; import { FlexItem, + SearchControl, __experimentalHStack as HStack, } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useState, useMemo, useEffect, useRef } from '@wordpress/element'; import { BlockIcon } from '@wordpress/block-editor'; +import { useDebounce } from '@wordpress/compose'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -68,6 +72,45 @@ function BlockMenuItem( { block } ) { function ScreenBlockList() { const sortedBlockTypes = useSortedBlockTypes(); + const [ filterValue, setFilterValue ] = useState( '' ); + const debouncedSpeak = useDebounce( speak, 500 ); + const isMatchingSearchTerm = useSelect( + ( select ) => select( blocksStore ).isMatchingSearchTerm, + [] + ); + const filteredBlockTypes = useMemo( () => { + if ( ! filterValue ) { + return sortedBlockTypes; + } + return sortedBlockTypes.filter( ( blockType ) => + isMatchingSearchTerm( blockType, filterValue ) + ); + }, [ filterValue, sortedBlockTypes, isMatchingSearchTerm ] ); + + const blockTypesListRef = useRef(); + + // Announce search results on change + useEffect( () => { + if ( ! filterValue ) { + return; + } + // We extract the results from the wrapper div's `ref` because + // filtered items can contain items that will eventually not + // render and there is no reliable way to detect when a child + // will return `null`. + // TODO: We should find a better way of handling this as it's + // fragile and depends on the number of rendered elements of `BlockMenuItem`, + // which is now one. + // @see https://github.com/WordPress/gutenberg/pull/39117#discussion_r816022116 + const count = blockTypesListRef.current.childElementCount; + const resultsFoundMessage = sprintf( + /* translators: %d: number of results. */ + _n( '%d result found.', '%d results found.', count ), + count + ); + debouncedSpeak( resultsFoundMessage, count ); + }, [ filterValue, debouncedSpeak ] ); + return ( <> - { sortedBlockTypes.map( ( block ) => ( - - ) ) } + +
+ { filteredBlockTypes.map( ( block ) => ( + + ) ) } +
); } diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index 480c060835436c..122b753dffa172 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -44,7 +44,8 @@ } } -.edit-site-global-styles-header__description { +.edit-site-global-styles-header__description, +.edit-site-block-types-search { padding: 0 $grid-unit-20; }