diff --git a/docs/designers-developers/developers/slotfills/plugin-preview.md b/docs/designers-developers/developers/slotfills/plugin-preview.md
new file mode 100644
index 00000000000000..f950deda2533c9
--- /dev/null
+++ b/docs/designers-developers/developers/slotfills/plugin-preview.md
@@ -0,0 +1,61 @@
+# PluginPreview
+
+Renders the main content area when that menu item is chosen.
+
+A component used to define a custom preview menu item and optional content.
+
+The children of this component will be displayed in the main area of the
+block editor, instead of the `VisualEditor` component.
+
+The `title` and `icon` are used to populate the Preview menu item.
+
+## Available Props
+
+- **children** `WPElement`: Preview content.
+- **icon** `WPIcon`: Menu item icon to be rendered.
+- **name** `string`: A unique name of the custom preview.
+- **onClick** `Function`: Menu item click handler, e.g. for previews that provide no content (`children`).
+- **title** `string`: Menu item title.
+
+## Example
+
+```js
+import { __ } from '@wordpress/i18n';
+import { PluginPreview } from '@wordpress/block-editor';
+import { registerPlugin } from '@wordpress/plugins';
+import { external } from '@wordpress/icons';
+
+const PluginPreviewTest = () => (
+ <>
+
+
+ { __( 'Custom Preview 1 Content' ) }
+
+
+
+ console.log( event ) }
+ >
+
+ { __( 'Custom Preview 2 Content' ) }
+
+
+
+ console.log( event ) }
+ />
+ >
+);
+
+registerPlugin( "plugin-preview-test", {
+ render: PluginPreviewTest,
+} );
+```
diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md
index 5be4de892073d1..4734e347e60303 100644
--- a/docs/reference-guides/slotfills/README.md
+++ b/docs/reference-guides/slotfills/README.md
@@ -104,3 +104,7 @@ The following SlotFills are available in the `edit-post` package. Please refer t
- [PluginPrePublishPanel](/docs/reference-guides/slotfills/plugin-pre-publish-panel.md)
- [PluginSidebar](/docs/reference-guides/slotfills/plugin-sidebar.md)
- [PluginSidebarMoreMenuItem](/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md)
+
+The following SlotFill is available in the `block-editor` package:
+
+- [PluginPreview](/docs/designers-developers/developers/slotfills/plugin-preview.md)
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index da2515ead12b03..051f330718b6c6 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -281,7 +281,24 @@ Undocumented declaration.
Undocumented declaration.
-### createCustomColorsHOC
+# **coreDeviceTypes**
+
+An array of strings that represent `deviceType` values that belong to the
+block editor core system.
+
+When the `deviceType` returned by `__experimentalGetPreviewDeviceType()`, is
+one of these values, the built-in `VisualEditor` is responsible for rendering
+a preview of that type.
+
+When the `deviceType` is something other than one of the `coreDeviceTypes`,
+we are rendering a custom preview registered by the ``
+component and defer to a `` filled by the plugin to draw the preview.
+
+_Type_
+
+- `Array`
+
+# **createCustomColorsHOC**
A higher-order component factory for creating a 'withCustomColors' HOC, which handles color logic
for class generation color value, retrieval and color attribute setting.
@@ -520,7 +537,25 @@ _Related_
-
-### PreserveScrollInReorder
+# **PluginPreview**
+
+Component used to define a custom preview menu item and optional content.
+
+The children of this component will be displayed in the main area of the
+block editor, instead of the `VisualEditor` component.
+
+The `title` and `icon` are used to populate the Preview menu item.
+
+_Parameters_
+
+- _props_ `Object`: Component properties.
+- _props.children_ `WPElement`: Preview content.
+- _props.icon_ `WPIcon`: Menu item icon to be rendered.
+- _props.name_ `string`: A unique name of the custom preview.
+- _props.onClick_ `Function`: Menu item click handler, e.g. for previews that provide no content (`children`).
+- _props.title_ `string`: Menu item title.
+
+# **PreserveScrollInReorder**
Undocumented declaration.
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index fed9e9752194c7..c3401a4f4a6780 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -99,7 +99,11 @@ export { default as withColorContext } from './color-palette/with-color-context'
export { default as __unstableBlockSettingsMenuFirstItem } from './block-settings-menu/block-settings-menu-first-item';
export { default as __unstableInserterMenuExtension } from './inserter-menu-extension';
-export { default as __experimentalPreviewOptions } from './preview-options';
+export {
+ default as __experimentalPreviewOptions,
+ coreDeviceTypes,
+} from './preview-options';
+export { default as PluginPreview } from './plugin-preview';
export { default as __experimentalUseResizeCanvas } from './use-resize-canvas';
export { default as BlockInspector } from './block-inspector';
export { default as BlockList } from './block-list';
diff --git a/packages/block-editor/src/components/plugin-preview/index.js b/packages/block-editor/src/components/plugin-preview/index.js
new file mode 100644
index 00000000000000..785f0c012a5518
--- /dev/null
+++ b/packages/block-editor/src/components/plugin-preview/index.js
@@ -0,0 +1,78 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ __experimentalUseSlot as useSlot,
+ createSlotFill,
+ Fill,
+ MenuItem,
+} from '@wordpress/components';
+import { check } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import { coreDeviceTypes } from '../preview-options';
+
+const {
+ Fill: PluginPreviewMenuFill,
+ Slot: PluginPreviewMenuSlot,
+} = createSlotFill( 'PluginPreviewMenu' );
+
+export { PluginPreviewMenuSlot };
+
+/**
+ * Component used to define a custom preview menu item and optional content.
+ *
+ * The children of this component will be displayed in the main area of the
+ * block editor, instead of the `VisualEditor` component.
+ *
+ * The `title` and `icon` are used to populate the Preview menu item.
+ *
+ * @param {Object} props Component properties.
+ * @param {WPElement} props.children Preview content.
+ * @param {WPIcon} props.icon Menu item icon to be rendered.
+ * @param {string} props.name A unique name of the custom preview.
+ * @param {Function} props.onClick Menu item click handler, e.g. for previews
+ * that provide no content (`children`).
+ * @param {string} props.title Menu item title.
+ */
+export default function PluginPreview( {
+ children,
+ icon,
+ name,
+ onClick,
+ title,
+ ...props
+} ) {
+ const previewSlotName = `core/block-editor/plugin-preview/${ name }`;
+ const previewSlot = useSlot( previewSlotName );
+
+ if ( coreDeviceTypes.includes( name ) ) {
+ return null;
+ }
+
+ return (
+ <>
+
+ { ( { deviceType, setDeviceType } ) => (
+
+ ) }
+
+ { children && { children } }
+ >
+ );
+}
diff --git a/packages/block-editor/src/components/preview-options/index.js b/packages/block-editor/src/components/preview-options/index.js
index 5fb6ba1063bd45..060e33455fd80e 100644
--- a/packages/block-editor/src/components/preview-options/index.js
+++ b/packages/block-editor/src/components/preview-options/index.js
@@ -7,10 +7,36 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { useViewportMatch } from '@wordpress/compose';
-import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
+import {
+ __experimentalUseSlot as useSlot,
+ DropdownMenu,
+ MenuGroup,
+ MenuItem,
+} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { check } from '@wordpress/icons';
+/**
+ * Internal dependencies
+ */
+import { PluginPreviewMenuSlot } from '../plugin-preview';
+
+/**
+ * An array of strings that represent `deviceType` values that belong to the
+ * block editor core system.
+ *
+ * When the `deviceType` returned by `__experimentalGetPreviewDeviceType()`, is
+ * one of these values, the built-in `VisualEditor` is responsible for rendering
+ * a preview of that type.
+ *
+ * When the `deviceType` is something other than one of the `coreDeviceTypes`,
+ * we are rendering a custom preview registered by the ``
+ * component and defer to a `` filled by the plugin to draw the preview.
+ *
+ * @type {Array}
+ */
+export const coreDeviceTypes = [ 'Desktop', 'Tablet', 'Mobile' ];
+
export default function PreviewOptions( {
children,
className,
@@ -18,6 +44,7 @@ export default function PreviewOptions( {
deviceType,
setDeviceType,
} ) {
+ const previewMenuSlot = useSlot( PluginPreviewMenuSlot.__unstableName );
const isMobile = useViewportMatch( 'small', '<' );
if ( isMobile ) return null;
@@ -67,6 +94,18 @@ export default function PreviewOptions( {
{ __( 'Mobile' ) }
+
+ { previewMenuSlot.fills?.length > 0 && (
+
+
+
+ ) }
+
{ children }
>
) }
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index f0bd691a1019ff..cddb6fe7a4dcfa 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -34,7 +34,7 @@ import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
* Internal dependencies
*/
import TextEditor from '../text-editor';
-import VisualEditor from '../visual-editor';
+import VisualEditorOrPluginPreview from '../visual-editor/visual-editor-or-plugin-preview';
import EditPostKeyboardShortcuts from '../keyboard-shortcuts';
import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
import PreferencesModal from '../preferences-modal';
@@ -85,8 +85,6 @@ function Layout( { styles } ) {
showIconLabels,
hasReducedUI,
showBlockBreadcrumbs,
- isTemplateMode,
- documentLabel,
} = useSelect( ( select ) => {
const { getEditorSettings, getPostTypeLabel } = select( editorStore );
const editorSettings = getEditorSettings();
@@ -125,8 +123,6 @@ function Layout( { styles } ) {
showBlockBreadcrumbs: select( editPostStore ).isFeatureActive(
'showBlockBreadcrumbs'
),
- // translators: Default label for the Document in the Block Breadcrumb.
- documentLabel: postTypeLabel || _x( 'Document', 'noun' ),
};
}, [] );
const className = classnames( 'edit-post-layout', 'is-mode-' + mode, {
@@ -228,13 +224,7 @@ function Layout( { styles } ) {
) }
{ isRichEditingEnabled && mode === 'visual' && (
-
- ) }
- { ! isTemplateMode && (
-
-
-
-
+
) }
{ isMobileViewport && sidebarIsOpened && (
diff --git a/packages/edit-post/src/components/visual-editor/visual-editor-or-plugin-preview.js b/packages/edit-post/src/components/visual-editor/visual-editor-or-plugin-preview.js
new file mode 100644
index 00000000000000..dadf43021a0308
--- /dev/null
+++ b/packages/edit-post/src/components/visual-editor/visual-editor-or-plugin-preview.js
@@ -0,0 +1,33 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalUseSlot as useSlot, Slot } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import VisualEditor from './index';
+import { store as editPostStore } from '../../store';
+
+/**
+ * Component that renders a preview slot fill if found or a VisualEditor instead.
+ *
+ * @param {Object} props Component properties.
+ */
+function VisualEditorOrPluginPreview( props ) {
+ const previewId = useSelect(
+ ( select ) =>
+ select( editPostStore ).__experimentalGetPreviewDeviceType(),
+ []
+ );
+ const previewSlotName = `core/block-editor/plugin-preview/${ previewId }`;
+ const previewSlot = useSlot( previewSlotName );
+
+ if ( previewSlot.fills?.length > 0 ) {
+ return ;
+ }
+
+ return ;
+}
+export default VisualEditorOrPluginPreview;