Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigator: simplify backwards navigation APIs #63317

Merged
merged 20 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- `ToggleControl`
- `ToggleGroupControl`
- `TreeSelect`
- Deprecate `NavigatorToParentButton` and `useNavigator().goToParent()` in favor of `NavigatorBackButton` and `useNavigator().goBack()` ([#63317](https://github.com/WordPress/gutenberg/pull/63317)).

### Enhancements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function UnconnectedNavigatorBackButton(
* <NavigatorScreen path="/child">
* <p>This is the child screen.</p>
* <NavigatorBackButton>
* Go back
* Go back (to parent)
* </NavigatorBackButton>
* </NavigatorScreen>
* </NavigatorProvider>
Expand Down
16 changes: 6 additions & 10 deletions packages/components/src/navigator/navigator-back-button/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,27 @@ import type { WordPressComponentProps } from '../../context';
import { useContextSystem } from '../../context';
import Button from '../../button';
import useNavigator from '../use-navigator';
import type { NavigatorBackButtonHookProps } from '../types';
import type { NavigatorBackButtonProps } from '../types';

export function useNavigatorBackButton(
props: WordPressComponentProps< NavigatorBackButtonHookProps, 'button' >
props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >
) {
const {
onClick,
as = Button,
goToParent: goToParentProp = false,

...otherProps
} = useContextSystem( props, 'NavigatorBackButton' );

const { goBack, goToParent } = useNavigator();
const { goBack } = useNavigator();
const handleClick: React.MouseEventHandler< HTMLButtonElement > =
useCallback(
( e ) => {
e.preventDefault();
if ( goToParentProp ) {
goToParent();
} else {
goBack();
}
goBack();
onClick?.( e );
},
[ goToParentProp, goToParent, goBack, onClick ]
[ goBack, onClick ]
);

return {
Expand Down
74 changes: 38 additions & 36 deletions packages/components/src/navigator/navigator-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,42 @@ The `NavigatorProvider` component allows rendering nested views/panels/menus (vi

```jsx
import {
__experimentalNavigatorProvider as NavigatorProvider,
__experimentalNavigatorScreen as NavigatorScreen,
__experimentalNavigatorButton as NavigatorButton,
__experimentalNavigatorToParentButton as NavigatorToParentButton,
__experimentalNavigatorProvider as NavigatorProvider,
__experimentalNavigatorScreen as NavigatorScreen,
__experimentalNavigatorButton as NavigatorButton,
__experimentalNavigatorBackButton as NavigatorBackButton,
} from '@wordpress/components';

const MyNavigation = () => (
<NavigatorProvider initialPath="/">
<NavigatorScreen path="/">
<p>This is the home screen.</p>
<NavigatorButton path="/child">
Navigate to child screen.
</NavigatorButton>
</NavigatorScreen>

<NavigatorScreen path="/child">
<p>This is the child screen.</p>
<NavigatorToParentButton>
Go back
</NavigatorToParentButton>
</NavigatorScreen>
</NavigatorProvider>
<NavigatorProvider initialPath="/">
<NavigatorScreen path="/">
<p>This is the home screen.</p>
<NavigatorButton path="/child">
Navigate to child screen.
</NavigatorButton>
</NavigatorScreen>

<NavigatorScreen path="/child">
<p>This is the child screen.</p>
<NavigatorBackButton>Go back</NavigatorBackButton>
</NavigatorScreen>
</NavigatorProvider>
);
```

**Important note**

Parent/child navigation only works if the path you define are hierarchical, following a URL-like scheme where each path segment is separated by the `/` character.
`Navigator` assumes that screens are organized hierarchically according to their `path`, which should follow a URL-like scheme where each path segment starts with and is separated by the `/` character.

`Navigator` will treat "back" navigations as going to the parent screen — it is therefore responsibility of the consumer of the component to create the correct screen hierarchy.

For example:
- `/` is the root of all paths. There should always be a screen with `path="/"`.
- `/parent/child` is a child of `/parent`.
- `/parent/child/grand-child` is a child of `/parent/child`.
- `/parent/:param` is a child of `/parent` as well.

- `/` is the root of all paths. There should always be a screen with `path="/"`.
- `/parent/child` is a child of `/parent`.
- `/parent/child/grand-child` is a child of `/parent/child`.
- `/parent/:param` is a child of `/parent` as well.
- if the current screen has a `path` with value `/parent/child/grand-child`, when going "back" `Navigator` will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) is found.

## Props

Expand All @@ -65,28 +69,26 @@ The `goTo` function allows navigating to a given path. The second argument can a

The available options are:

- `focusTargetSelector`: `string`. An optional property used to specify the CSS selector used to restore focus on the matching element when navigating back.
- `isBack`: `boolean`. An optional property used to specify whether the navigation should be considered as backwards (thus enabling focus restoration when possible, and causing the animation to be backwards too)

### `goToParent`: `() => void;`
- `focusTargetSelector`: `string`. An optional property used to specify the CSS selector used to restore focus on the matching element when navigating back;
- `isBack`: `boolean`. An optional property used to specify whether the navigation should be considered as backwards (thus enabling focus restoration when possible, and causing the animation to be backwards too);
- `skipFocus`: `boolean`. An optional property used to opt out of `Navigator`'s focus management, useful when the consumer of the component wants to manage focus themselves;
- `replace`: `boolean`. An optional property used to cause the new location to replace the current location in the stack.

The `goToParent` function allows navigating to the parent screen.
### `goBack`: `( path: string, options: NavigateOptions ) => void`

Parent/child navigation only works if the path you define are hierarchical (see note above).
The `goBack` function allows navigating to the parent screen. Parent/child navigation only works if the paths you define are hierarchical (see note above).

When a match is not found, the function will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) are found.

### `goBack`: `() => void`

The `goBack` function allows navigating to the previous path.
The available options are the same as for the `goTo` method, except for the `isBack` property, which is not available for the `goBack` method.

### `location`: `NavigatorLocation`

The `location` object represent the current location, and has a few properties:

- `path`: `string`. The path associated to the location.
- `isBack`: `boolean`. A flag that is `true` when the current location was reached by navigating backwards in the location stack.
- `isInitial`: `boolean`. A flag that is `true` only for the first (root) location in the location stack.
- `path`: `string`. The path associated to the location.
- `isBack`: `boolean`. A flag that is `true` when the current location was reached by navigating backwards in the location history.
- `isInitial`: `boolean`. A flag that is `true` only for the first (root) location in the location history.

### `params`: `Record< string, string | string[] >`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import type {
Screen,
NavigateToParentOptions,
} from '../types';
import deprecated from '@wordpress/deprecated';

type MatchedPath = ReturnType< typeof patternMatch >;

type RouterAction =
| { type: 'add' | 'remove'; screen: Screen }
| { type: 'goback' }
| { type: 'goto'; path: string; options?: NavigateOptions }
| { type: 'gotoparent'; options?: NavigateToParentOptions };

Expand Down Expand Up @@ -160,9 +160,6 @@ function routerReducer(
case 'remove':
screens = removeScreen( state, action.screen );
break;
case 'goback':
locationHistory = goBack( state );
break;
case 'goto':
locationHistory = goTo( state, action.path, action.options );
break;
Expand Down Expand Up @@ -223,11 +220,20 @@ function UnconnectedNavigatorProvider(
// The methods are constant forever, create stable references to them.
const methods = useMemo(
() => ( {
goBack: () => dispatch( { type: 'goback' } ),
// Note: calling goBack calls `goToParent` internally, as it was established
// that `goBack` should behave like `goToParent`, and `goToParent` should
// be marked as deprecated.
goBack: ( options: NavigateToParentOptions | undefined ) =>
dispatch( { type: 'gotoparent', options } ),
goTo: ( path: string, options?: NavigateOptions ) =>
dispatch( { type: 'goto', path, options } ),
goToParent: ( options: NavigateToParentOptions | undefined ) =>
dispatch( { type: 'gotoparent', options } ),
goToParent: ( options: NavigateToParentOptions | undefined ) => {
deprecated( `wp.components.useNavigator().goToParent`, {
since: '6.7',
alternative: 'wp.components.useNavigator().goBack',
} );
dispatch( { type: 'gotoparent', options } );
},
addScreen: ( screen: Screen ) =>
dispatch( { type: 'add', screen } ),
removeScreen: ( screen: Screen ) =>
Expand Down
14 changes: 13 additions & 1 deletion packages/components/src/navigator/navigator-screen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ The component accepts the following props:

### `path`: `string`

The screen's path, matched against the current path stored in the navigator.
The screen&quot;s path, matched against the current path stored in the navigator.

`Navigator` assumes that screens are organized hierarchically according to their `path`, which should follow a URL-like scheme where each path segment starts with and is separated by the `/` character.

`Navigator` will treat "back" navigations as going to the parent screen — it is therefore responsibility of the consumer of the component to create the correct screen hierarchy.

For example:

- `/` is the root of all paths. There should always be a screen with `path="/"`.
- `/parent/child` is a child of `/parent`.
- `/parent/child/grand-child` is a child of `/parent/child`.
- `/parent/:param` is a child of `/parent` as well.
- if the current screen has a `path` with value `/parent/child/grand-child`, when going "back" `Navigator` will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) is found.

- Required: Yes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like we can remove the experimental callout now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning on removing those experimental callouts and Storybook tags once we also export the component without the experimental prefix (in a follow-up PR)

</div>

This component is deprecated. Please use the [`NavigatorBackButton`](/packages/components/src/navigator/navigator-back-button/README.md) component instead.

The `NavigatorToParentButton` component can be used to navigate to a screen and should be used in combination with the [`NavigatorProvider`](/packages/components/src/navigator/navigator-provider/README.md), the [`NavigatorScreen`](/packages/components/src/navigator/navigator-screen/README.md) and the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md) components (or the `useNavigator` hook).

## Usage
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,33 @@
/**
* External dependencies
* WordPress dependencies
*/
import type { ForwardedRef } from 'react';
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import { NavigatorBackButton } from '../navigator-back-button';
import type { WordPressComponentProps } from '../../context';
import { contextConnect } from '../../context';
import { View } from '../../view';
import { useNavigatorBackButton } from '../navigator-back-button/hook';
import type { NavigatorToParentButtonProps } from '../types';
import type { NavigatorBackButtonProps } from '../types';

function UnconnectedNavigatorToParentButton(
props: WordPressComponentProps< NavigatorToParentButtonProps, 'button' >,
forwardedRef: ForwardedRef< any >
props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >,
forwardedRef: React.ForwardedRef< any >
) {
const navigatorToParentButtonProps = useNavigatorBackButton( {
...props,
goToParent: true,
deprecated( 'wp.components.NavigatorToParentButton', {
since: '6.7',
alternative: 'wp.components.NavigatorBackButton',
} );

return <View ref={ forwardedRef } { ...navigatorToParentButtonProps } />;
return <NavigatorBackButton ref={ forwardedRef } { ...props } />;
}

/*
* The `NavigatorToParentButton` component can be used to navigate to a screen and
* should be used in combination with the `NavigatorProvider`, the
* `NavigatorScreen` and the `NavigatorButton` components (or the `useNavigator`
* hook).
*
* @example
* ```jsx
* import {
* __experimentalNavigatorProvider as NavigatorProvider,
* __experimentalNavigatorScreen as NavigatorScreen,
* __experimentalNavigatorButton as NavigatorButton,
* __experimentalNavigatorToParentButton as NavigatorToParentButton,
* } from '@wordpress/components';
*
* const MyNavigation = () => (
* <NavigatorProvider initialPath="/">
* <NavigatorScreen path="/">
* <p>This is the home screen.</p>
* <NavigatorButton path="/child">
* Navigate to child screen.
* </NavigatorButton>
* </NavigatorScreen>
/**
* _Note: this component is deprecated. Please use the `NavigatorBackButton`
* component instead._
*
* <NavigatorScreen path="/child">
* <p>This is the child screen.</p>
* <NavigatorToParentButton>
* Go to parent
* </NavigatorToParentButton>
* </NavigatorScreen>
* </NavigatorProvider>
* );
* ```
* @deprecated
*/
export const NavigatorToParentButton = contextConnect(
UnconnectedNavigatorToParentButton,
Expand Down
Loading
Loading