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 && ( + + { () => ( + { + setIsTemplatePartSelectionOpen( true ); + } } + > + { createInterpolateElement( + __( 'Replace ' ), + { + BlockTitle: ( + + ), } - > - { __( 'Replace' ) } - - - - ) } + ) } + + ) } + + ) } { 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 ( - { - linkProps.onClick( event ); - onClose(); - } } - > - { - /* translators: %s: template part title */ - sprintf( __( 'Edit %s' ), selectedTemplatePart.slug ) - } - - ); -} 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 +);