From e7590362d3a97e232e8b63efd9487f38fc4936e3 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 4 Apr 2024 14:51:08 +0200 Subject: [PATCH 01/14] Site Editor: create router adapter for sidebar --- package-lock.json | 14 ++ .../navigator-provider/component.tsx | 21 +- packages/components/src/navigator/types.ts | 4 + packages/edit-site/package.json | 1 + .../edit-site/src/components/layout/index.js | 6 +- .../edit-site/src/components/layout/router.js | 24 +- .../edit-site/src/components/sidebar/index.js | 17 +- .../use-sync-path-with-url.js | 232 +++++++++++------- 8 files changed, 189 insertions(+), 130 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55f2e0ad54118..eb12dbcc3e997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54165,6 +54165,7 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", + "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" }, "engines": { @@ -54175,6 +54176,11 @@ "react-dom": "^18.0.0" } }, + "packages/edit-site/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", "version": "5.32.0", @@ -69260,7 +69266,15 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", + "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + } } }, "@wordpress/edit-widgets": { diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx index 15eb4d6bd3b1d..8134a232ee9d2 100644 --- a/packages/components/src/navigator/navigator-provider/component.tsx +++ b/packages/components/src/navigator/navigator-provider/component.tsx @@ -203,13 +203,7 @@ function routerReducer( return { screens, locationHistory, matchedPath }; } -function UnconnectedNavigatorProvider( - props: WordPressComponentProps< NavigatorProviderProps, 'div' >, - forwardedRef: ForwardedRef< any > -) { - const { initialPath, children, className, ...otherProps } = - useContextSystem( props, 'NavigatorProvider' ); - +function useMemoryRouter( initialPath: string ): NavigatorContextType { const [ routerState, dispatch ] = useReducer( routerReducer, initialPath, @@ -238,7 +232,7 @@ function UnconnectedNavigatorProvider( const { locationHistory, matchedPath } = routerState; - const navigatorContextValue: NavigatorContextType = useMemo( + return useMemo( () => ( { location: { ...locationHistory[ locationHistory.length - 1 ], @@ -250,6 +244,17 @@ function UnconnectedNavigatorProvider( } ), [ locationHistory, matchedPath, methods ] ); +} + +function UnconnectedNavigatorProvider( + props: WordPressComponentProps< NavigatorProviderProps, 'div' >, + forwardedRef: ForwardedRef< any > +) { + const { initialPath, children, className, router, ...otherProps } = + useContextSystem( props, 'NavigatorProvider' ); + + const memoryRouter = useMemoryRouter( initialPath ); + const navigatorContextValue = router ?? memoryRouter; const cx = useCx(); const classes = useMemo( diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts index 557f8074fd42e..eec4daeff6b3f 100644 --- a/packages/components/src/navigator/types.ts +++ b/packages/components/src/navigator/types.ts @@ -45,6 +45,10 @@ export type NavigatorProviderProps = { * The initial active path. */ initialPath: string; + /** + * External router + */ + router?: NavigatorContext; /** * The children elements. */ diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index c556264207676..6221df6ca5e40 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -76,6 +76,7 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", + "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" }, "peerDependencies": { diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 51f55302f16e7..88563eb2e4da6 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -35,7 +35,6 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ -import Sidebar from '../sidebar'; import ErrorBoundary from '../error-boundary'; import { store as editSiteStore } from '../../store'; import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-entity-from-url'; @@ -224,8 +223,7 @@ export default function Layout() { The NavigableRegion must always be rendered and not use `inert` otherwise `useNavigateRegions` will fail. */ } - { ( ! isMobileViewport || - ( isMobileViewport && ! areas.mobile ) ) && ( + { ( ! isMobileViewport || ! areas.mobile ) && ( - + { areas.sidebar } ) } diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index 3482c122c3630..fabb1341865cf 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -12,7 +12,8 @@ import Editor from '../editor'; import PagePages from '../page-pages'; import PagePatterns from '../page-patterns'; import PageTemplatesTemplateParts from '../page-templates-template-parts'; - +import Sidebar from '../sidebar'; +import { useRouter } from '../sync-state-with-url/use-sync-path-with-url'; import { TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE, @@ -25,6 +26,7 @@ export default function useLayoutAreas() { const history = useHistory(); const { params } = useLocation(); const { postType, postId, path, layout, isCustom, canvas } = params ?? {}; + const router = useRouter(); // Note: Since "sidebar" is not yet supported here, // returning undefined from "mobile" means show the sidebar. @@ -35,6 +37,7 @@ export default function useLayoutAreas() { return { key: 'pages-list', areas: { + sidebar: , content: , preview: isListLayout && ( , preview: , - mobile: - canvas === 'edit' ? ( - - ) : undefined, + mobile: canvas === 'edit' && ( + + ), }, }; } @@ -82,6 +85,7 @@ export default function useLayoutAreas() { return { key: 'templates-list', areas: { + sidebar: , content: ( , content: ( , content: , mobile: , }, @@ -143,11 +149,11 @@ export default function useLayoutAreas() { return { key: 'default', areas: { + sidebar: , preview: , - mobile: - canvas === 'edit' ? ( - - ) : undefined, + mobile: canvas === 'edit' && ( + + ), }, }; } diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 31a758218acfa..29fd595f09794 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -6,12 +6,11 @@ import classNames from 'classnames'; /** * WordPress dependencies */ -import { memo, useRef } from '@wordpress/element'; +import { memo } from '@wordpress/element'; import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, } from '@wordpress/components'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useViewportMatch } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -22,21 +21,15 @@ import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; import SidebarNavigationScreenTemplate from '../sidebar-navigation-screen-template'; import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; import SidebarNavigationScreenPattern from '../sidebar-navigation-screen-pattern'; -import useSyncPathWithURL, { - getPathFromURL, -} from '../sync-state-with-url/use-sync-path-with-url'; import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; import SaveHub from '../save-hub'; -import { unlock } from '../../lock-unlock'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import DataViewsSidebarContent from '../sidebar-dataviews'; import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; -const { useLocation } = unlock( routerPrivateApis ); - function SidebarScreenWrapper( { className, ...props } ) { return ( diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js index 2aceee88e8acd..689d486bfff65 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js @@ -1,8 +1,12 @@ +/** + * External dependencies + */ +import { match } from 'path-to-regexp'; + /** * WordPress dependencies */ -import { __experimentalUseNavigator as useNavigator } from '@wordpress/components'; -import { useEffect, useRef } from '@wordpress/element'; +import { useReducer, useMemo } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; /** @@ -41,107 +45,145 @@ export function getPathFromURL( urlParams ) { return path; } -function isSubset( subset, superset ) { - return Object.entries( subset ).every( ( [ key, value ] ) => { - return superset[ key ] === value; - } ); +function getParamsFromPath( path, params ) { + if ( params?.postType && params?.postId ) { + return { + postType: params?.postType, + postId: params?.postId, + path: undefined, + layout: undefined, + }; + } else if ( path.startsWith( '/page/' ) && params?.postId ) { + return { + postType: 'page', + postId: params?.postId, + path: undefined, + layout: undefined, + }; + } else if ( path === '/patterns' ) { + return { + postType: undefined, + postId: undefined, + canvas: undefined, + path, + }; + } else if ( + // These sidebar paths are special in the sense that the url in these pages may or may not have a postId and we need to retain it if it has. + // The "type" property should be kept as well. + path === '/page' || + path === '/wp_template' || + path === '/wp_template_part/all' + ) { + return { + postType: undefined, + categoryType: undefined, + categoryId: undefined, + path, + }; + } + return { + postType: undefined, + postId: undefined, + categoryType: undefined, + categoryId: undefined, + layout: undefined, + path: path === '/' ? undefined : path, + }; +} + +function matchPath( path, pattern ) { + const matcher = match( pattern, { decode: decodeURIComponent } ); + return matcher( path ); +} + +function patternMatch( path, screens ) { + for ( const screen of screens ) { + const matched = matchPath( path, screen.path ); + if ( matched ) { + return { params: matched.params, id: screen.id }; + } + } + + return undefined; +} + +function findParent( path, screens ) { + if ( ! path.startsWith( '/' ) ) { + return undefined; + } + const pathParts = path.split( '/' ); + while ( pathParts.length > 1 ) { + pathParts.pop(); + const parentPath = pathParts.join( '/' ) || '/'; + if ( + screens.some( + ( screen ) => matchPath( parentPath, screen.path ) !== false + ) + ) { + return parentPath; + } + } + + return undefined; } -export default function useSyncPathWithURL() { +export function useRouter() { const history = useHistory(); - const { params: urlParams } = useLocation(); - const { - location: navigatorLocation, - params: navigatorParams, - goTo, - } = useNavigator(); - const isMounting = useRef( true ); - - useEffect( - () => { - // The navigatorParams are only initially filled properly when the - // navigator screens mount. so we ignore the first synchronisation. - if ( isMounting.current ) { - isMounting.current = false; - return; - } + const { params } = useLocation(); + const path = getPathFromURL( params ); + const [ screens, dispatch ] = useReducer( ( state, action ) => { + switch ( action.type ) { + case 'add': + return [ ...state, action.screen ]; + case 'remove': + return state.filter( ( s ) => s.id !== action.screen.id ); + default: + return state; + } + }, [] ); - function updateUrlParams( newUrlParams ) { - if ( isSubset( newUrlParams, urlParams ) ) { - return; - } - const updatedParams = { - ...urlParams, - ...newUrlParams, - }; - history.push( updatedParams ); - } + const matchedPath = useMemo( () => { + return path !== undefined ? patternMatch( path, screens ) : undefined; + }, [ path, screens ] ); - if ( navigatorParams?.postType && navigatorParams?.postId ) { - updateUrlParams( { - postType: navigatorParams?.postType, - postId: navigatorParams?.postId, - path: undefined, - layout: undefined, - } ); - } else if ( - navigatorLocation.path.startsWith( '/page/' ) && - navigatorParams?.postId - ) { - updateUrlParams( { - postType: 'page', - postId: navigatorParams?.postId, - path: undefined, - layout: undefined, - } ); - } else if ( navigatorLocation.path === '/patterns' ) { - updateUrlParams( { - postType: undefined, - postId: undefined, - canvas: undefined, - path: navigatorLocation.path, - } ); - } else if ( - // These sidebar paths are special in the sense that the url in these pages may or may not have a postId and we need to retain it if it has. - // The "type" property should be kept as well. - navigatorLocation.path === '/page' || - navigatorLocation.path === '/wp_template' || - navigatorLocation.path === '/wp_template_part/all' - ) { - updateUrlParams( { - postType: undefined, - categoryType: undefined, - categoryId: undefined, - path: navigatorLocation.path, - } ); - } else { - updateUrlParams( { - postType: undefined, - postId: undefined, - categoryType: undefined, - categoryId: undefined, - layout: undefined, - path: - navigatorLocation.path === '/' - ? undefined - : navigatorLocation.path, + const goMethods = useMemo( () => { + const goTo = ( p ) => { + const matched = patternMatch( p, screens ); + history.push( getParamsFromPath( p, matched?.params ?? {} ) ); + }; + + const goToParent = () => { + const parentPath = findParent( path, screens ); + if ( parentPath !== undefined ) { + goTo( parentPath, { + isBack: true, } ); } - }, - // Trigger only when navigator changes to prevent infinite loops. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ navigatorLocation?.path, navigatorParams ] + }; + + const goBack = () => { + history.back(); + }; + + return { goTo, goToParent, goBack }; + }, [ history, screens, path ] ); + + const screenMethods = useMemo( + () => ( { + addScreen: ( screen ) => dispatch( { type: 'add', screen } ), + removeScreen: ( screen ) => dispatch( { type: 'remove', screen } ), + } ), + [] ); - useEffect( - () => { - const path = getPathFromURL( urlParams ); - if ( navigatorLocation.path !== path ) { - goTo( path ); - } - }, - // Trigger only when URL changes to prevent infinite loops. - // eslint-disable-next-line react-hooks/exhaustive-deps - [ urlParams ] + return useMemo( + () => ( { + location: { path }, + params: matchedPath?.params ?? {}, + match: matchedPath?.id, + ...goMethods, + ...screenMethods, + } ), + [ path, matchedPath, goMethods, screenMethods ] ); } From 4db02eff9f93a2f8c14159033a790a9b9fe999fd Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 11 Apr 2024 09:36:20 +0200 Subject: [PATCH 02/14] Dissolve the Sidebar component, put items into areas.sidebar --- .../navigator/navigator-screen/component.tsx | 15 ++- .../edit-site/src/components/layout/index.js | 15 ++- .../edit-site/src/components/layout/router.js | 76 +++++++++++-- .../index.js | 7 +- .../edit-site/src/components/sidebar/index.js | 106 ------------------ .../use-sync-path-with-url.js | 47 ++++---- 6 files changed, 114 insertions(+), 152 deletions(-) delete mode 100644 packages/edit-site/src/components/sidebar/index.js diff --git a/packages/components/src/navigator/navigator-screen/component.tsx b/packages/components/src/navigator/navigator-screen/component.tsx index be5c4bfaf41ec..5362281db8b0d 100644 --- a/packages/components/src/navigator/navigator-screen/component.tsx +++ b/packages/components/src/navigator/navigator-screen/component.tsx @@ -33,7 +33,6 @@ function UnconnectedNavigatorScreen( props: WordPressComponentProps< NavigatorScreenProps, 'div', false >, forwardedRef: ForwardedRef< any > ) { - const screenId = useId(); const { children, className, path, ...otherProps } = useContextSystem( props, 'NavigatorScreen' @@ -41,10 +40,13 @@ function UnconnectedNavigatorScreen( const { location, match, addScreen, removeScreen } = useContext( NavigatorContext ); - const isMatch = match === screenId; const wrapperRef = useRef< HTMLDivElement >( null ); + const screenId = useId(); useEffect( () => { + if ( ! path ) { + return; + } const screen = { id: screenId, path: escapeAttribute( path ), @@ -53,6 +55,7 @@ function UnconnectedNavigatorScreen( return () => removeScreen( screen ); }, [ screenId, path, addScreen, removeScreen ] ); + const isMatch = ! path || match === screenId; const isRTL = isRTLFn(); const { isInitial, isBack } = location; const cx = useCx(); @@ -131,11 +134,15 @@ function UnconnectedNavigatorScreen( const mergedWrapperRef = useMergeRefs( [ forwardedRef, wrapperRef ] ); - return isMatch ? ( + if ( ! isMatch ) { + return null; + } + + return ( { children } - ) : null; + ); } /** diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 88563eb2e4da6..7e203449d6009 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -11,6 +11,8 @@ import { __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence, __unstableUseNavigateRegions as useNavigateRegions, + __experimentalNavigatorProvider as NavigatorProvider, + __experimentalNavigatorScreen as NavigatorScreen, } from '@wordpress/components'; import { useReducedMotion, @@ -50,6 +52,8 @@ import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands import { useIsSiteEditorLoading } from './hooks'; import useLayoutAreas from './router'; import useMovingAnimation from './animation'; +import { useRouter } from '../sync-state-with-url/use-sync-path-with-url'; +import SaveHub from '../save-hub'; const { useCommands } = unlock( coreCommandsPrivateApis ); const { useCommandContext } = unlock( commandsPrivateApis ); @@ -118,6 +122,7 @@ export default function Layout() { const animationRef = useMovingAnimation( { triggerAnimationOnChange: canvasMode + '__' + routeKey, } ); + const router = useRouter(); // This determines which animation variant should apply to the header. // There is also a `isDistractionFreeHovering` state that gets priority @@ -246,7 +251,15 @@ export default function Layout() { } } className="edit-site-layout__sidebar" > - { areas.sidebar } + + + { areas.sidebar } + + + ) } diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index fabb1341865cf..df1b4f6884e61 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -12,8 +13,17 @@ import Editor from '../editor'; import PagePages from '../page-pages'; import PagePatterns from '../page-patterns'; import PageTemplatesTemplateParts from '../page-templates-template-parts'; -import Sidebar from '../sidebar'; -import { useRouter } from '../sync-state-with-url/use-sync-path-with-url'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; +import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; +import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; +import SidebarNavigationScreenTemplate from '../sidebar-navigation-screen-template'; +import SidebarNavigationScreenPattern from '../sidebar-navigation-screen-pattern'; +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; +import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; +import DataViewsSidebarContent from '../sidebar-dataviews'; import { TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE, @@ -26,7 +36,6 @@ export default function useLayoutAreas() { const history = useHistory(); const { params } = useLocation(); const { postType, postId, path, layout, isCustom, canvas } = params ?? {}; - const router = useRouter(); // Note: Since "sidebar" is not yet supported here, // returning undefined from "mobile" means show the sidebar. @@ -37,7 +46,14 @@ export default function useLayoutAreas() { return { key: 'pages-list', areas: { - sidebar: , + sidebar: postId ? ( + + ) : ( + } + /> + ), content: , preview: isListLayout && ( ; + } else if ( postType === 'wp_template' ) { + sidebar = ; + } else { + sidebar = ; + } return { key: 'page', areas: { - sidebar: , + sidebar, preview: , mobile: canvas === 'edit' && ( @@ -85,7 +109,11 @@ export default function useLayoutAreas() { return { key: 'templates-list', areas: { - sidebar: , + sidebar: postId ? ( + + ) : ( + + ), content: ( , + sidebar: ( + + ), content: ( , + sidebar: , content: , mobile: , }, }; } + // Styles + if ( path === '/wp_global_styles' ) { + return { + key: 'styles', + areas: { + sidebar: , + preview: , + mobile: canvas === 'edit' && ( + + ), + }, + }; + } + + // Navigation + if ( path === '/navigation' ) { + return { + key: 'styles', + areas: { + sidebar: , + preview: , + mobile: canvas === 'edit' && ( + + ), + }, + }; + } + // Fallback shows the home page preview return { key: 'default', areas: { - sidebar: , + sidebar: , preview: , mobile: canvas === 'edit' && ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js index c50f3065e3279..76c9631a65301 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js @@ -3,8 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; - -import { __experimentalUseNavigator as useNavigator } from '@wordpress/components'; import { privateApis as routerPrivateApis } from '@wordpress/router'; /** @@ -39,10 +37,7 @@ const config = { const { useLocation } = unlock( routerPrivateApis ); -export default function SidebarNavigationScreenTemplatesBrowse() { - const { - params: { postType }, - } = useNavigator(); +export default function SidebarNavigationScreenTemplatesBrowse( { postType } ) { const { params: { didAccessPatternsPage, activeView = 'all' }, } = useLocation(); diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js deleted file mode 100644 index 29fd595f09794..0000000000000 --- a/packages/edit-site/src/components/sidebar/index.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * External dependencies - */ -import classNames from 'classnames'; - -/** - * WordPress dependencies - */ -import { memo } from '@wordpress/element'; -import { - __experimentalNavigatorProvider as NavigatorProvider, - __experimentalNavigatorScreen as NavigatorScreen, -} from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; -import SidebarNavigationScreenTemplate from '../sidebar-navigation-screen-template'; -import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; -import SidebarNavigationScreenPattern from '../sidebar-navigation-screen-pattern'; -import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; -import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; -import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; -import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; -import SaveHub from '../save-hub'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; - -function SidebarScreenWrapper( { className, ...props } ) { - return ( - - ); -} - -function SidebarScreens() { - const isMobileViewport = useViewportMatch( 'medium', '<' ); - - return ( - <> - - - - - - - - - - - - - - } - /> - - - - - - - - { ! isMobileViewport && ( - - - - ) } - - - - - - - - - - - ); -} - -function Sidebar( { router } ) { - return ( - <> - - - - - - ); -} - -export default memo( Sidebar ); diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js index 689d486bfff65..9ebe25dad4ded 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js @@ -6,7 +6,7 @@ import { match } from 'path-to-regexp'; /** * WordPress dependencies */ -import { useReducer, useMemo } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; /** @@ -21,6 +21,20 @@ import { const { useLocation, useHistory } = unlock( routerPrivateApis ); +const SCREENS = [ + '/', + '/wp_global_styles', + '/patterns', + '/navigation', + '/navigation/:postType/:postId', + '/page', + '/page/:postId', + '/:postType(wp_template_part)/all', + '/:postType(wp_template_part|wp_block)/:postId', + '/:postType(wp_template)', + '/:postType(wp_template)/:postId', +].map( ( path, id ) => ( { id, path } ) ); + export function getPathFromURL( urlParams ) { let path = urlParams?.path ?? '/'; @@ -131,29 +145,19 @@ export function useRouter() { const history = useHistory(); const { params } = useLocation(); const path = getPathFromURL( params ); - const [ screens, dispatch ] = useReducer( ( state, action ) => { - switch ( action.type ) { - case 'add': - return [ ...state, action.screen ]; - case 'remove': - return state.filter( ( s ) => s.id !== action.screen.id ); - default: - return state; - } - }, [] ); const matchedPath = useMemo( () => { - return path !== undefined ? patternMatch( path, screens ) : undefined; - }, [ path, screens ] ); + return path !== undefined ? patternMatch( path, SCREENS ) : undefined; + }, [ path ] ); const goMethods = useMemo( () => { const goTo = ( p ) => { - const matched = patternMatch( p, screens ); + const matched = patternMatch( p, SCREENS ); history.push( getParamsFromPath( p, matched?.params ?? {} ) ); }; const goToParent = () => { - const parentPath = findParent( path, screens ); + const parentPath = findParent( path, SCREENS ); if ( parentPath !== undefined ) { goTo( parentPath, { isBack: true, @@ -166,15 +170,7 @@ export function useRouter() { }; return { goTo, goToParent, goBack }; - }, [ history, screens, path ] ); - - const screenMethods = useMemo( - () => ( { - addScreen: ( screen ) => dispatch( { type: 'add', screen } ), - removeScreen: ( screen ) => dispatch( { type: 'remove', screen } ), - } ), - [] - ); + }, [ history, path ] ); return useMemo( () => ( { @@ -182,8 +178,7 @@ export function useRouter() { params: matchedPath?.params ?? {}, match: matchedPath?.id, ...goMethods, - ...screenMethods, } ), - [ path, matchedPath, goMethods, screenMethods ] + [ path, matchedPath, goMethods ] ); } From 50e75af684c65c1d92c6abd39e55e7e0a630f50e Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 09:00:20 +0200 Subject: [PATCH 03/14] Remove unused SidebarNavigationScreenNavigationItem --- .../index.js | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-navigation-item/index.js diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-item/index.js deleted file mode 100644 index 385938597da1f..0000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-item/index.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useDispatch } from '@wordpress/data'; -import { - __experimentalUseNavigator as useNavigator, - ExternalLink, -} from '@wordpress/components'; -import { useEntityRecord } from '@wordpress/core-data'; -import { decodeEntities } from '@wordpress/html-entities'; -import { pencil } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import { unlock } from '../../lock-unlock'; -import { store as editSiteStore } from '../../store'; -import SidebarButton from '../sidebar-button'; - -export default function SidebarNavigationScreenNavigationItem() { - const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); - const { - params: { postType, postId }, - } = useNavigator(); - const { record } = useEntityRecord( 'postType', postType, postId ); - - return ( - setCanvasMode( 'edit' ) } - label={ __( 'Edit' ) } - icon={ pencil } - /> - } - description={ __( - 'Posts are entries listed in reverse chronological order on the site homepage or on the posts page.' - ) } - content={ - <> - { record?.link ? ( - - { record.link } - - ) : null } - { record - ? decodeEntities( record?.description?.rendered ) - : null } - - } - /> - ); -} From d004005cd8d857bbaf639afb64862944069776ce Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 09:07:28 +0200 Subject: [PATCH 04/14] Eliminate usages of useNavigator() --- .../edit-site/src/components/layout/router.js | 9 +- .../sidebar-navigation-screen-main/index.js | 9 +- .../index.js | 11 +- .../use-navigation-menu-handlers.js | 16 +- .../leaf-more-menu.js | 9 +- .../sidebar-navigation-screen-page/index.js | 7 +- .../index.js | 15 +- .../index.js | 15 +- .../index.js | 2 +- .../sidebar-navigation-screen/index.js | 32 +++- .../use-sync-path-with-url.js | 164 +----------------- 11 files changed, 76 insertions(+), 213 deletions(-) diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index df1b4f6884e61..634a5663bf163 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -40,15 +40,13 @@ export default function useLayoutAreas() { // Note: Since "sidebar" is not yet supported here, // returning undefined from "mobile" means show the sidebar. - // Regular page + // Page list if ( path === '/page' ) { const isListLayout = layout === 'list' || ! layout; return { key: 'pages-list', areas: { - sidebar: postId ? ( - - ) : ( + sidebar: ( } @@ -60,7 +58,6 @@ export default function useLayoutAreas() { isLoading={ isSiteEditorLoading } onClick={ () => history.push( { - path, postType: 'page', postId, canvas: 'edit', @@ -88,6 +85,8 @@ export default function useLayoutAreas() { sidebar = ; } else if ( postType === 'wp_template' ) { sidebar = ; + } else if ( postType === 'page' ) { + sidebar = ; } else { sidebar = ; } 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 d6e03f3d36ccb..ae744dd36e8e1 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 @@ -4,12 +4,10 @@ import { __experimentalItemGroup as ItemGroup, __experimentalNavigatorButton as NavigatorButton, - __experimentalUseNavigator as useNavigator, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { layout, symbol, navigation, styles, page } from '@wordpress/icons'; import { useDispatch } from '@wordpress/data'; - import { useEffect } from '@wordpress/element'; /** @@ -22,17 +20,14 @@ import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; export default function SidebarNavigationScreenMain() { - const { location } = useNavigator(); const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) ); // Clear the editor canvas container view when accessing the main navigation screen. useEffect( () => { - if ( location?.path === '/' ) { - setEditorCanvasContainerView( undefined ); - } - }, [ setEditorCanvasContainerView, location?.path ] ); + setEditorCanvasContainerView( undefined ); + }, [ setEditorCanvasContainerView ] ); return ( { const postId = navigationMenu?.id; @@ -41,7 +42,7 @@ function useDeleteNavigationMenu() { type: 'snackbar', } ); - goTo( '/navigation' ); + history.push( { path: '/navigation' } ); } catch ( error ) { createErrorNotice( sprintf( @@ -132,8 +133,7 @@ function useSaveNavigationMenu() { } function useDuplicateNavigationMenu() { - const { goTo } = useNavigator(); - + const history = useHistory(); const { saveEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = @@ -165,7 +165,7 @@ function useDuplicateNavigationMenu() { createSuccessNotice( __( 'Duplicated Navigation menu' ), { type: 'snackbar', } ); - goTo( `/navigation/${ postType }/${ savedRecord.id }` ); + history.push( { postType, postId: savedRecord.id } ); } } catch ( error ) { createErrorNotice( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js index 6b093ad27e25b..1e63225641895 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js @@ -23,12 +23,11 @@ import { currentlyPreviewingTheme, } from '../../utils/is-previewing-theme'; import { unlock } from '../../lock-unlock'; -import { getPathFromURL } from '../sync-state-with-url/use-sync-path-with-url'; const { useLocation, useHistory } = unlock( routerPrivateApis ); export default function LeafMoreMenu( props ) { - const location = useLocation(); + const { params } = useLocation(); const history = useHistory(); const { block } = props; const { clientId } = block; @@ -74,7 +73,7 @@ export default function LeafMoreMenu( props ) { } ), }, { - backPath: getPathFromURL( location.params ), + backPath: params, } ); } @@ -88,12 +87,12 @@ export default function LeafMoreMenu( props ) { } ), }, { - backPath: getPathFromURL( location.params ), + backPath: params, } ); } }, - [ history ] + [ history, params ] ); return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js index d6a43fd9b6a91..e8df6a2b7677d 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js @@ -4,7 +4,6 @@ import { __ } from '@wordpress/i18n'; import { useDispatch, useSelect } from '@wordpress/data'; import { - __experimentalUseNavigator as useNavigator, __experimentalVStack as VStack, ExternalLink, __experimentalTruncate as Truncate, @@ -29,7 +28,7 @@ import SidebarButton from '../sidebar-button'; import PageDetails from './page-details'; import SidebarNavigationScreenDetailsFooter from '../sidebar-navigation-screen-details-footer'; -const { useHistory } = unlock( routerPrivateApis ); +const { useLocation, useHistory } = unlock( routerPrivateApis ); const { PostActions } = unlock( editorPrivateApis ); export default function SidebarNavigationScreenPage( { backPath } ) { @@ -37,7 +36,7 @@ export default function SidebarNavigationScreenPage( { backPath } ) { const history = useHistory(); const { params: { postId }, - } = useNavigator(); + } = useLocation(); const { record, hasResolved } = useEntityRecord( 'postType', 'page', @@ -80,7 +79,7 @@ export default function SidebarNavigationScreenPage( { backPath } ) { canvas: 'view', } ); } - }, [ hasResolved, history ] ); + }, [ hasResolved, record, history ] ); const onActionPerformed = useCallback( ( actionId, items ) => { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js index 693d16914869a..8607dad09241c 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/index.js @@ -1,11 +1,11 @@ /** * WordPress dependencies */ -import { __experimentalUseNavigator as useNavigator } from '@wordpress/components'; import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { pencil } from '@wordpress/icons'; import { getQueryArgs } from '@wordpress/url'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies @@ -19,11 +19,14 @@ import { unlock } from '../../lock-unlock'; import TemplateActions from '../template-actions'; import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; +const { useLocation, useHistory } = unlock( routerPrivateApis ); + export default function SidebarNavigationScreenPattern() { - const navigator = useNavigator(); + const history = useHistory(); + const location = useLocation(); const { params: { postType, postId }, - } = navigator; + } = location; const { categoryType } = getQueryArgs( window.location.href ); const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); @@ -36,8 +39,8 @@ export default function SidebarNavigationScreenPattern() { // page and the back button should return them to that list page. const backPath = ! categoryType && postType === TEMPLATE_PART_POST_TYPE - ? '/wp_template_part/all' - : '/patterns'; + ? { path: '/wp_template_part/all' } + : { path: '/patterns' }; return ( { - navigator.goTo( backPath ); + history.push( backPath ); } } /> { - navigator.goTo( '/' + postType ); + history.push( { path: '/' + postType } ); } } /> ( { id, path } ) ); - -export function getPathFromURL( urlParams ) { - let path = urlParams?.path ?? '/'; - - // Compute the navigator path based on the URL params. - if ( urlParams?.postType && urlParams?.postId ) { - switch ( urlParams.postType ) { - case PATTERN_TYPES.user: - case TEMPLATE_POST_TYPE: - case TEMPLATE_PART_POST_TYPE: - case 'page': - path = `/${ encodeURIComponent( - urlParams.postType - ) }/${ encodeURIComponent( urlParams.postId ) }`; - break; - default: - path = `/navigation/${ encodeURIComponent( - urlParams.postType - ) }/${ encodeURIComponent( urlParams.postId ) }`; - } - } - return path; -} - -function getParamsFromPath( path, params ) { - if ( params?.postType && params?.postId ) { - return { - postType: params?.postType, - postId: params?.postId, - path: undefined, - layout: undefined, - }; - } else if ( path.startsWith( '/page/' ) && params?.postId ) { - return { - postType: 'page', - postId: params?.postId, - path: undefined, - layout: undefined, - }; - } else if ( path === '/patterns' ) { - return { - postType: undefined, - postId: undefined, - canvas: undefined, - path, - }; - } else if ( - // These sidebar paths are special in the sense that the url in these pages may or may not have a postId and we need to retain it if it has. - // The "type" property should be kept as well. - path === '/page' || - path === '/wp_template' || - path === '/wp_template_part/all' - ) { - return { - postType: undefined, - categoryType: undefined, - categoryId: undefined, - path, - }; - } - return { - postType: undefined, - postId: undefined, - categoryType: undefined, - categoryId: undefined, - layout: undefined, - path: path === '/' ? undefined : path, - }; -} - -function matchPath( path, pattern ) { - const matcher = match( pattern, { decode: decodeURIComponent } ); - return matcher( path ); -} - -function patternMatch( path, screens ) { - for ( const screen of screens ) { - const matched = matchPath( path, screen.path ); - if ( matched ) { - return { params: matched.params, id: screen.id }; - } - } - - return undefined; -} - -function findParent( path, screens ) { - if ( ! path.startsWith( '/' ) ) { - return undefined; - } - const pathParts = path.split( '/' ); - while ( pathParts.length > 1 ) { - pathParts.pop(); - const parentPath = pathParts.join( '/' ) || '/'; - if ( - screens.some( - ( screen ) => matchPath( parentPath, screen.path ) !== false - ) - ) { - return parentPath; - } - } - - return undefined; -} +const { useHistory } = unlock( routerPrivateApis ); export function useRouter() { const history = useHistory(); - const { params } = useLocation(); - const path = getPathFromURL( params ); - - const matchedPath = useMemo( () => { - return path !== undefined ? patternMatch( path, SCREENS ) : undefined; - }, [ path ] ); const goMethods = useMemo( () => { const goTo = ( p ) => { - const matched = patternMatch( p, SCREENS ); - history.push( getParamsFromPath( p, matched?.params ?? {} ) ); - }; - - const goToParent = () => { - const parentPath = findParent( path, SCREENS ); - if ( parentPath !== undefined ) { - goTo( parentPath, { - isBack: true, - } ); - } - }; - - const goBack = () => { - history.back(); + history.push( { path: p } ); }; - return { goTo, goToParent, goBack }; - }, [ history, path ] ); + return { goTo }; + }, [ history ] ); return useMemo( () => ( { - location: { path }, - params: matchedPath?.params ?? {}, - match: matchedPath?.id, + location: { isBack: false, isInitial: false, skipFocus: false }, ...goMethods, } ), - [ path, matchedPath, goMethods ] + [ goMethods ] ); } From 1119a019908a79ff744c45a15b1fc6b6362dfd13 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 11:11:46 +0200 Subject: [PATCH 05/14] Eliminate NavigatorButton from SidebarNavigationItem --- .../sidebar-navigation-item/index.js | 23 +++++++++++++++++ .../index.js | 13 ++-------- .../sidebar-navigation-screen-main/index.js | 25 +++++++------------ .../use-sync-path-with-url.js | 21 +--------------- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-item/index.js index fad9f63421714..41fbe8284349b 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-item/index.js @@ -13,15 +13,37 @@ import { } from '@wordpress/components'; import { isRTL } from '@wordpress/i18n'; import { chevronRightSmall, chevronLeftSmall, Icon } from '@wordpress/icons'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { useHistory } = unlock( routerPrivateApis ); export default function SidebarNavigationItem( { className, icon, withChevron = false, suffix, + path, + onClick, children, ...props } ) { + const history = useHistory(); + let handleClick = onClick; + + // If there is no custom click handler, create one that navigates to `path`. + if ( ! handleClick && path ) { + handleClick = ( e ) => { + e.preventDefault(); + // history.replaceState( { focusTargetSelector: `[$id="${ path }"]` } ); + history.push( { path } ); + }; + } + return ( 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 87ab8095ed827..f0585b410e40d 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 @@ -5,10 +5,7 @@ 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 { - __experimentalNavigatorButton as NavigatorButton, - __experimentalVStack as VStack, -} from '@wordpress/components'; +import { __experimentalVStack as VStack } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { BlockEditorProvider } from '@wordpress/block-editor'; import { useCallback } from '@wordpress/element'; @@ -48,13 +45,7 @@ export function SidebarNavigationItemGlobalStyles( props ) { [] ); if ( hasGlobalStyleVariations ) { - return ( - - ); + return ; } return ( - { __( 'Navigation' ) } - + { __( 'Styles' ) } - { __( 'Pages' ) } - - + { __( 'Templates' ) } - - + { __( 'Patterns' ) } - + } diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js index e72283d33f06a..b2cc617741495 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js @@ -2,31 +2,12 @@ * WordPress dependencies */ import { useMemo } from '@wordpress/element'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; - -const { useHistory } = unlock( routerPrivateApis ); export function useRouter() { - const history = useHistory(); - - const goMethods = useMemo( () => { - const goTo = ( p ) => { - history.push( { path: p } ); - }; - - return { goTo }; - }, [ history ] ); - return useMemo( () => ( { location: { isBack: false, isInitial: false, skipFocus: false }, - ...goMethods, } ), - [ goMethods ] + [] ); } From 6f49dfac55cfb02d25153fde6b10c421b4bd642f Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 12:44:09 +0200 Subject: [PATCH 06/14] Remove NavigatorProvider and NavigatorScreen --- .../navigator-provider/component.tsx | 21 +++++++------------ .../navigator/navigator-screen/component.tsx | 15 ++++--------- packages/components/src/navigator/types.ts | 4 ---- .../edit-site/src/components/layout/index.js | 15 +++---------- .../src/components/sidebar/style.scss | 2 -- .../use-sync-path-with-url.js | 13 ------------ 6 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx index 8134a232ee9d2..15eb4d6bd3b1d 100644 --- a/packages/components/src/navigator/navigator-provider/component.tsx +++ b/packages/components/src/navigator/navigator-provider/component.tsx @@ -203,7 +203,13 @@ function routerReducer( return { screens, locationHistory, matchedPath }; } -function useMemoryRouter( initialPath: string ): NavigatorContextType { +function UnconnectedNavigatorProvider( + props: WordPressComponentProps< NavigatorProviderProps, 'div' >, + forwardedRef: ForwardedRef< any > +) { + const { initialPath, children, className, ...otherProps } = + useContextSystem( props, 'NavigatorProvider' ); + const [ routerState, dispatch ] = useReducer( routerReducer, initialPath, @@ -232,7 +238,7 @@ function useMemoryRouter( initialPath: string ): NavigatorContextType { const { locationHistory, matchedPath } = routerState; - return useMemo( + const navigatorContextValue: NavigatorContextType = useMemo( () => ( { location: { ...locationHistory[ locationHistory.length - 1 ], @@ -244,17 +250,6 @@ function useMemoryRouter( initialPath: string ): NavigatorContextType { } ), [ locationHistory, matchedPath, methods ] ); -} - -function UnconnectedNavigatorProvider( - props: WordPressComponentProps< NavigatorProviderProps, 'div' >, - forwardedRef: ForwardedRef< any > -) { - const { initialPath, children, className, router, ...otherProps } = - useContextSystem( props, 'NavigatorProvider' ); - - const memoryRouter = useMemoryRouter( initialPath ); - const navigatorContextValue = router ?? memoryRouter; const cx = useCx(); const classes = useMemo( diff --git a/packages/components/src/navigator/navigator-screen/component.tsx b/packages/components/src/navigator/navigator-screen/component.tsx index 5362281db8b0d..be5c4bfaf41ec 100644 --- a/packages/components/src/navigator/navigator-screen/component.tsx +++ b/packages/components/src/navigator/navigator-screen/component.tsx @@ -33,6 +33,7 @@ function UnconnectedNavigatorScreen( props: WordPressComponentProps< NavigatorScreenProps, 'div', false >, forwardedRef: ForwardedRef< any > ) { + const screenId = useId(); const { children, className, path, ...otherProps } = useContextSystem( props, 'NavigatorScreen' @@ -40,13 +41,10 @@ function UnconnectedNavigatorScreen( const { location, match, addScreen, removeScreen } = useContext( NavigatorContext ); + const isMatch = match === screenId; const wrapperRef = useRef< HTMLDivElement >( null ); - const screenId = useId(); useEffect( () => { - if ( ! path ) { - return; - } const screen = { id: screenId, path: escapeAttribute( path ), @@ -55,7 +53,6 @@ function UnconnectedNavigatorScreen( return () => removeScreen( screen ); }, [ screenId, path, addScreen, removeScreen ] ); - const isMatch = ! path || match === screenId; const isRTL = isRTLFn(); const { isInitial, isBack } = location; const cx = useCx(); @@ -134,15 +131,11 @@ function UnconnectedNavigatorScreen( const mergedWrapperRef = useMergeRefs( [ forwardedRef, wrapperRef ] ); - if ( ! isMatch ) { - return null; - } - - return ( + return isMatch ? ( { children } - ); + ) : null; } /** diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts index eec4daeff6b3f..557f8074fd42e 100644 --- a/packages/components/src/navigator/types.ts +++ b/packages/components/src/navigator/types.ts @@ -45,10 +45,6 @@ export type NavigatorProviderProps = { * The initial active path. */ initialPath: string; - /** - * External router - */ - router?: NavigatorContext; /** * The children elements. */ diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 7e203449d6009..3c56cb679bd8a 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -11,8 +11,6 @@ import { __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence, __unstableUseNavigateRegions as useNavigateRegions, - __experimentalNavigatorProvider as NavigatorProvider, - __experimentalNavigatorScreen as NavigatorScreen, } from '@wordpress/components'; import { useReducedMotion, @@ -52,7 +50,6 @@ import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands import { useIsSiteEditorLoading } from './hooks'; import useLayoutAreas from './router'; import useMovingAnimation from './animation'; -import { useRouter } from '../sync-state-with-url/use-sync-path-with-url'; import SaveHub from '../save-hub'; const { useCommands } = unlock( coreCommandsPrivateApis ); @@ -122,7 +119,6 @@ export default function Layout() { const animationRef = useMovingAnimation( { triggerAnimationOnChange: canvasMode + '__' + routeKey, } ); - const router = useRouter(); // This determines which animation variant should apply to the header. // There is also a `isDistractionFreeHovering` state that gets priority @@ -251,14 +247,9 @@ export default function Layout() { } } className="edit-site-layout__sidebar" > - - - { areas.sidebar } - - +
+ { areas.sidebar } +
) } diff --git a/packages/edit-site/src/components/sidebar/style.scss b/packages/edit-site/src/components/sidebar/style.scss index 9bbe11f44ee7b..5a3dbab14b739 100644 --- a/packages/edit-site/src/components/sidebar/style.scss +++ b/packages/edit-site/src/components/sidebar/style.scss @@ -1,9 +1,7 @@ .edit-site-sidebar__content { flex-grow: 1; overflow-y: auto; -} -.edit-site-sidebar__screen-wrapper { @include custom-scrollbars-on-hover(transparent, $gray-700); scrollbar-gutter: stable; display: flex; diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js deleted file mode 100644 index b2cc617741495..0000000000000 --- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; - -export function useRouter() { - return useMemo( - () => ( { - location: { isBack: false, isInitial: false, skipFocus: false }, - } ), - [] - ); -} From e6832c0631964d939043c5e64421b95280b40020 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 12:54:53 +0200 Subject: [PATCH 07/14] edit-site no longer needs path-to-regexp --- package-lock.json | 14 -------------- packages/edit-site/package.json | 1 - 2 files changed, 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb12dbcc3e997..55f2e0ad54118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54165,7 +54165,6 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" }, "engines": { @@ -54176,11 +54175,6 @@ "react-dom": "^18.0.0" } }, - "packages/edit-site/node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" - }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", "version": "5.32.0", @@ -69266,15 +69260,7 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" - }, - "dependencies": { - "path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" - } } }, "@wordpress/edit-widgets": { diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 6221df6ca5e40..c556264207676 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -76,7 +76,6 @@ "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", - "path-to-regexp": "^6.2.1", "react-autosize-textarea": "^7.1.0" }, "peerDependencies": { From 590e096ea69ecc9e97fcb357b51a79e413120452 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 15 Apr 2024 14:48:01 +0200 Subject: [PATCH 08/14] Animation when navigating forward/backward --- .../edit-site/src/components/layout/index.js | 20 +++++++-- .../edit-site/src/components/layout/router.js | 9 ++++ .../sidebar-navigation-screen/index.js | 12 ++---- .../src/components/sidebar/style.scss | 41 +++++++++++++++++++ 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 3c56cb679bd8a..ecb6fc7ed55cf 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -48,7 +48,7 @@ import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; import { useCommonCommands } from '../../hooks/commands/use-common-commands'; import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands'; import { useIsSiteEditorLoading } from './hooks'; -import useLayoutAreas from './router'; +import useLayoutAreas, { useDirection } from './router'; import useMovingAnimation from './animation'; import SaveHub from '../save-hub'; @@ -59,6 +59,20 @@ const { NavigableRegion } = unlock( editorPrivateApis ); const ANIMATION_DURATION = 0.3; +function SidebarContent( { children } ) { + const isForward = useDirection(); + return ( +
+ { children } +
+ ); +} + export default function Layout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); @@ -247,9 +261,9 @@ export default function Layout() { } } className="edit-site-layout__sidebar" > -
+ { areas.sidebar } -
+ ) } diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index 634a5663bf163..1a798c59708fa 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -31,6 +31,15 @@ import { const { useLocation, useHistory } = unlock( routerPrivateApis ); +export function useDirection() { + const { params } = useLocation(); + + if ( params.path || params.postType ) { + return true; // forward + } + return false; +} + export default function useLayoutAreas() { const isSiteEditorLoading = useIsSiteEditorLoading(); const history = useHistory(); diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/index.js b/packages/edit-site/src/components/sidebar-navigation-screen/index.js index 8eb2a4f47b160..e9d84a66652ae 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen/index.js @@ -102,14 +102,10 @@ export default function SidebarNavigationScreen( { { const backPath = - backPathProp ?? location.state?.backPath; - if ( backPath ) { - history.push( backPath ); - } else { - history.push( - getBackPath( location.params ) - ); - } + backPathProp ?? + location.state?.backPath ?? + getBackPath( location.params ); + history.push( backPath ); } } icon={ icon } label={ __( 'Back' ) } diff --git a/packages/edit-site/src/components/sidebar/style.scss b/packages/edit-site/src/components/sidebar/style.scss index 5a3dbab14b739..2c3a1c1149e8e 100644 --- a/packages/edit-site/src/components/sidebar/style.scss +++ b/packages/edit-site/src/components/sidebar/style.scss @@ -1,5 +1,28 @@ +@keyframes sidebar-fade-in-from-right { + 0% { + opacity: 0; + transform: translateX(50px); + } + 100% { + opacity: 1; + transform: none; + } +} + +@keyframes sidebar-fade-in-from-left { + 0% { + opacity: 0; + transform: translateX(-50px); + } + 100% { + opacity: 1; + transform: none; + } +} + .edit-site-sidebar__content { flex-grow: 1; + overflow-x: auto; overflow-y: auto; @include custom-scrollbars-on-hover(transparent, $gray-700); @@ -7,7 +30,25 @@ display: flex; flex-direction: column; height: 100%; + max-height: 100%; // This matches the logo padding padding: 0 $grid-unit-15; + + // Animation + animation-duration: 0.14s; + animation-timing-function: ease-in-out; + will-change: transform, opacity; + + @media ( prefers-reduced-motion ) { + animation-duration: 0s; + } + + &.fade-in-from-right { + animation-name: sidebar-fade-in-from-right; + } + + &.fade-in-from-left { + animation-name: sidebar-fade-in-from-left; + } } From 74f9481a77e4088f85d4219fc4e4d7cb691965b1 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Tue, 16 Apr 2024 15:21:08 +0200 Subject: [PATCH 09/14] Sidebar animation with motion.div, basic focus mgmt --- .../edit-site/src/components/layout/index.js | 85 ++++++++++++++++--- .../edit-site/src/components/layout/router.js | 9 -- .../sidebar-navigation-item/index.js | 15 ++-- .../sidebar-navigation-screen/index.js | 5 +- .../src/components/sidebar/style.scss | 43 +--------- 5 files changed, 89 insertions(+), 68 deletions(-) diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index ecb6fc7ed55cf..fe0294ce4fc7e 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -18,7 +18,13 @@ import { useResizeObserver, } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; +import { + useCallback, + createContext, + useState, + useRef, + useEffect, +} from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { CommandMenu, @@ -31,6 +37,7 @@ import { } from '@wordpress/block-editor'; import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { focus } from '@wordpress/dom'; /** * Internal dependencies @@ -48,7 +55,7 @@ import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; import { useCommonCommands } from '../../hooks/commands/use-common-commands'; import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands'; import { useIsSiteEditorLoading } from './hooks'; -import useLayoutAreas, { useDirection } from './router'; +import useLayoutAreas from './router'; import useMovingAnimation from './animation'; import SaveHub from '../save-hub'; @@ -59,17 +66,69 @@ const { NavigableRegion } = unlock( editorPrivateApis ); const ANIMATION_DURATION = 0.3; -function SidebarContent( { children } ) { - const isForward = useDirection(); +export const NavigateContext = createContext( () => {} ); + +function getAnim( isBack ) { + switch ( isBack ) { + case true: + return { + initial: { opacity: 0, x: '-50px' }, + animate: { opacity: 1, x: '0' }, + }; + case false: + return { + initial: { opacity: 0, x: '50px' }, + animate: { opacity: 1, x: '0' }, + }; + default: + return { initial: false, animate: false }; + } +} + +function SidebarContent( { routeKey, children } ) { + const [ { navDirection, focusSelector }, setNavDirection ] = useState( { + navDirection: null, + focusSelector: null, + } ); + + const navigate = useCallback( ( isBack, backFocusSelector ) => { + setNavDirection( ( prevDir ) => ( { + navDirection: isBack, + focusSelector: + ! isBack && backFocusSelector + ? backFocusSelector + : prevDir.focusSelector, + } ) ); + }, [] ); + const { initial, animate } = getAnim( navDirection ); + + const wrapperRef = useRef(); + useEffect( () => { + let elementToFocus; + if ( navDirection === false ) { + const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); + elementToFocus = firstTabbable ?? wrapperRef.current; + } else if ( navDirection === true && focusSelector ) { + elementToFocus = wrapperRef.current.querySelector( focusSelector ); + } + elementToFocus?.focus(); + }, [ navDirection, focusSelector ] ); + return ( -
- { children } -
+ +
+ + { children } + +
+
); } @@ -261,7 +320,7 @@ export default function Layout() { } } className="edit-site-layout__sidebar" > - + { areas.sidebar } diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index 1a798c59708fa..634a5663bf163 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -31,15 +31,6 @@ import { const { useLocation, useHistory } = unlock( routerPrivateApis ); -export function useDirection() { - const { params } = useLocation(); - - if ( params.path || params.postType ) { - return true; // forward - } - return false; -} - export default function useLayoutAreas() { const isSiteEditorLoading = useIsSiteEditorLoading(); const history = useHistory(); diff --git a/packages/edit-site/src/components/sidebar-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-item/index.js index 41fbe8284349b..d3e123e52210b 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-item/index.js @@ -14,11 +14,13 @@ import { import { isRTL } from '@wordpress/i18n'; import { chevronRightSmall, chevronLeftSmall, Icon } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useContext } from '@wordpress/element'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { NavigateContext } from '../layout'; const { useHistory } = unlock( routerPrivateApis ); @@ -33,15 +35,18 @@ export default function SidebarNavigationItem( { ...props } ) { const history = useHistory(); - let handleClick = onClick; + const navigate = useContext( NavigateContext ); // If there is no custom click handler, create one that navigates to `path`. - if ( ! handleClick && path ) { - handleClick = ( e ) => { + function handleClick( e ) { + if ( onClick ) { + onClick( e ); + navigate( false ); + } else if ( path ) { e.preventDefault(); - // history.replaceState( { focusTargetSelector: `[$id="${ path }"]` } ); history.push( { path } ); - }; + navigate( false, `[id="${ path }"]` ); + } } return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/index.js b/packages/edit-site/src/components/sidebar-navigation-screen/index.js index e9d84a66652ae..4b8599ad83bd0 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen/index.js @@ -16,6 +16,7 @@ import { chevronRight, chevronLeft } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useContext } from '@wordpress/element'; /** * Internal dependencies @@ -27,6 +28,7 @@ import { isPreviewingTheme, currentlyPreviewingTheme, } from '../../utils/is-previewing-theme'; +import { NavigateContext } from '../layout'; const { useHistory, useLocation } = unlock( routerPrivateApis ); @@ -79,8 +81,8 @@ export default function SidebarNavigationScreen( { ); const location = useLocation(); const history = useHistory(); + const navigate = useContext( NavigateContext ); const icon = isRTL() ? chevronRight : chevronLeft; - return ( <> Date: Tue, 16 Apr 2024 15:29:48 +0200 Subject: [PATCH 10/14] Add id attr to SidebarNavigationItem --- .../edit-site/src/components/sidebar-navigation-item/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-site/src/components/sidebar-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-item/index.js index d3e123e52210b..45f8959cc726d 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-item/index.js @@ -57,6 +57,7 @@ export default function SidebarNavigationItem( { className ) } onClick={ handleClick } + id={ path } { ...props } > From 979d1bf1c94b4cbf6d5f32e4d0119699e419ead5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Tue, 16 Apr 2024 16:35:16 +0200 Subject: [PATCH 11/14] Separate module for SidebarContent --- .../edit-site/src/components/layout/index.js | 76 +---------------- .../sidebar-navigation-item/index.js | 8 +- .../sidebar-navigation-screen/index.js | 6 +- .../edit-site/src/components/sidebar/index.js | 81 +++++++++++++++++++ 4 files changed, 90 insertions(+), 81 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar/index.js diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index fe0294ce4fc7e..9ddeed49981be 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -18,13 +18,7 @@ import { useResizeObserver, } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { - useCallback, - createContext, - useState, - useRef, - useEffect, -} from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { CommandMenu, @@ -37,7 +31,6 @@ import { } from '@wordpress/block-editor'; import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; -import { focus } from '@wordpress/dom'; /** * Internal dependencies @@ -57,6 +50,7 @@ import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands import { useIsSiteEditorLoading } from './hooks'; import useLayoutAreas from './router'; import useMovingAnimation from './animation'; +import SidebarContent from '../sidebar'; import SaveHub from '../save-hub'; const { useCommands } = unlock( coreCommandsPrivateApis ); @@ -66,72 +60,6 @@ const { NavigableRegion } = unlock( editorPrivateApis ); const ANIMATION_DURATION = 0.3; -export const NavigateContext = createContext( () => {} ); - -function getAnim( isBack ) { - switch ( isBack ) { - case true: - return { - initial: { opacity: 0, x: '-50px' }, - animate: { opacity: 1, x: '0' }, - }; - case false: - return { - initial: { opacity: 0, x: '50px' }, - animate: { opacity: 1, x: '0' }, - }; - default: - return { initial: false, animate: false }; - } -} - -function SidebarContent( { routeKey, children } ) { - const [ { navDirection, focusSelector }, setNavDirection ] = useState( { - navDirection: null, - focusSelector: null, - } ); - - const navigate = useCallback( ( isBack, backFocusSelector ) => { - setNavDirection( ( prevDir ) => ( { - navDirection: isBack, - focusSelector: - ! isBack && backFocusSelector - ? backFocusSelector - : prevDir.focusSelector, - } ) ); - }, [] ); - const { initial, animate } = getAnim( navDirection ); - - const wrapperRef = useRef(); - useEffect( () => { - let elementToFocus; - if ( navDirection === false ) { - const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); - elementToFocus = firstTabbable ?? wrapperRef.current; - } else if ( navDirection === true && focusSelector ) { - elementToFocus = wrapperRef.current.querySelector( focusSelector ); - } - elementToFocus?.focus(); - }, [ navDirection, focusSelector ] ); - - return ( - -
- - { children } - -
-
- ); -} - export default function Layout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); diff --git a/packages/edit-site/src/components/sidebar-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-item/index.js index 45f8959cc726d..bbac07c42de0d 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-item/index.js @@ -20,7 +20,7 @@ import { useContext } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import { NavigateContext } from '../layout'; +import { SidebarNavigationContext } from '../sidebar'; const { useHistory } = unlock( routerPrivateApis ); @@ -35,17 +35,17 @@ export default function SidebarNavigationItem( { ...props } ) { const history = useHistory(); - const navigate = useContext( NavigateContext ); + const navigate = useContext( SidebarNavigationContext ); // If there is no custom click handler, create one that navigates to `path`. function handleClick( e ) { if ( onClick ) { onClick( e ); - navigate( false ); + navigate( 'forward' ); } else if ( path ) { e.preventDefault(); history.push( { path } ); - navigate( false, `[id="${ path }"]` ); + navigate( 'forward', `[id="${ path }"]` ); } } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/index.js b/packages/edit-site/src/components/sidebar-navigation-screen/index.js index 4b8599ad83bd0..a65c177cecfee 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen/index.js @@ -28,7 +28,7 @@ import { isPreviewingTheme, currentlyPreviewingTheme, } from '../../utils/is-previewing-theme'; -import { NavigateContext } from '../layout'; +import { SidebarNavigationContext } from '../sidebar'; const { useHistory, useLocation } = unlock( routerPrivateApis ); @@ -81,7 +81,7 @@ export default function SidebarNavigationScreen( { ); const location = useLocation(); const history = useHistory(); - const navigate = useContext( NavigateContext ); + const navigate = useContext( SidebarNavigationContext ); const icon = isRTL() ? chevronRight : chevronLeft; return ( <> @@ -108,7 +108,7 @@ export default function SidebarNavigationScreen( { location.state?.backPath ?? getBackPath( location.params ); history.push( backPath ); - navigate( true ); + navigate( 'back' ); } } icon={ icon } label={ __( 'Back' ) } diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js new file mode 100644 index 0000000000000..1acb1e7278985 --- /dev/null +++ b/packages/edit-site/src/components/sidebar/index.js @@ -0,0 +1,81 @@ +/** + * WordPress dependencies + */ +import { __unstableMotion as motion } from '@wordpress/components'; +import { useReducedMotion } from '@wordpress/compose'; +import { + useCallback, + createContext, + useState, + useRef, + useEffect, +} from '@wordpress/element'; +import { focus } from '@wordpress/dom'; + +export const SidebarNavigationContext = createContext( () => {} ); + +function getAnim( direction ) { + switch ( direction ) { + case 'back': + return { + initial: { opacity: 0, x: '-50px' }, + animate: { opacity: 1, x: '0' }, + }; + case 'forward': + return { + initial: { opacity: 0, x: '50px' }, + animate: { opacity: 1, x: '0' }, + }; + default: + return { initial: false, animate: false }; + } +} + +export default function SidebarContent( { routeKey, children } ) { + const [ { navDirection, focusSelector }, setNavDirection ] = useState( { + navDirection: null, + focusSelector: null, + } ); + + const navigate = useCallback( ( direction, backFocusSelector ) => { + setNavDirection( ( prevDir ) => ( { + navDirection: direction, + focusSelector: + direction === 'forward' && backFocusSelector + ? backFocusSelector + : prevDir.focusSelector, + } ) ); + }, [] ); + + const wrapperRef = useRef(); + useEffect( () => { + let elementToFocus; + if ( navDirection === 'forward' ) { + const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); + elementToFocus = firstTabbable ?? wrapperRef.current; + } else if ( navDirection === 'back' && focusSelector ) { + elementToFocus = wrapperRef.current.querySelector( focusSelector ); + } + elementToFocus?.focus(); + }, [ navDirection, focusSelector ] ); + + const disableMotion = useReducedMotion(); + const { initial, animate } = getAnim( navDirection ); + + return ( + +
+ + { children } + +
+
+ ); +} From f24027b1d87926aee1157ad8c7741f432b5f46a8 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 17 Apr 2024 11:57:46 +0200 Subject: [PATCH 12/14] Fix focusing when going back --- .../edit-site/src/components/sidebar/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 1acb1e7278985..acbf38064b785 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -32,16 +32,16 @@ function getAnim( direction ) { } export default function SidebarContent( { routeKey, children } ) { - const [ { navDirection, focusSelector }, setNavDirection ] = useState( { + const [ { navDirection, focusSelector }, setNavState ] = useState( { navDirection: null, focusSelector: null, } ); - const navigate = useCallback( ( direction, backFocusSelector ) => { - setNavDirection( ( prevDir ) => ( { + const navigate = useCallback( ( direction, backFocusSelector = null ) => { + setNavState( ( prevDir ) => ( { navDirection: direction, focusSelector: - direction === 'forward' && backFocusSelector + direction === 'forward' ? backFocusSelector : prevDir.focusSelector, } ) ); @@ -50,11 +50,12 @@ export default function SidebarContent( { routeKey, children } ) { const wrapperRef = useRef(); useEffect( () => { let elementToFocus; - if ( navDirection === 'forward' ) { + if ( navDirection === 'back' && focusSelector ) { + elementToFocus = wrapperRef.current.querySelector( focusSelector ); + } + if ( ! elementToFocus ) { const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); elementToFocus = firstTabbable ?? wrapperRef.current; - } else if ( navDirection === 'back' && focusSelector ) { - elementToFocus = wrapperRef.current.querySelector( focusSelector ); } elementToFocus?.focus(); }, [ navDirection, focusSelector ] ); From d3aec42c9b3cd83b54a018cdd92a35bea3c1f419 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 17 Apr 2024 12:22:38 +0200 Subject: [PATCH 13/14] Don't focus on initial render --- packages/edit-site/src/components/sidebar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index acbf38064b785..96ae631fdcde9 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -53,7 +53,7 @@ export default function SidebarContent( { routeKey, children } ) { if ( navDirection === 'back' && focusSelector ) { elementToFocus = wrapperRef.current.querySelector( focusSelector ); } - if ( ! elementToFocus ) { + if ( navDirection !== null && ! elementToFocus ) { const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); elementToFocus = firstTabbable ?? wrapperRef.current; } From 74904584d818e5ea09f3f15fa1cf7969dba083fc Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 17 Apr 2024 13:33:40 +0200 Subject: [PATCH 14/14] Keep focusSelector across multiple back navigations --- .../edit-site/src/components/sidebar/index.js | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 96ae631fdcde9..2cb7997b32bbe 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -32,36 +32,38 @@ function getAnim( direction ) { } export default function SidebarContent( { routeKey, children } ) { - const [ { navDirection, focusSelector }, setNavState ] = useState( { - navDirection: null, + const [ navState, setNavState ] = useState( { + direction: null, focusSelector: null, } ); - const navigate = useCallback( ( direction, backFocusSelector = null ) => { - setNavState( ( prevDir ) => ( { - navDirection: direction, + const navigate = useCallback( ( direction, focusSelector = null ) => { + setNavState( ( prevState ) => ( { + direction, focusSelector: - direction === 'forward' - ? backFocusSelector - : prevDir.focusSelector, + direction === 'forward' && focusSelector + ? focusSelector + : prevState.focusSelector, } ) ); }, [] ); const wrapperRef = useRef(); useEffect( () => { let elementToFocus; - if ( navDirection === 'back' && focusSelector ) { - elementToFocus = wrapperRef.current.querySelector( focusSelector ); + if ( navState.direction === 'back' && navState.focusSelector ) { + elementToFocus = wrapperRef.current.querySelector( + navState.focusSelector + ); } - if ( navDirection !== null && ! elementToFocus ) { + if ( navState.direction !== null && ! elementToFocus ) { const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current ); elementToFocus = firstTabbable ?? wrapperRef.current; } elementToFocus?.focus(); - }, [ navDirection, focusSelector ] ); + }, [ navState ] ); const disableMotion = useReducedMotion(); - const { initial, animate } = getAnim( navDirection ); + const { initial, animate } = getAnim( navState.direction ); return (