diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index ac6f762ded6c8..4957beee030f2 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -36,8 +36,8 @@ import { store as editSiteStore } from '../../store'; import BackButton from './back-button'; import ResizableEditor from './resizable-editor'; import EditorCanvas from './editor-canvas'; -import StyleBook from '../style-book'; import { unlock } from '../../private-apis'; +import EditorCanvasContainer from '../editor-canvas-container'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); @@ -165,19 +165,19 @@ export default function BlockEditor() { - { /* Potentially this could be a generic slot (e.g. EditorCanvas.Slot) if there are other uses for it. */ } - - { ( [ styleBook ] ) => - styleBook ? ( + + { ( [ editorCanvasView ] ) => + editorCanvasView ? (
- { styleBook } + { editorCanvasView }
) : ( ) } -
+ ); diff --git a/packages/edit-site/src/components/editor-canvas-container/index.js b/packages/edit-site/src/components/editor-canvas-container/index.js new file mode 100644 index 0000000000000..3c3157e462c48 --- /dev/null +++ b/packages/edit-site/src/components/editor-canvas-container/index.js @@ -0,0 +1,115 @@ +/** + * WordPress dependencies + */ +import { Children, cloneElement, useState, useMemo } from '@wordpress/element'; +import { + Button, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; +import { ESCAPE } from '@wordpress/keycodes'; +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { closeSmall } from '@wordpress/icons'; +import { useFocusOnMount, useFocusReturn } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { unlock } from '../../private-apis'; +import { store as editSiteStore } from '../../store'; + +/** + * Returns a translated string for the title of the editor canvas container. + * + * @param {string} view Editor canvas container view. + * + * @return {string} Translated string corresponding to value of view. Default is ''. + */ +export function getEditorCanvasContainerTitle( view ) { + switch ( view ) { + case 'style-book': + return __( 'Style Book' ); + default: + return ''; + } +} + +// Creates a private slot fill. +const { createPrivateSlotFill } = unlock( componentsPrivateApis ); +const SLOT_FILL_NAME = 'EditSiteEditorCanvasContainerSlot'; +const { Slot: EditorCanvasContainerSlot, Fill: EditorCanvasContainerFill } = + createPrivateSlotFill( SLOT_FILL_NAME ); + +function EditorCanvasContainer( { + children, + closeButtonLabel, + onClose = () => {}, +} ) { + const editorCanvasContainerView = useSelect( + ( select ) => + unlock( select( editSiteStore ) ).getEditorCanvasContainerView(), + [] + ); + const [ isClosed, setIsClosed ] = useState( false ); + const { setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) + ); + const focusOnMountRef = useFocusOnMount( 'firstElement' ); + const sectionFocusReturnRef = useFocusReturn(); + const title = useMemo( + () => getEditorCanvasContainerTitle( editorCanvasContainerView ), + [ editorCanvasContainerView ] + ); + function onCloseContainer() { + onClose(); + setEditorCanvasContainerView( undefined ); + setIsClosed( true ); + } + + function closeOnEscape( event ) { + if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) { + event.preventDefault(); + onCloseContainer(); + } + } + + const childrenWithProps = Array.isArray( children ) + ? Children.map( children, ( child, index ) => + index === 0 + ? cloneElement( child, { + ref: sectionFocusReturnRef, + } ) + : child + ) + : cloneElement( children, { + ref: sectionFocusReturnRef, + } ); + + if ( isClosed ) { + return null; + } + + return ( + + { /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */ } +
+
+
+ ); +} + +EditorCanvasContainer.Slot = EditorCanvasContainerSlot; +export default EditorCanvasContainer; diff --git a/packages/edit-site/src/components/editor-canvas-container/style.scss b/packages/edit-site/src/components/editor-canvas-container/style.scss new file mode 100644 index 0000000000000..acc0e0872f0b4 --- /dev/null +++ b/packages/edit-site/src/components/editor-canvas-container/style.scss @@ -0,0 +1,19 @@ +.edit-site-editor-canvas-container { + background: $white; // Fallback color, overridden by JavaScript. + border-radius: $radius-block-ui; + bottom: 0; + left: 0; + overflow: hidden; + position: absolute; + right: 0; + top: 0; + transition: all 0.3s; // Match .block-editor-iframe__body transition. +} + +.edit-site-editor-canvas-container__close-button { + position: absolute; + right: $grid-unit-10; + top: math.div($grid-unit-60 - $button-size, 2); // ( tab height - button size ) / 2 + z-index: 1; + background: $white; +} diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index b2f3da2306a1d..a536014180a27 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -42,6 +42,7 @@ import StyleBook from '../style-book'; import ScreenCSS from './screen-css'; import { unlock } from '../../private-apis'; import ScreenEffects from './screen-effects'; +import { store as editSiteStore } from '../../store'; const SLOT_FILL_NAME = 'GlobalStylesMenu'; const { Slot: GlobalStylesMenuSlot, Fill: GlobalStylesMenuFill } = @@ -239,7 +240,7 @@ function ContextScreens( { name, parentMenu = '', variation = '' } ) { ); } -function GlobalStylesStyleBook( { onClose } ) { +function GlobalStylesStyleBook() { const navigator = useNavigator(); const { path } = navigator.location; return ( @@ -257,7 +258,6 @@ function GlobalStylesStyleBook( { onClose } ) { // Now go to the selected block. navigator.goTo( '/blocks/' + encodeURIComponent( blockName ) ); } } - onClose={ onClose } /> ); } @@ -296,9 +296,13 @@ function GlobalStylesBlockLink() { }, [ selectedBlockClientId, selectedBlockName, blockHasGlobalStyles ] ); } -function GlobalStylesUI( { isStyleBookOpened, onCloseStyleBook } ) { +function GlobalStylesUI() { const blocks = getBlockTypes(); - + const editorCanvasContainerView = useSelect( + ( select ) => + unlock( select( editSiteStore ) ).getEditorCanvasContainerView(), + [] + ); return ( ); } ) } - { isStyleBookOpened && ( - + { 'style-book' === editorCanvasContainerView && ( + ) } diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 448682100092d..494e115863ee5 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -38,7 +38,8 @@ import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; import DocumentActions from './document-actions'; import { store as editSiteStore } from '../../store'; -import { useHasStyleBook } from '../style-book'; +import { getEditorCanvasContainerTitle } from '../editor-canvas-container'; +import { unlock } from '../../private-apis'; const preventDefault = ( event ) => { event.preventDefault(); @@ -56,6 +57,7 @@ export default function HeaderEditMode() { blockEditorMode, homeUrl, showIconLabels, + editorCanvasView, } = useSelect( ( select ) => { const { __experimentalGetPreviewDeviceType, @@ -88,6 +90,9 @@ export default function HeaderEditMode() { 'core/edit-site', 'showIconLabels' ), + editorCanvasView: unlock( + select( editSiteStore ) + ).getEditorCanvasContainerView(), }; }, [] ); @@ -117,7 +122,7 @@ export default function HeaderEditMode() { [ setIsListViewOpened, isListViewOpen ] ); - const hasStyleBook = useHasStyleBook(); + const hasDefaultEditorCanvasView = ! editorCanvasView; const isFocusMode = templateType === 'wp_template_part'; @@ -138,7 +143,7 @@ export default function HeaderEditMode() { 'show-icon-labels': showIconLabels, } ) } > - { ! hasStyleBook && ( + { hasDefaultEditorCanvasView && ( - { hasStyleBook ? __( 'Style Book' ) : } + { ! hasDefaultEditorCanvasView ? ( + getEditorCanvasContainerTitle( editorCanvasView ) + ) : ( + + ) }
- { ! isFocusMode && ! hasStyleBook && ( + { ! isFocusMode && hasDefaultEditorCanvasView && (
select( editSiteStore ).getEditorMode(), - [] + const { editorMode, editorCanvasView } = useSelect( ( select ) => { + return { + editorMode: select( editSiteStore ).getEditorMode(), + editorCanvasView: unlock( + select( editSiteStore ) + ).getEditorCanvasContainerView(), + }; + }, [] ); + const { setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) ); useEffect( () => { if ( editorMode !== 'visual' ) { - setIsStyleBookOpened( false ); + setEditorCanvasContainerView( undefined ); } }, [ editorMode ] ); + + const isStyleBookOpened = editorCanvasView === 'style-book'; + return ( { - setIsStyleBookOpened( ! isStyleBookOpened ); - } } + onClick={ () => + setEditorCanvasContainerView( + isStyleBookOpened ? undefined : 'style-book' + ) + } /> @@ -58,10 +69,7 @@ export default function GlobalStylesSidebar() { } > - setIsStyleBookOpened( false ) } - /> + ); } diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index a010f811dcf1d..650f38eea95b9 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -7,14 +7,11 @@ import classnames from 'classnames'; * WordPress dependencies */ import { - Button, __unstableComposite as Composite, __unstableUseCompositeState as useCompositeState, __unstableCompositeItem as CompositeItem, Disabled, TabPanel, - createSlotFill, - __experimentalUseSlotFills as useSlotFills, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { @@ -31,29 +28,19 @@ import { __unstableIframe as Iframe, } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; -import { closeSmall } from '@wordpress/icons'; -import { - useResizeObserver, - useFocusOnMount, - useFocusReturn, - useMergeRefs, -} from '@wordpress/compose'; +import { useResizeObserver } from '@wordpress/compose'; import { useMemo, memo } from '@wordpress/element'; -import { ESCAPE } from '@wordpress/keycodes'; /** * Internal dependencies */ import { unlock } from '../../private-apis'; +import EditorCanvasContainer from '../editor-canvas-container'; const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock( blockEditorPrivateApis ); -const SLOT_FILL_NAME = 'EditSiteStyleBook'; -const { Slot: StyleBookSlot, Fill: StyleBookFill } = - createSlotFill( SLOT_FILL_NAME ); - // The content area of the Style Book is rendered within an iframe so that global styles // are applied to elements within the entire content area. To support elements that are // not part of the block previews, such as headings and layout for the block previews, @@ -174,11 +161,8 @@ function getExamples() { return [ headingsExample, ...otherExamples ]; } -function StyleBook( { isSelected, onSelect, onClose } ) { +function StyleBook( { isSelected, onSelect } ) { const [ resizeObserver, sizes ] = useResizeObserver(); - const focusOnMountRef = useFocusOnMount( 'firstElement' ); - const sectionFocusReturnRef = useFocusReturn(); - const [ textColor ] = useGlobalStyle( 'color.text' ); const [ backgroundColor ] = useGlobalStyle( 'color.background' ); const examples = useMemo( getExamples, [] ); @@ -207,17 +191,9 @@ function StyleBook( { isSelected, onSelect, onClose } ) { [ originalSettings ] ); - function closeOnEscape( event ) { - if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) { - event.preventDefault(); - onClose(); - } - } - return ( - - { /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */ } -
+
600, } ) } @@ -225,21 +201,8 @@ function StyleBook( { isSelected, onSelect, onClose } ) { color: textColor, background: backgroundColor, } } - aria-label={ __( 'Style Book' ) } - onKeyDown={ closeOnEscape } - ref={ useMergeRefs( [ - sectionFocusReturnRef, - focusOnMountRef, - ] ) } > { resizeObserver } -
-
+
+ ); } @@ -365,11 +328,4 @@ const Example = ( { composite, id, title, blocks, isSelected, onClick } ) => { ); }; -function useHasStyleBook() { - const fills = useSlotFills( SLOT_FILL_NAME ); - return !! fills?.length; -} - -StyleBook.Slot = StyleBookSlot; export default StyleBook; -export { useHasStyleBook }; diff --git a/packages/edit-site/src/components/style-book/style.scss b/packages/edit-site/src/components/style-book/style.scss index cf84655258725..6dcc1fec328ab 100644 --- a/packages/edit-site/src/components/style-book/style.scss +++ b/packages/edit-site/src/components/style-book/style.scss @@ -1,21 +1,3 @@ -.edit-site-style-book { - background: $white; // Fallback color, overridden by JavaScript. - border-radius: $radius-block-ui; - bottom: 0; - left: 0; - overflow: hidden; - position: absolute; - right: 0; - top: 0; - transition: all 0.3s; // Match .block-editor-iframe__body transition. -} - -.edit-site-style-book__close-button { - position: absolute; - right: $grid-unit-10; - top: math.div($grid-unit-60 - $button-size, 2); // ( tab height - button size ) / 2 -} - .edit-site-style-book__tab-panel { .components-tab-panel__tabs { background: $white; diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index aeaf1d72833d7..952c1852ae305 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -27,3 +27,17 @@ export const setCanvasMode = dispatch.setIsListViewOpened( true ); } }; + +/** + * Action that switches the editor canvas container view. + * + * @param {?string} view Editor canvas container view. + */ +export const setEditorCanvasContainerView = + ( view ) => + ( { dispatch } ) => { + dispatch( { + type: 'SET_EDITOR_CANVAS_CONTAINER_VIEW', + view, + } ); + }; diff --git a/packages/edit-site/src/store/private-selectors.js b/packages/edit-site/src/store/private-selectors.js index fa4085d17f89c..1f1f6e999fdb2 100644 --- a/packages/edit-site/src/store/private-selectors.js +++ b/packages/edit-site/src/store/private-selectors.js @@ -8,3 +8,14 @@ export function getCanvasMode( state ) { return state.canvasMode; } + +/** + * Returns the editor canvas container view. + * + * @param {Object} state Global application state. + * + * @return {string} Editor canvas container view. + */ +export function getEditorCanvasContainerView( state ) { + return state.editorCanvasContainerView; +} diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index fee3a8fcc5618..a46d215f90507 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -140,6 +140,23 @@ function canvasMode( state = 'init', action ) { return state; } +/** + * Reducer used to track the site editor canvas container view. + * Default is `undefined`, denoting the default, visual block editor. + * This could be, for example, `'style-book'` (the style book). + * + * @param {string|undefined} state Current state. + * @param {Object} action Dispatched action. + */ +function editorCanvasContainerView( state = undefined, action ) { + switch ( action.type ) { + case 'SET_EDITOR_CANVAS_CONTAINER_VIEW': + return action.view; + } + + return state; +} + export default combineReducers( { deviceType, settings, @@ -148,4 +165,5 @@ export default combineReducers( { listViewPanel, saveViewPanel, canvasMode, + editorCanvasContainerView, } ); diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index a68971f901157..30abf4057b80e 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -31,6 +31,7 @@ @import "./components/sidebar-navigation-screen-navigation-menus/style.scss"; @import "./components/site-icon/style.scss"; @import "./components/style-book/style.scss"; +@import "./components/editor-canvas-container/style.scss"; @import "./hooks/push-changes-to-global-styles/style.scss"; html #wpadminbar {