@@ -92,32 +86,32 @@ const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => {
-
-
-
diff --git a/packages/edit-site/src/components/routes/index.js b/packages/edit-site/src/components/routes/index.js
new file mode 100644
index 00000000000000..b24fa069f1efd2
--- /dev/null
+++ b/packages/edit-site/src/components/routes/index.js
@@ -0,0 +1,53 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ createContext,
+ useState,
+ useEffect,
+ useContext,
+} from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import history from '../../utils/history';
+
+const RoutesContext = createContext();
+const HistoryContext = createContext();
+
+export function useLocation() {
+ return useContext( RoutesContext );
+}
+
+export function useHistory() {
+ return useContext( HistoryContext );
+}
+
+function getLocationWithParams( location ) {
+ const searchParams = new URLSearchParams( location.search );
+ return {
+ ...location,
+ params: Object.fromEntries( searchParams.entries() ),
+ };
+}
+
+export function Routes( { children } ) {
+ const [ location, setLocation ] = useState( () =>
+ getLocationWithParams( history.location )
+ );
+
+ useEffect( () => {
+ return history.listen( ( { location: updatedLocation } ) => {
+ setLocation( getLocationWithParams( updatedLocation ) );
+ } );
+ }, [] );
+
+ return (
+
+
+ { children( location ) }
+
+
+ );
+}
diff --git a/packages/edit-site/src/components/routes/link.js b/packages/edit-site/src/components/routes/link.js
new file mode 100644
index 00000000000000..8454260b908aab
--- /dev/null
+++ b/packages/edit-site/src/components/routes/link.js
@@ -0,0 +1,44 @@
+/**
+ * WordPress dependencies
+ */
+import { addQueryArgs } from '@wordpress/url';
+
+/**
+ * Internal dependencies
+ */
+import { useHistory } from './index';
+
+export function useLink( params = {}, state, shouldReplace = false ) {
+ const history = useHistory();
+
+ function onClick( event ) {
+ event.preventDefault();
+
+ if ( shouldReplace ) {
+ history.replace( params, state );
+ } else {
+ history.push( params, state );
+ }
+ }
+
+ return {
+ href: addQueryArgs( window.location.href, params ),
+ onClick,
+ };
+}
+
+export default function Link( {
+ params = {},
+ state,
+ replace: shouldReplace = false,
+ children,
+ ...props
+} ) {
+ const { href, onClick } = useLink( params, state, shouldReplace );
+
+ return (
+
+ { children }
+
+ );
+}
diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js
new file mode 100644
index 00000000000000..23ebaf31006e73
--- /dev/null
+++ b/packages/edit-site/src/components/routes/use-title.js
@@ -0,0 +1,56 @@
+/**
+ * WordPress dependencies
+ */
+import { useEffect, useRef } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+import { __, sprintf } from '@wordpress/i18n';
+import { speak } from '@wordpress/a11y';
+
+/**
+ * Internal dependencies
+ */
+import { useLocation } from './index';
+
+export default function useTitle( title ) {
+ const location = useLocation();
+ const siteTitle = useSelect(
+ ( select ) =>
+ select( coreStore ).getEntityRecord( 'root', 'site' )?.title,
+ []
+ );
+ const isInitialLocationRef = useRef( true );
+
+ useEffect( () => {
+ isInitialLocationRef.current = false;
+ }, [ location ] );
+
+ useEffect( () => {
+ // Don't update or announce the title for initial page load.
+ if ( isInitialLocationRef.current ) {
+ return;
+ }
+
+ if ( title && siteTitle ) {
+ // @see https://github.com/WordPress/wordpress-develop/blob/94849898192d271d533e09756007e176feb80697/src/wp-admin/admin-header.php#L67-L68
+ const formattedTitle = sprintf(
+ /* translators: Admin screen title. 1: Admin screen name, 2: Network or site name. */
+ __( '%1$s ‹ %2$s — WordPress' ),
+ title,
+ siteTitle
+ );
+
+ document.title = formattedTitle;
+
+ // Announce title on route change for screen readers.
+ speak(
+ sprintf(
+ /* translators: The page title that is currently displaying. */
+ __( 'Now displaying: %s' ),
+ document.title
+ ),
+ 'assertive'
+ );
+ }
+ }, [ title, siteTitle, location ] );
+}
diff --git a/packages/edit-site/src/components/template-details/index.js b/packages/edit-site/src/components/template-details/index.js
index 78414f7936e07f..5963040b7ef5bd 100644
--- a/packages/edit-site/src/components/template-details/index.js
+++ b/packages/edit-site/src/components/template-details/index.js
@@ -12,7 +12,6 @@ import {
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
-import { addQueryArgs } from '@wordpress/url';
/**
* Internal dependencies
@@ -25,6 +24,7 @@ import {
import { store as editSiteStore } from '../../store';
import TemplateAreas from './template-areas';
import EditTemplateTitle from './edit-template-title';
+import { useLink } from '../routes/link';
export default function TemplateDetails( { template, onClose } ) {
const { title, description } = useSelect(
@@ -44,6 +44,12 @@ export default function TemplateDetails( { template, onClose } ) {
);
}, [ template ] );
+ const browseAllLinkProps = useLink( {
+ // TODO: We should update this to filter by template part's areas as well.
+ postType: template.type,
+ postId: undefined,
+ } );
+
if ( ! template ) {
return null;
}
@@ -95,11 +101,7 @@ export default function TemplateDetails( { template, onClose } ) {