diff --git a/packages/e2e-tests/plugins/plugins-api.php b/packages/e2e-tests/plugins/plugins-api.php index fb8054924cebb0..10e35f16226f2a 100644 --- a/packages/e2e-tests/plugins/plugins-api.php +++ b/packages/e2e-tests/plugins/plugins-api.php @@ -86,6 +86,19 @@ function enqueue_plugins_api_plugin_scripts() { filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/document-setting.js' ), true ); + + wp_enqueue_script( + 'gutenberg-test-plugins-api-preview-menu', + plugins_url( 'plugins-api/preview-menu.js', __FILE__ ), + array( + 'wp-editor', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/preview-menu.js' ), + true + ); } add_action( 'init', 'enqueue_plugins_api_plugin_scripts' ); diff --git a/packages/e2e-tests/plugins/plugins-api/preview-menu.js b/packages/e2e-tests/plugins/plugins-api/preview-menu.js new file mode 100644 index 00000000000000..1aa53b2e8509ac --- /dev/null +++ b/packages/e2e-tests/plugins/plugins-api/preview-menu.js @@ -0,0 +1,14 @@ +( function () { + const { __ } = wp.i18n; + const { registerPlugin } = wp.plugins; + const PluginPreviewMenuItem = wp.editor.PluginPreviewMenuItem; + const el = wp.element.createElement; + + function CustomPreviewMenuItem() { + return el( PluginPreviewMenuItem, {}, __( 'Custom Preview' ) ); + } + + registerPlugin( 'custom-preview-menu-item', { + render: CustomPreviewMenuItem, + } ); +} )(); diff --git a/packages/editor/README.md b/packages/editor/README.md index 89ea15ef378495..ebd4af31e287d8 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -862,6 +862,43 @@ _Returns_ - `Component`: The component to be rendered. +### PluginPreviewMenuItem + +Renders a menu item in the Preview dropdown, which can be used as a button or link depending on the props provided. The text within the component appears as the menu item label. + +_Usage_ + +```jsx +import { __ } from '@wordpress/i18n'; +import { PluginPreviewMenuItem } from '@wordpress/editor'; +import { external } from '@wordpress/icons'; + +function onPreviewClick() { + // Handle preview action +} + +const ExternalPreviewMenuItem = () => ( + + { __( 'Preview in new tab' ) } + +); +registerPlugin( 'external-preview-menu-item', { + render: ExternalPreviewMenuItem, +} ); +``` + +_Parameters_ + +- _props_ `Object`: Component properties. +- _props.href_ `[string]`: When `href` is provided, the menu item is rendered as an anchor instead of a button. It corresponds to the `href` attribute of the anchor. +- _props.icon_ `[WPBlockTypeIconRender]`: The icon to be rendered to the left of the menu item label. Can be a Dashicon slug or an SVG WP element. +- _props.onClick_ `[Function]`: The callback function to be executed when the user clicks the menu item. +- _props.other_ `[...*]`: Any additional props are passed through to the underlying MenuItem component. + +_Returns_ + +- `Component`: The rendered menu item component. + ### PluginSidebar Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. It also automatically renders a corresponding `PluginSidebarMenuItem` component when `isPinnable` flag is set to `true`. If you wish to display the sidebar, you can with use the `PluginSidebarMoreMenuItem` component or the `wp.data.dispatch` API: diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 91dcc883d661b2..b42566aac653be 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -32,6 +32,7 @@ export { default as PluginMoreMenuItem } from './plugin-more-menu-item'; export { default as PluginPostPublishPanel } from './plugin-post-publish-panel'; export { default as PluginPostStatusInfo } from './plugin-post-status-info'; export { default as PluginPrePublishPanel } from './plugin-pre-publish-panel'; +export { default as PluginPreviewMenuItem } from './plugin-preview-menu-item'; export { default as PluginSidebar } from './plugin-sidebar'; export { default as PluginSidebarMoreMenuItem } from './plugin-sidebar-more-menu-item'; export { default as PostTemplatePanel } from './post-template/panel'; diff --git a/packages/editor/src/components/plugin-preview-menu-item/index.js b/packages/editor/src/components/plugin-preview-menu-item/index.js new file mode 100644 index 00000000000000..422248e17b88e1 --- /dev/null +++ b/packages/editor/src/components/plugin-preview-menu-item/index.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { MenuItem } from '@wordpress/components'; +import { withPluginContext } from '@wordpress/plugins'; +import { ActionItem } from '@wordpress/interface'; + +/** + * Renders a menu item in the Preview dropdown, which can be used as a button or link depending on the props provided. + * The text within the component appears as the menu item label. + * + * @param {Object} props Component properties. + * @param {string} [props.href] When `href` is provided, the menu item is rendered as an anchor instead of a button. It corresponds to the `href` attribute of the anchor. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The icon to be rendered to the left of the menu item label. Can be a Dashicon slug or an SVG WP element. + * @param {Function} [props.onClick] The callback function to be executed when the user clicks the menu item. + * @param {...*} [props.other] Any additional props are passed through to the underlying MenuItem component. + * + * @example + * ```jsx + * import { __ } from '@wordpress/i18n'; + * import { PluginPreviewMenuItem } from '@wordpress/editor'; + * import { external } from '@wordpress/icons'; + * + * function onPreviewClick() { + * // Handle preview action + * } + * + * const ExternalPreviewMenuItem = () => ( + * + * { __( 'Preview in new tab' ) } + * + * ); + * registerPlugin( 'external-preview-menu-item', { + * render: ExternalPreviewMenuItem, + * } ); + * ``` + * + * @return {Component} The rendered menu item component. + */ +export default compose( + withPluginContext( ( context, ownProps ) => { + return { + as: ownProps.as ?? MenuItem, + icon: ownProps.icon || context.icon, + name: 'core/plugin-preview-menu', + }; + } ) +)( ActionItem ); diff --git a/packages/editor/src/components/preview-dropdown/index.js b/packages/editor/src/components/preview-dropdown/index.js index ec30d55cf0f17b..8b51bb79bc8873 100644 --- a/packages/editor/src/components/preview-dropdown/index.js +++ b/packages/editor/src/components/preview-dropdown/index.js @@ -22,6 +22,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { useEffect, useRef } from '@wordpress/element'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as blockEditorStore } from '@wordpress/block-editor'; +import { ActionItem } from '@wordpress/interface'; /** * Internal dependencies @@ -206,6 +207,11 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { /> ) } + ) } diff --git a/test/e2e/specs/editor/plugins/plugins-api.spec.js b/test/e2e/specs/editor/plugins/plugins-api.spec.js index c71b49e3c4d815..c7f3a655e14240 100644 --- a/test/e2e/specs/editor/plugins/plugins-api.spec.js +++ b/test/e2e/specs/editor/plugins/plugins-api.spec.js @@ -230,4 +230,25 @@ test.describe( 'Plugins API', () => { ).toBeVisible(); } ); } ); + + test.describe( 'Preview Menu Item', () => { + test( 'Should render and interact with PluginPreviewMenuItem', async ( { + page, + } ) => { + await page + .getByRole( 'region', { name: 'Editor top bar' } ) + .locator( '.editor-preview-dropdown__toggle' ) + .click(); + + const customPreviewItem = page.getByRole( 'menuitem', { + name: 'Custom Preview', + } ); + + await expect( customPreviewItem ).toBeVisible(); + + await customPreviewItem.click(); + + await expect( customPreviewItem ).toBeHidden(); + } ); + } ); } );