diff --git a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js index 46719a00c16aad..7c88fee0d5b727 100644 --- a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js +++ b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js @@ -60,11 +60,10 @@ export default function useEditorIframeProps() { } ); } }, - onClick: () => { + onClick: () => history.push( { ...params, canvas: 'edit' }, undefined, { transition: 'canvas-mode-edit-transition', - } ); - }, + } ), onClickCapture: ( event ) => { if ( currentPostIsTrashed ) { event.preventDefault(); diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 9ca88f40f1f001..2edea0fdbc3da3 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -19,7 +19,8 @@ import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; import { moreVertical } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, Fragment } from '@wordpress/element'; +import { usePrevious } from '@wordpress/compose'; /** * Internal dependencies @@ -291,18 +292,52 @@ function GlobalStylesEditorCanvasContainerLink() { }, [ editorCanvasContainerView, isRevisionsOpen, goTo ] ); } -function GlobalStylesUI() { +function useNavigatorSync( parentPath, onPathChange ) { + const navigator = useNavigator(); + const { path: childPath } = navigator.location; + const previousParentPath = usePrevious( parentPath ); + const previousChildPath = usePrevious( childPath ); + useEffect( () => { + if ( parentPath !== childPath ) { + if ( parentPath !== previousParentPath ) { + navigator.goTo( parentPath ); + } else if ( childPath !== previousChildPath ) { + onPathChange( childPath ); + } + } + }, [ + onPathChange, + parentPath, + previousChildPath, + previousParentPath, + childPath, + navigator, + ] ); +} + +// This component is used to wrap the hook in order to conditionally execute it +// when the parent component is used on controlled mode. +function NavigationSync( { path: parentPath, onPathChange, children } ) { + useNavigatorSync( parentPath, onPathChange ); + return children; +} + +function GlobalStylesUI( { path, onPathChange } ) { const blocks = getBlockTypes(); const editorCanvasContainerView = useSelect( ( select ) => unlock( select( editSiteStore ) ).getEditorCanvasContainerView(), [] ); + return ( + { path && onPathChange && ( + + ) } diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 551d1448fde5c9..cbc0a4661bf3e7 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -125,7 +125,12 @@ export default function Layout( { route } ) { isResizableFrameOversized } /> - + { areas.sidebar } diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js new file mode 100644 index 00000000000000..afa9f489dde22b --- /dev/null +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js @@ -0,0 +1,145 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useMemo, useState } from '@wordpress/element'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useViewportMatch } from '@wordpress/compose'; +import { + Button, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import GlobalStylesUI from '../global-styles/ui'; +import Page from '../page'; +import { unlock } from '../../lock-unlock'; +import StyleBook from '../style-book'; +import { STYLE_BOOK_COLOR_GROUPS } from '../style-book/constants'; + +const { useLocation, useHistory } = unlock( routerPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); +const GLOBAL_STYLES_PATH_PREFIX = '/wp_global_styles'; + +const GlobalStylesPageActions = ( { + isStyleBookOpened, + setIsStyleBookOpened, +} ) => { + return ( + + { __( 'Preview' ) } + + } + > + setIsStyleBookOpened( true ) } + defaultChecked + > + { __( 'Style book' ) } + + { __( 'Preview blocks and styles.' ) } + + + setIsStyleBookOpened( false ) } + > + { __( 'Site' ) } + + { __( 'Preview your site.' ) } + + + + ); +}; + +export default function GlobalStylesUIWrapper() { + const { params } = useLocation(); + const history = useHistory(); + const { canvas = 'view' } = params; + const [ isStyleBookOpened, setIsStyleBookOpened ] = useState( false ); + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const pathWithPrefix = params.path; + const [ path, onPathChange ] = useMemo( () => { + const processedPath = pathWithPrefix.substring( + GLOBAL_STYLES_PATH_PREFIX.length + ); + return [ + processedPath ? processedPath : '/', + ( newPath ) => { + history.push( { + path: + ! newPath || newPath === '/' + ? GLOBAL_STYLES_PATH_PREFIX + : `${ GLOBAL_STYLES_PATH_PREFIX }${ newPath }`, + } ); + }, + ]; + }, [ pathWithPrefix, history ] ); + + return ( + <> + + ) : null + } + className="edit-site-styles" + title={ __( 'Styles' ) } + > + + + { canvas === 'view' && isStyleBookOpened && ( + + // Match '/blocks/core%2Fbutton' and + // '/blocks/core%2Fbutton/typography', but not + // '/blocks/core%2Fbuttons'. + path === + `/wp_global_styles/blocks/${ encodeURIComponent( + blockName + ) }` || + path.startsWith( + `/wp_global_styles/blocks/${ encodeURIComponent( + blockName + ) }/` + ) + } + path={ path } + onSelect={ ( blockName ) => { + if ( + STYLE_BOOK_COLOR_GROUPS.find( + ( group ) => group.slug === blockName + ) + ) { + // Go to color palettes Global Styles. + onPathChange( '/colors/palette' ); + return; + } + + // Now go to the selected block. + onPathChange( + `/blocks/${ encodeURIComponent( blockName ) }` + ); + } } + /> + ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss new file mode 100644 index 00000000000000..88aa9ddf0c1618 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss @@ -0,0 +1,35 @@ +.edit-site-styles .edit-site-page-content { + .edit-site-global-styles-screen-root { + box-shadow: none; + & > div > hr { + display: none; + } + } + .edit-site-global-styles-sidebar__navigator-provider { + .components-tools-panel { + border-top: none; + } + overflow-y: auto; + padding-left: 0; + padding-right: 0; + + .edit-site-global-styles-sidebar__navigator-screen { + padding-top: $grid-unit-15; + padding-left: $grid-unit-15; + padding-right: $grid-unit-15; + padding-bottom: $grid-unit-15; + outline: none; + } + } + .edit-site-page-header { + padding-left: $grid-unit-60; + padding-right: $grid-unit-60; + @container (max-width: 430px) { + padding-left: $grid-unit-30; + padding-right: $grid-unit-30; + } + } + .edit-site-sidebar-button { + color: $gray-900; + } +} diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 016027ef715a45..202de5300076c1 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -7,7 +7,7 @@ &:hover, &:focus, - &[aria-current] { + &[aria-current="true"] { color: $gray-200; background: $gray-800; @@ -16,7 +16,7 @@ } } - &[aria-current] { + &[aria-current="true"] { background: var(--wp-admin-theme-color); color: $white; } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index 6579107a60e55f..3dc93ff4d4df63 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -2,12 +2,9 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { edit, seen } from '@wordpress/icons'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { useViewportMatch } from '@wordpress/compose'; import { useCallback } from '@wordpress/element'; -import { store as editorStore } from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { privateApis as routerPrivateApis } from '@wordpress/router'; @@ -17,18 +14,14 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; -import SidebarButton from '../sidebar-button'; import SidebarNavigationItem from '../sidebar-navigation-item'; -import StyleBook from '../style-book'; import useGlobalStylesRevisions from '../global-styles/screen-revisions/use-global-styles-revisions'; import SidebarNavigationScreenDetailsFooter from '../sidebar-navigation-screen-details-footer'; -import SidebarNavigationScreenGlobalStylesContent from './content'; +import { MainSidebarNavigationContent } from '../sidebar-navigation-screen-main'; const { useLocation, useHistory } = unlock( routerPrivateApis ); export function SidebarNavigationItemGlobalStyles( props ) { - const { openGeneralSidebar } = useDispatch( editSiteStore ); - const history = useHistory(); const { params } = useLocation(); const hasGlobalStyleVariations = useSelect( ( select ) => @@ -43,47 +36,25 @@ export function SidebarNavigationItemGlobalStyles( props ) { { ...props } params={ { path: '/wp_global_styles' } } uid="global-styles-navigation-item" + aria-current={ + params.path && params.path.startsWith( '/wp_global_styles' ) + } /> ); } - return ( - { - // Switch to edit mode. - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - // Open global styles sidebar. - openGeneralSidebar( 'edit-site/global-styles' ); - } } - /> - ); + return ; } -export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { +export default function SidebarNavigationScreenGlobalStyles() { const history = useHistory(); const { params } = useLocation(); - const { canvas = 'view' } = params; const { revisions, isLoading: isLoadingRevisions } = useGlobalStylesRevisions(); const { openGeneralSidebar } = useDispatch( editSiteStore ); - const { setIsListViewOpened } = useDispatch( editorStore ); - const isMobileViewport = useViewportMatch( 'medium', '<' ); const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) ); - const { isStyleBookOpened, revisionsCount } = useSelect( ( select ) => { - const { getEditorCanvasContainerView } = unlock( - select( editSiteStore ) - ); + const { revisionsCount } = useSelect( ( select ) => { const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = select( coreStore ); const globalStylesId = __experimentalGetCurrentGlobalStylesId(); @@ -91,7 +62,6 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) : undefined; return { - isStyleBookOpened: 'style-book' === getEditorCanvasContainerView(), revisionsCount: globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0, }; @@ -115,19 +85,6 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { ] ); }, [ history, params, openGeneralSidebar, setPreference ] ); - const openStyleBook = useCallback( async () => { - await openGlobalStyles(); - // Open the Style Book once the canvas mode is set to edit, - // and the global styles sidebar is open. This ensures that - // the Style Book is not prematurely closed. - setEditorCanvasContainerView( 'style-book' ); - setIsListViewOpened( false ); - }, [ - openGlobalStyles, - setEditorCanvasContainerView, - setIsListViewOpened, - ] ); - const openRevisions = useCallback( async () => { await openGlobalStyles(); // Open the global styles revisions once the canvas mode is set to edit, @@ -142,16 +99,17 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { const modifiedDateTime = revisions?.[ 0 ]?.modified; const shouldShowGlobalStylesFooter = hasRevisions && ! isLoadingRevisions && modifiedDateTime; - return ( <> } + content={ + + } footer={ shouldShowGlobalStylesFooter && ( ) } - actions={ - <> - { ! isMobileViewport && ( - - setEditorCanvasContainerView( - ! isStyleBookOpened - ? 'style-book' - : undefined - ) - } - isPressed={ isStyleBookOpened } - /> - ) } - await openGlobalStyles() } - /> - - } /> - { isStyleBookOpened && ! isMobileViewport && canvas === 'view' && ( - false } - onClick={ openStyleBook } - onSelect={ openStyleBook } - showCloseButton={ false } - showTabs={ false } - /> - ) } ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js index bdfb6ac93b51c4..49e60d44047326 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js @@ -21,6 +21,51 @@ import { PATTERN_TYPES, } from '../../utils/constants'; +export function MainSidebarNavigationContent() { + return ( + + + { __( 'Navigation' ) } + + + { __( 'Styles' ) } + + + { __( 'Pages' ) } + + + { __( 'Templates' ) } + + + { __( 'Patterns' ) } + + + ); +} + export default function SidebarNavigationScreenMain() { const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) @@ -38,51 +83,7 @@ export default function SidebarNavigationScreenMain() { description={ __( 'Customize the appearance of your website using the block editor.' ) } - content={ - <> - - - { __( 'Navigation' ) } - - - { __( 'Styles' ) } - - - { __( 'Pages' ) } - - - { __( 'Templates' ) } - - - { __( 'Patterns' ) } - - - - } + content={ } /> ); } diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 84820952e1b621..7ecd24719a47bf 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -55,7 +55,7 @@ function createNavState() { }; } -function SidebarContentWrapper( { children } ) { +function SidebarContentWrapper( { children, shouldAnimate } ) { const navState = useContext( SidebarNavigationContext ); const wrapperRef = useRef(); const [ navAnimation, setNavAnimation ] = useState( null ); @@ -66,10 +66,19 @@ function SidebarContentWrapper( { children } ) { setNavAnimation( direction ); }, [ navState ] ); - const wrapperCls = clsx( 'edit-site-sidebar__screen-wrapper', { - 'slide-from-left': navAnimation === 'back', - 'slide-from-right': navAnimation === 'forward', - } ); + const wrapperCls = clsx( + 'edit-site-sidebar__screen-wrapper', + /* + * Some panes do not have sub-panes and therefore + * should not animate when clicked on. + */ + shouldAnimate + ? { + 'slide-from-left': navAnimation === 'back', + 'slide-from-right': navAnimation === 'forward', + } + : {} + ); return (
@@ -78,13 +87,20 @@ function SidebarContentWrapper( { children } ) { ); } -export default function SidebarContent( { routeKey, children } ) { +export default function SidebarContent( { + routeKey, + shouldAnimate, + children, +} ) { const [ navState ] = useState( createNavState ); return (
- + { children }
diff --git a/packages/edit-site/src/components/site-editor-routes/styles-edit.js b/packages/edit-site/src/components/site-editor-routes/styles-edit.js index ff52b957bc3609..e8225a8f526ebd 100644 --- a/packages/edit-site/src/components/site-editor-routes/styles-edit.js +++ b/packages/edit-site/src/components/site-editor-routes/styles-edit.js @@ -3,15 +3,24 @@ */ import Editor from '../editor'; import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; export const stylesEditRoute = { name: 'styles-edit', match: ( params ) => { - return params.path === '/wp_global_styles' && params.canvas === 'edit'; + return ( + params.path && + params.path.startsWith( '/wp_global_styles' ) && + params.canvas !== 'edit' + ); }, areas: { + content: , sidebar: , preview: , mobile: , }, + widths: { + content: 380, + }, }; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-view.js b/packages/edit-site/src/components/site-editor-routes/styles-view.js index 856a610eb23677..cc9411eb8144c0 100644 --- a/packages/edit-site/src/components/site-editor-routes/styles-view.js +++ b/packages/edit-site/src/components/site-editor-routes/styles-view.js @@ -3,14 +3,24 @@ */ import Editor from '../editor'; import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; export const stylesViewRoute = { name: 'styles-view', match: ( params ) => { - return params.path === '/wp_global_styles' && params.canvas !== 'edit'; + return ( + params.path && + params.path.startsWith( '/wp_global_styles' ) && + params.canvas !== 'edit' + ); }, areas: { + content: , sidebar: , preview: , + mobile: , + }, + widths: { + content: 380, }, }; diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index e9660323b83734..9918c169ff6ab0 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -24,7 +24,14 @@ import { import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; import { useResizeObserver } from '@wordpress/compose'; -import { useMemo, useState, memo, useContext } from '@wordpress/element'; +import { + useMemo, + useState, + memo, + useContext, + useRef, + useLayoutEffect, +} from '@wordpress/element'; import { ENTER, SPACE } from '@wordpress/keycodes'; /** @@ -53,6 +60,48 @@ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } +/** + * Scrolls to a section within an iframe. + * + * @param {string} anchorId The id of the element to scroll to. + * @param {HTMLIFrameElement} iframe The target iframe. + */ +const scrollToSection = ( anchorId, iframe ) => { + if ( ! iframe || ! iframe?.contentDocument ) { + return; + } + + const element = iframe.contentDocument.getElementById( anchorId ); + if ( element ) { + element.scrollIntoView( { + behavior: 'smooth', + } ); + } +}; + +/** + * Parses a Block Editor navigation path to extract the block name and + * build a style book navigation path. The object can be extended to include a category, + * representing a style book tab/section. + * + * @param {string} path An internal Block Editor navigation path. + * @return {null|{block: string}} An object containing the example to navigate to. + */ +const getStyleBookNavigationFromPath = ( path ) => { + if ( path && typeof path === 'string' ) { + let block = path.includes( '/blocks/' ) + ? decodeURIComponent( path.split( '/blocks/' )[ 1 ] ) + : null; + // Default to theme-colors if the path ends with /colors. + block = path.endsWith( '/colors' ) ? 'theme-colors' : block; + + return { + block, + }; + } + return null; +}; + /** * Retrieves colors, gradients, and duotone filters from Global Styles. * The inclusion of default (Core) palettes is controlled by the relevant @@ -137,6 +186,7 @@ function StyleBook( { onClose, showTabs = true, userConfig = {}, + path = '', } ) { const [ resizeObserver, sizes ] = useResizeObserver(); const [ textColor ] = useGlobalStyle( 'color.text' ); @@ -154,6 +204,7 @@ function StyleBook( { ); const { base: baseConfig } = useContext( GlobalStylesContext ); + const goTo = getStyleBookNavigationFromPath( path ); const mergedConfig = useMemo( () => { if ( ! isObjectEmpty( userConfig ) && ! isObjectEmpty( baseConfig ) ) { @@ -228,6 +279,7 @@ function StyleBook( { settings={ settings } sizes={ sizes } title={ tab.title } + goTo={ goTo } /> ) ) } @@ -240,6 +292,7 @@ function StyleBook( { onSelect={ onSelect } settings={ settings } sizes={ sizes } + goTo={ goTo } /> ) }
@@ -256,9 +309,11 @@ const StyleBookBody = ( { settings, sizes, title, + goTo, } ) => { const [ isFocused, setIsFocused ] = useState( false ); - + const [ hasIframeLoaded, setHasIframeLoaded ] = useState( false ); + const iframeRef = useRef( null ); // The presence of an `onClick` prop indicates that the Style Book is being used as a button. // In this case, add additional props to the iframe to make it behave like a button. const buttonModeProps = { @@ -287,8 +342,17 @@ const StyleBookBody = ( { readonly: true, }; + const handleLoad = () => setHasIframeLoaded( true ); + useLayoutEffect( () => { + if ( goTo?.block && hasIframeLoaded && iframeRef?.current ) { + scrollToSection( `example-${ goTo?.block }`, iframeRef?.current ); + } + }, [ iframeRef?.current, goTo?.block, scrollToSection, hasIframeLoaded ] ); + return (