diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js
index 70078e5935836a..4523c58cfd130d 100644
--- a/packages/block-library/src/template-part/edit/index.js
+++ b/packages/block-library/src/template-part/edit/index.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { useSelect } from '@wordpress/data';
+import { useSelect, useDispatch } from '@wordpress/data';
import {
BlockSettingsMenuControls,
useBlockProps,
@@ -10,11 +10,14 @@ import {
RecursionProvider,
useHasRecursion,
InspectorControls,
+ __experimentalBlockPatternsList as BlockPatternsList,
} from '@wordpress/block-editor';
-import { Spinner, Modal, MenuItem } from '@wordpress/components';
+import { PanelBody, Spinner, Modal, MenuItem } from '@wordpress/components';
+import { useAsyncList } from '@wordpress/compose';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
import { useState } from '@wordpress/element';
+import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
@@ -24,10 +27,12 @@ import TemplatePartSelectionModal from './selection-modal';
import { TemplatePartAdvancedControls } from './advanced-controls';
import TemplatePartInnerBlocks from './inner-blocks';
import { createTemplatePartId } from './utils/create-template-part-id';
+import { mapTemplatePartToBlockPattern } from './utils/map-template-part-to-block-pattern';
import {
useAlternativeBlockPatterns,
useAlternativeTemplateParts,
useTemplatePartArea,
+ useCreateTemplatePartFromBlocks,
} from './utils/hooks';
function ReplaceButton( {
@@ -43,7 +48,6 @@ function ReplaceButton( {
templatePartId
);
const blockPatterns = useAlternativeBlockPatterns( area, clientId );
-
const hasReplacements = !! templateParts.length || !! blockPatterns.length;
const canReplace =
isEntityAvailable &&
@@ -67,11 +71,29 @@ function ReplaceButton( {
);
}
+function TemplatesList( { availableTemplates, onSelect } ) {
+ const shownTemplates = useAsyncList( availableTemplates );
+
+ if ( ! availableTemplates ) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
export default function TemplatePartEdit( {
attributes,
setAttributes,
clientId,
} ) {
+ const { createSuccessNotice } = useDispatch( noticesStore );
const currentTheme = useSelect(
( select ) => select( coreStore ).getCurrentTheme()?.stylesheet,
[]
@@ -117,12 +139,28 @@ export default function TemplatePartEdit( {
[ templatePartId, attributes.area, clientId ]
);
+ const { templateParts } = useAlternativeTemplateParts(
+ area,
+ templatePartId
+ );
+ const blockPatterns = useAlternativeBlockPatterns( area, clientId );
+ const hasReplacements = !! templateParts.length || !! blockPatterns.length;
const areaObject = useTemplatePartArea( area );
const blockProps = useBlockProps();
const isPlaceholder = ! slug;
const isEntityAvailable = ! isPlaceholder && ! isMissing && isResolved;
const TagName = tagName || areaObject.tagName;
+ const canReplace =
+ isEntityAvailable &&
+ hasReplacements &&
+ ( area === 'header' || area === 'footer' );
+
+ const createFromBlocks = useCreateTemplatePartFromBlocks(
+ area,
+ setAttributes
+ );
+
// We don't want to render a missing state if we have any inner blocks.
// A new template part is automatically created if we have any inner blocks but no entity.
if (
@@ -154,6 +192,28 @@ export default function TemplatePartEdit( {
);
}
+ const partsAsPatterns = templateParts.map( ( templatePart ) =>
+ mapTemplatePartToBlockPattern( templatePart )
+ );
+
+ const onTemplatePartSelect = ( templatePart ) => {
+ setAttributes( {
+ slug: templatePart.slug,
+ theme: templatePart.theme,
+ area: undefined,
+ } );
+ createSuccessNotice(
+ sprintf(
+ /* translators: %s: template part title. */
+ __( 'Template Part "%s" replaceed.' ),
+ templatePart.title?.rendered || templatePart.slug
+ ),
+ {
+ type: 'snackbar',
+ }
+ );
+ };
+
return (
<>
@@ -207,6 +267,33 @@ export default function TemplatePartEdit( {
);
} }
+
+ { canReplace &&
+ ( partsAsPatterns.length > 0 ||
+ blockPatterns.length > 0 ) && (
+
+
+ {
+ onTemplatePartSelect(
+ pattern.templatePart
+ );
+ } }
+ />
+ {
+ createFromBlocks(
+ blocks,
+ pattern.title
+ );
+ } }
+ />
+
+
+ ) }
+
{ isEntityAvailable && (
{
- const partsAsPatterns = templateParts.map( ( templatePart ) => ( {
- name: createTemplatePartId( templatePart.theme, templatePart.slug ),
- title: templatePart.title.rendered,
- blocks: parse( templatePart.content.raw ),
- templatePart,
- } ) );
+ const partsAsPatterns = templateParts.map( ( templatePart ) =>
+ mapTemplatePartToBlockPattern( templatePart )
+ );
return searchPatterns( partsAsPatterns, searchValue );
}, [ templateParts, searchValue ] );
diff --git a/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js b/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js
new file mode 100644
index 00000000000000..5a053350d750f8
--- /dev/null
+++ b/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js
@@ -0,0 +1,23 @@
+/**
+ * WordPress dependencies
+ */
+import { parse } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import { createTemplatePartId } from './create-template-part-id';
+
+/**
+ * This maps the properties of a template part to those of a block pattern.
+ * @param {Object} templatePart
+ * @return {Object} The template part in the shape of block pattern.
+ */
+export function mapTemplatePartToBlockPattern( templatePart ) {
+ return {
+ name: createTemplatePartId( templatePart.theme, templatePart.slug ),
+ title: templatePart.title.rendered,
+ blocks: parse( templatePart.content.raw ),
+ templatePart,
+ };
+}