diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index 07c4b1d2ba175..76cfad0b424a1 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -139,6 +139,7 @@ $z-layers: (
".reusable-blocks-menu-items__convert-modal": 1000001,
".edit-site-create-template-part-modal": 1000001,
".block-editor-block-lock-modal": 1000001,
+ ".block-editor-template-part__selection-modal": 1000001,
// Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts
// because it uses emotion and not sass. We need it to render on top its parent popover.
diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js
index 9d2df62a47cfd..213dca72e1a4a 100644
--- a/packages/block-library/src/template-part/edit/index.js
+++ b/packages/block-library/src/template-part/edit/index.js
@@ -8,22 +8,18 @@ import { isEmpty } from 'lodash';
*/
import { useSelect } from '@wordpress/data';
import {
- BlockControls,
+ BlockSettingsMenuControls,
+ BlockTitle,
useBlockProps,
- __experimentalUseNoRecursiveRenders as useNoRecursiveRenders,
Warning,
store as blockEditorStore,
+ __experimentalUseNoRecursiveRenders as useNoRecursiveRenders,
__experimentalUseBlockOverlayActive as useBlockOverlayActive,
} from '@wordpress/block-editor';
-import {
- ToolbarGroup,
- ToolbarButton,
- Spinner,
- Modal,
-} from '@wordpress/components';
+import { Spinner, Modal, MenuItem } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
-import { useState } from '@wordpress/element';
+import { useState, createInterpolateElement } from '@wordpress/element';
/**
* Internal dependencies
@@ -43,6 +39,7 @@ export default function TemplatePartEdit( {
attributes,
setAttributes,
clientId,
+ isSelected,
} ) {
const { slug, theme, tagName, layout = {} } = attributes;
const templatePartId = createTemplatePartId( theme, slug );
@@ -105,6 +102,14 @@ export default function TemplatePartEdit( {
const isEntityAvailable = ! isPlaceholder && ! isMissing && isResolved;
const TagName = tagName || areaObject.tagName;
+ // The `isSelected` check ensures the `BlockSettingsMenuControls` fill
+ // doesn't render multiple times. The block controls has similar internal check.
+ const canReplace =
+ isSelected &&
+ isEntityAvailable &&
+ hasReplacements &&
+ ( area === 'header' || area === 'footer' );
+
// 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 (
@@ -158,21 +163,29 @@ export default function TemplatePartEdit( {
/>
) }
- { isEntityAvailable &&
- hasReplacements &&
- ( area === 'header' || area === 'footer' ) && (
-
-
-
- setIsTemplatePartSelectionOpen( true )
+ { canReplace && (
+
+ { () => (
+
-
-
- ) }
+ ) }
+
+ ) }
+
+ ) }
{ isEntityAvailable && (
-
<__experimentalLinkControl.ViewerFill>
{ useCallback(
diff --git a/packages/edit-site/src/components/edit-template-part-menu-button/index.js b/packages/edit-site/src/components/edit-template-part-menu-button/index.js
deleted file mode 100644
index 83a0308b1ad1e..0000000000000
--- a/packages/edit-site/src/components/edit-template-part-menu-button/index.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useSelect } from '@wordpress/data';
-import {
- store as blockEditorStore,
- BlockSettingsMenuControls,
-} from '@wordpress/block-editor';
-import { store as coreStore } from '@wordpress/core-data';
-import { MenuItem } from '@wordpress/components';
-import { isTemplatePart } from '@wordpress/blocks';
-import { __, sprintf } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import { useLocation } from '../routes';
-import { useLink } from '../routes/link';
-
-export default function EditTemplatePartMenuButton() {
- return (
-
- { ( { selectedClientIds, onClose } ) => (
-
- ) }
-
- );
-}
-
-function EditTemplatePartMenuItem( { selectedClientId, onClose } ) {
- const { params } = useLocation();
- const selectedTemplatePart = useSelect(
- ( select ) => {
- const block =
- select( blockEditorStore ).getBlock( selectedClientId );
-
- if ( block && isTemplatePart( block ) ) {
- const { theme, slug } = block.attributes;
-
- return select( coreStore ).getEntityRecord(
- 'postType',
- 'wp_template_part',
- // Ideally this should be an official public API.
- `${ theme }//${ slug }`
- );
- }
- },
- [ selectedClientId ]
- );
-
- const linkProps = useLink(
- {
- postId: selectedTemplatePart?.id,
- postType: selectedTemplatePart?.type,
- },
- {
- fromTemplateId: params.postId,
- }
- );
-
- if ( ! selectedTemplatePart ) {
- return null;
- }
-
- return (
-
- );
-}
diff --git a/packages/edit-site/src/hooks/index.js b/packages/edit-site/src/hooks/index.js
index c6cbc1d173e86..1f7196dd2256c 100644
--- a/packages/edit-site/src/hooks/index.js
+++ b/packages/edit-site/src/hooks/index.js
@@ -2,3 +2,4 @@
* Internal dependencies
*/
import './components';
+import './template-part-edit';
diff --git a/packages/edit-site/src/hooks/template-part-edit.js b/packages/edit-site/src/hooks/template-part-edit.js
new file mode 100644
index 0000000000000..d28530dcdc4b8
--- /dev/null
+++ b/packages/edit-site/src/hooks/template-part-edit.js
@@ -0,0 +1,82 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useSelect } from '@wordpress/data';
+import { BlockControls } from '@wordpress/block-editor';
+import { store as coreStore } from '@wordpress/core-data';
+import { ToolbarButton } from '@wordpress/components';
+import { addFilter } from '@wordpress/hooks';
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import { useLocation } from '../components/routes';
+import { useLink } from '../components/routes/link';
+
+function EditTemplatePartMenuItem( { attributes } ) {
+ const { theme, slug } = attributes;
+ const { params } = useLocation();
+ const templatePart = useSelect(
+ ( select ) => {
+ return select( coreStore ).getEntityRecord(
+ 'postType',
+ 'wp_template_part',
+ // Ideally this should be an official public API.
+ `${ theme }//${ slug }`
+ );
+ },
+ [ theme, slug ]
+ );
+
+ const linkProps = useLink(
+ {
+ postId: templatePart?.id,
+ postType: templatePart?.type,
+ },
+ {
+ fromTemplateId: params.postId,
+ }
+ );
+
+ if ( ! templatePart ) {
+ return null;
+ }
+
+ return (
+
+ {
+ linkProps.onClick( event );
+ } }
+ >
+ { __( 'Edit' ) }
+
+
+ );
+}
+
+export const withEditBlockControls = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ const { attributes, name } = props;
+ const isDisplayed = name === 'core/template-part' && attributes.slug;
+
+ return (
+ <>
+
+ { isDisplayed && (
+
+ ) }
+ >
+ );
+ },
+ 'withEditBlockControls'
+);
+
+addFilter(
+ 'editor.BlockEdit',
+ 'core/edit-site/template-part-edit-button',
+ withEditBlockControls
+);