Skip to content

Commit

Permalink
Template Parts: Add search to replacement modal (#42459)
Browse files Browse the repository at this point in the history
* Template Parts: Add search to replacement modal

* Nitpick

* Make search sticky

* Improve search

* Update function name

* Try absolute position

* Search input height

* Fix ESLint error

* Search input width on small screens

* Position search absolutely on larger screens only

* Remove absolute positioning

* Use correct z-index

Co-authored-by: James Koster <[email protected]>
  • Loading branch information
Mamaduka and jameskoster authored Jul 20, 2022
1 parent 49a4a01 commit dd22e60
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 40 deletions.
1 change: 1 addition & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ $z-layers: (
".block-editor-inserter__tabs .components-tab-panel__tab-content": 0, // lower scrolling content
".block-editor-inserter__tabs .components-tab-panel__tabs": 1, // higher sticky element
".block-editor-inserter__search": 1, // higher sticky element
".block-library-template-part__selection-search": 1, // higher sticky element

// These next two share a stacking context
".interface-complementary-area .components-panel" : 0, // lower scrolling content
Expand Down
108 changes: 68 additions & 40 deletions packages/block-library/src/template-part/edit/selection-modal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useCallback, useMemo } from '@wordpress/element';
import { useCallback, useMemo, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useDispatch } from '@wordpress/data';
Expand All @@ -11,6 +11,10 @@ import {
__experimentalBlockPatternsList as BlockPatternsList,
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
SearchControl,
__experimentalHStack as HStack,
} from '@wordpress/components';

/**
* Internal dependencies
Expand All @@ -21,6 +25,7 @@ import {
useCreateTemplatePartFromBlocks,
} from './utils/hooks';
import { createTemplatePartId } from './utils/create-template-part-id';
import { searchPatterns } from './utils/search';

export default function TemplatePartSelectionModal( {
setAttributes,
Expand All @@ -29,6 +34,8 @@ export default function TemplatePartSelectionModal( {
area,
clientId,
} ) {
const [ searchValue, setSearchValue ] = useState( '' );

// When the templatePartId is undefined,
// it means the user is creating a new one from the placeholder.
const isReplacingTemplatePartContent = !! templatePartId;
Expand All @@ -37,18 +44,24 @@ export default function TemplatePartSelectionModal( {
templatePartId
);
// We can map template parts to block patters to reuse the BlockPatternsList UI
const templartPartsAsBlockPatterns = useMemo( () => {
return templateParts.map( ( templatePart ) => ( {
const filteredTemplateParts = useMemo( () => {
const partsAsPatterns = templateParts.map( ( templatePart ) => ( {
name: createTemplatePartId( templatePart.theme, templatePart.slug ),
title: templatePart.title.rendered,
blocks: parse( templatePart.content.raw ),
templatePart,
} ) );
}, [ templateParts ] );
const shownTemplateParts = useAsyncList( templartPartsAsBlockPatterns );
const { createSuccessNotice } = useDispatch( noticesStore );

return searchPatterns( partsAsPatterns, searchValue );
}, [ templateParts, searchValue ] );
const shownTemplateParts = useAsyncList( filteredTemplateParts );
const blockPatterns = useAlternativeBlockPatterns( area, clientId );
const shownBlockPatterns = useAsyncList( blockPatterns );
const filteredBlockPatterns = useMemo( () => {
return searchPatterns( blockPatterns, searchValue );
}, [ blockPatterns, searchValue ] );
const shownBlockPatterns = useAsyncList( filteredBlockPatterns );

const { createSuccessNotice } = useDispatch( noticesStore );
const { replaceInnerBlocks } = useDispatch( blockEditorStore );

const onTemplatePartSelect = useCallback( ( templatePart ) => {
Expand All @@ -75,41 +88,56 @@ export default function TemplatePartSelectionModal( {
setAttributes
);

const hasTemplateParts = !! filteredTemplateParts.length;
const hasBlockPatterns = !! filteredBlockPatterns.length;

return (
<>
<div className="block-library-template-part__selection-content">
{ !! templartPartsAsBlockPatterns.length && (
<div>
<h2>{ __( 'Existing template parts' ) }</h2>
<BlockPatternsList
blockPatterns={ templartPartsAsBlockPatterns }
shownPatterns={ shownTemplateParts }
onClickPattern={ ( pattern ) => {
onTemplatePartSelect( pattern.templatePart );
} }
/>
</div>
) }
<div className="block-library-template-part__selection-content">
<div className="block-library-template-part__selection-search">
<SearchControl
onChange={ setSearchValue }
value={ searchValue }
label={ __( 'Search for replacements' ) }
placeholder={ __( 'Search' ) }
/>
</div>
{ hasTemplateParts && (
<div>
<h2>{ __( 'Existing template parts' ) }</h2>
<BlockPatternsList
blockPatterns={ filteredTemplateParts }
shownPatterns={ shownTemplateParts }
onClickPattern={ ( pattern ) => {
onTemplatePartSelect( pattern.templatePart );
} }
/>
</div>
) }

{ !! blockPatterns.length && (
<div>
<h2>{ __( 'Patterns' ) }</h2>
<BlockPatternsList
blockPatterns={ blockPatterns }
shownPatterns={ shownBlockPatterns }
onClickPattern={ ( pattern, blocks ) => {
if ( isReplacingTemplatePartContent ) {
replaceInnerBlocks( clientId, blocks );
} else {
createFromBlocks( blocks, pattern.title );
}
{ hasBlockPatterns && (
<div>
<h2>{ __( 'Patterns' ) }</h2>
<BlockPatternsList
blockPatterns={ filteredBlockPatterns }
shownPatterns={ shownBlockPatterns }
onClickPattern={ ( pattern, blocks ) => {
if ( isReplacingTemplatePartContent ) {
replaceInnerBlocks( clientId, blocks );
} else {
createFromBlocks( blocks, pattern.title );
}

onClose();
} }
/>
</div>
) }
</div>
</>
onClose();
} }
/>
</div>
) }

{ ! hasTemplateParts && ! hasBlockPatterns && (
<HStack alignment="center">
<p>{ __( 'No results found.' ) }</p>
</HStack>
) }
</div>
);
}
76 changes: 76 additions & 0 deletions packages/block-library/src/template-part/edit/utils/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* External dependencies
*/
import removeAccents from 'remove-accents';

/**
* Sanitizes the search input string.
*
* @param {string} input The search input to normalize.
*
* @return {string} The normalized search input.
*/
function normalizeSearchInput( input = '' ) {
// Disregard diacritics.
input = removeAccents( input );

// Trim & Lowercase.
input = input.trim().toLowerCase();

return input;
}

/**
* Get the search rank for a given pattern and a specific search term.
*
* @param {Object} pattern Pattern to rank
* @param {string} searchValue Search term
* @return {number} A pattern search rank
*/
function getPatternSearchRank( pattern, searchValue ) {
const normalizedSearchValue = normalizeSearchInput( searchValue );
const normalizedTitle = normalizeSearchInput( pattern.title );

let rank = 0;

if ( normalizedSearchValue === normalizedTitle ) {
rank += 30;
} else if ( normalizedTitle.startsWith( normalizedSearchValue ) ) {
rank += 20;
} else {
const searchTerms = normalizedSearchValue.split( ' ' );
const hasMatchedTerms = searchTerms.every( ( searchTerm ) =>
normalizedTitle.includes( searchTerm )
);

// Prefer pattern with every search word in the title.
if ( hasMatchedTerms ) {
rank += 10;
}
}

return rank;
}

/**
* Filters an pattern list given a search term.
*
* @param {Array} patterns Item list
* @param {string} searchValue Search input.
*
* @return {Array} Filtered pattern list.
*/
export function searchPatterns( patterns = [], searchValue = '' ) {
if ( ! searchValue ) {
return patterns;
}

const rankedPatterns = patterns
.map( ( pattern ) => {
return [ pattern, getPatternSearchRank( pattern, searchValue ) ];
} )
.filter( ( [ , rank ] ) => rank > 0 );

rankedPatterns.sort( ( [ , rank1 ], [ , rank2 ] ) => rank2 - rank1 );
return rankedPatterns.map( ( [ pattern ] ) => pattern );
}
8 changes: 8 additions & 0 deletions packages/block-library/src/template-part/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@
}
}
}

.block-library-template-part__selection-search {
background: $white;
position: sticky;
top: 0;
padding: $grid-unit-20 0;
z-index: z-index(".block-library-template-part__selection-search");
}

0 comments on commit dd22e60

Please sign in to comment.