diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index efd980cba06c66..f82e77bca9d857 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -49,6 +49,7 @@ "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/keycodes": "file:../keycodes", diff --git a/packages/block-editor/src/components/preview-options/index.js b/packages/block-editor/src/components/preview-options/index.js index d173542e02570a..2678acddd263db 100644 --- a/packages/block-editor/src/components/preview-options/index.js +++ b/packages/block-editor/src/components/preview-options/index.js @@ -10,6 +10,7 @@ import { useViewportMatch } from '@wordpress/compose'; import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { check, desktop, mobile, tablet } from '@wordpress/icons'; +import { PluginPreview } from '@wordpress/interface'; export default function PreviewOptions( { children, @@ -45,6 +46,12 @@ export default function PreviewOptions( { desktop, }; + const devices = [ + { type: 'Desktop', label: __( 'Desktop' ) }, + { type: 'Tablet', label: __( 'Tablet' ) }, + { type: 'Mobile', label: __( 'Mobile' ) }, + ]; + return ( ( <> - setDeviceType( 'Desktop' ) } - icon={ deviceType === 'Desktop' && check } - > - { __( 'Desktop' ) } - - setDeviceType( 'Tablet' ) } - icon={ deviceType === 'Tablet' && check } - > - { __( 'Tablet' ) } - - setDeviceType( 'Mobile' ) } - icon={ deviceType === 'Mobile' && check } - > - { __( 'Mobile' ) } - + { devices.map( ( { type, label } ) => ( + setDeviceType( type ) } + icon={ deviceType === type && check } + > + { label } + + ) ) } + { children } ) } diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index ac8902f6a5f7a1..7ef6c07a2301cf 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -31,7 +31,12 @@ import { __experimentaluseLayoutStyles as useLayoutStyles, } from '@wordpress/block-editor'; import { useEffect, useRef, useMemo } from '@wordpress/element'; -import { Button, __unstableMotion as motion } from '@wordpress/components'; +import { + __experimentalUseSlot as useSlot, + Slot, + Button, + __unstableMotion as motion, +} from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useMergeRefs } from '@wordpress/compose'; import { arrowLeft } from '@wordpress/icons'; @@ -104,6 +109,24 @@ function getPostContentAttributes( blocks ) { } } +function PluginPreviewContent( { children } ) { + const previewContent = children; + const previewId = useSelect( + ( select ) => + select( editPostStore ).__experimentalGetPreviewDeviceType(), + [] + ); + const previewSlotName = `PluginPreview/${ previewId }`; + const slot = useSlot( previewSlotName ); + const hasFills = Boolean( slot.fills && slot.fills.length ); + + if ( ! hasFills ) { + return previewContent; + } + + return ; +} + export default function VisualEditor( { styles } ) { const { deviceType, @@ -366,72 +389,74 @@ export default function VisualEditor( { styles } ) { initial={ desktopCanvasStyles } className={ previewMode } > - - { themeSupportsLayout && - ! themeHasDisabledLayoutStyles && - ! isTemplateMode && ( - <> - - { align && ( - - ) } - { postContentLayoutStyles && ( + + + { themeSupportsLayout && + ! themeHasDisabledLayoutStyles && + ! isTemplateMode && ( + <> + { align && ( + + ) } + { postContentLayoutStyles && ( + + ) } + + ) } + { ! isTemplateMode && ( +
+ contentEditable={ false } + > + +
) } - { ! isTemplateMode && ( -
- -
- ) } - - - -
+ + +
+ diff --git a/packages/interface/src/components/index.js b/packages/interface/src/components/index.js index c9c2d09b3b3ab0..767e4bce8329c6 100644 --- a/packages/interface/src/components/index.js +++ b/packages/interface/src/components/index.js @@ -11,3 +11,4 @@ export { default as PreferencesModalTabs } from './preferences-modal-tabs'; export { default as PreferencesModalSection } from './preferences-modal-section'; export { default as ___unstablePreferencesModalBaseOption } from './preferences-modal-base-option'; export { default as NavigableRegion } from './navigable-region'; +export { default as PluginPreview } from './plugin-preview'; diff --git a/packages/interface/src/components/plugin-preview/index.js b/packages/interface/src/components/plugin-preview/index.js new file mode 100644 index 00000000000000..ef67e1eda53646 --- /dev/null +++ b/packages/interface/src/components/plugin-preview/index.js @@ -0,0 +1,111 @@ +/** + * WordPress dependencies + */ +import { + __experimentalUseSlot as useSlot, + createSlotFill, + MenuItem, + MenuGroup, + Fill, +} from '@wordpress/components'; +import { check } from '@wordpress/icons'; + +const { Fill: PluginPreviewMenuFill, Slot: PluginPreviewMenuSlot } = + createSlotFill( 'PluginPreviewMenu' ); + +/** + * 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 wrapper expects a function that returns an element. The `VisualEditor` + * component is fed in as a prop to be used. + * + * 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 {Function} props.wrapper Wrapper for visual editor content. + * @param {string} props.title Menu item title. + */ +function PluginPreview( { + children, + icon, + name, + onClick, + title, + wrapper, + ...props +} ) { + if ( ! name || ( ! children && ! wrapper ) ) { + name = 'Desktop'; + } + + return ( + <> + + { ( { deviceType, setDeviceType } ) => ( + { + setDeviceType( name ); + if ( onClick ) { + onClick( ...args ); + } + } } + icon={ icon || ( deviceType === name && check ) } + { ...props } + > + { title } + + ) } + + + { children ? ( + + { children } + + ) : ( + typeof wrapper === 'function' && ( + + { ( { previewContent } ) => wrapper( previewContent ) } + + ) + ) } + + ); +} + +function PluginPreviewSlot( { devices, ...props } ) { + const slot = useSlot( PluginPreviewMenuSlot.__unstableName ); + const hasFills = Boolean( slot.fills && slot.fills.length ); + + if ( ! hasFills ) { + return null; + } + + function checkForDeviceNames( fill ) { + const { className } = fill[ 0 ].props; + if ( devices.includes( className ) ) { + return; + } + return fill; + } + + return ( + + + { ( fills ) => <>{ fills.filter( checkForDeviceNames ) } } + + + ); +} + +PluginPreview.Slot = PluginPreviewSlot; + +export default PluginPreview;