Skip to content

Commit

Permalink
Navigator: stabilize and export APIs (#64613)
Browse files Browse the repository at this point in the history
* Remove experimental status from Storybook

* NavigatorProvider => Navigator (internally)

* Move legacy exports to separate file

* Export overloaded notation

* Use overloaded notation in Storybook

* Use overloaded notation in unit tests (skipping deprecated component for now)

* Update docs manifest.json

* Navigator: update docs

* Navigator.BackButton: update docs

* NavigatorToParentButton: delete README

* Navigator.Button: update docs

* NavigatorScreen: update docs

* Restore `NavigatorToParentButton` test

* Use dot notation in context namespaces too

* Use named export for `NavigatorToParentButton` too

* Fixup docs manifest

* Export stable version from components package

* CHANGELOG

* Change index.ts extension to tsx to allow Storybook to extract JSDocs better, remove duplicate JSDocs

* Remove unnecessary and mispelled display name for top-level `Navigator`

* Remove @example tag from top-level export

* Clean up unused imports

* Fix storybook styles

* Update new section of the docs

* Update more docs

* Update deprecation warning

* Update and align docs

* Fix tests warning checks

* Typo

* Fix useContextSystem names

* Remove mentions of the `useNavigator` hook

* useNavigator JSDocs

* Move all READMEs under the main README

* Move README to component's root folder

* Update types JSDocs to match README

* Update docs manifest

* Fix JSDocs typos, grammar, and broken links.

* Update storybook selector

---

Co-authored-by: ciampo <[email protected]>
Co-authored-by: mirka <[email protected]>
Co-authored-by: tyxla <[email protected]>
  • Loading branch information
4 people authored Sep 27, 2024
1 parent 3e0d294 commit 6f0fd8e
Show file tree
Hide file tree
Showing 24 changed files with 445 additions and 385 deletions.
30 changes: 3 additions & 27 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1098,33 +1098,9 @@
"parent": "components"
},
{
"title": "NavigatorBackButton",
"slug": "navigator-back-button",
"markdown_source": "../packages/components/src/navigator/navigator-back-button/README.md",
"parent": "components"
},
{
"title": "NavigatorButton",
"slug": "navigator-button",
"markdown_source": "../packages/components/src/navigator/navigator-button/README.md",
"parent": "components"
},
{
"title": "NavigatorProvider",
"slug": "navigator-provider",
"markdown_source": "../packages/components/src/navigator/navigator-provider/README.md",
"parent": "components"
},
{
"title": "NavigatorScreen",
"slug": "navigator-screen",
"markdown_source": "../packages/components/src/navigator/navigator-screen/README.md",
"parent": "components"
},
{
"title": "NavigatorToParentButton",
"slug": "navigator-to-parent-button",
"markdown_source": "../packages/components/src/navigator/navigator-to-parent-button/README.md",
"title": "Navigator",
"slug": "navigator",
"markdown_source": "../packages/components/src/navigator/README.md",
"parent": "components"
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- `Navigator`: add support for exit animation ([#64777](https://github.com/WordPress/gutenberg/pull/64777)).
- `Guide`: Update finish button to use the new default size ([#65680](https://github.com/WordPress/gutenberg/pull/65680)).
- `BorderControl`: Use `__next40pxDefaultSize` prop for Reset button ([#65682](https://github.com/WordPress/gutenberg/pull/65682)).
- `Navigator`: stabilize APIs ([#64613](https://github.com/WordPress/gutenberg/pull/64613)).

## 28.8.0 (2024-09-19)

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export {
NavigatorToParentButton as __experimentalNavigatorToParentButton,
useNavigator as __experimentalUseNavigator,
} from './navigator/legacy';
export { Navigator, useNavigator } from './navigator';
export { default as Notice } from './notice';
export { default as __experimentalNumberControl } from './number-control';
export { default as NoticeList } from './notice/list';
Expand Down
168 changes: 168 additions & 0 deletions packages/components/src/navigator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# `Navigator`

`Navigator` is a collection components that allow rendering nested views/panels/menus (via the `Navigator.Screen` component) and navigate between them (via the `Navigator.Button` and `Navigator.BackButton` components).

## Usage

```jsx
import { Navigator } from '@wordpress/components';

const MyNavigation = () => (
<Navigator initialPath="/">
<Navigator.Screen path="/">
<p>This is the home screen.</p>
<Navigator.Button path="/child">
Navigate to child screen.
</Navigator.Button>
</Navigator.Screen>
<Navigator.Screen path="/child">
<p>This is the child screen.</p>
<Navigator.BackButton>Go back</Navigator.BackButton>
</Navigator.Screen>
</Navigator>
);
```

### Hierarchical `path`s

`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, the 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="/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.

### Height and animations

Due to how `Navigator.Screen` animations work, it is recommended that the `Navigator` component is assigned a `height` to prevent some potential UI jumps while moving across screens.

### Individual components

`Navigator` is comprised of four individual components:

- `Navigator`: a wrapper component and context provider. It holds the main logic for hiding and showing screens.
- `Navigator.Screen`: represents a single view/screen/panel;
- `Navigator.Button`: renders a button that allows navigating to a different `Navigator.Screen`;
- `Navigator.BackButton`: renders a button that allows navigating to the parent `Navigator.Screen` (see the section above about hierarchical paths).

For advanced usages, consumers can use the `useNavigator` hook.

#### `Navigator`

##### Props

###### `initialPath`: `string`

The initial active path.

- Required: Yes

###### `children`: `string`

The children elements.

- Required: Yes

#### `Navigator.Screen`

###### `path`: `string`

The screen'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, the 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

###### `children`: `string`

The children elements.

- Required: Yes

##### `Navigator.Button`

###### `path`: `string`

The path of the screen to navigate to. The value of this prop needs to be [a valid value for an HTML attribute](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2).

- Required: Yes

###### `attributeName`: `string`

The HTML attribute used to identify the `Navigator.Button`, which is used by `Navigator` to restore focus.

- Required: No
- Default: `id`

###### `children`: `string`

The children elements.

- Required: No

###### Inherited props

`Navigator.Button` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href` and `target`.

##### `Navigator.BackButton`

###### `children`: `string`

The children elements.

- Required: No

###### Inherited props

`Navigator.BackButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href` and `target`.

###### `useNavigator`

You can retrieve a `navigator` instance by using the `useNavigator` hook.

The `navigator` instance has a few properties:

###### `goTo`: `( path: string, options: NavigateOptions ) => void`

The `goTo` function allows navigating to a given path. The second argument can augment the navigation operations with different options.

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);
- `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;

###### `goBack`: `( path: string, options: NavigateOptions ) => void`

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 `/`) is found.

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 represents 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.
- `isInitial`: `boolean`. A flag that is `true` only for the initial location.

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

The parsed record of parameters from the current location. For example if the current screen path is `/product/:productId` and the location is `/product/123`, then `params` will be `{ productId: '123' }`.
131 changes: 131 additions & 0 deletions packages/components/src/navigator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* Internal dependencies
*/
import { Navigator as TopLevelNavigator } from './navigator/component';
import { NavigatorScreen } from './navigator-screen/component';
import { NavigatorButton } from './navigator-button/component';
import { NavigatorBackButton } from './navigator-back-button/component';
export { useNavigator } from './use-navigator';

/**
* The `Navigator` component allows rendering nested views/panels/menus
* (via the `Navigator.Screen` component) and navigate between them
* (via the `Navigator.Button` and `Navigator.BackButton` components).
*
* ```jsx
* import { Navigator } from '@wordpress/components';
*
* const MyNavigation = () => (
* <Navigator initialPath="/">
* <Navigator.Screen path="/">
* <p>This is the home screen.</p>
* <Navigator.Button path="/child">
* Navigate to child screen.
* </Navigator.Button>
* </Navigator.Screen>
*
* <Navigator.Screen path="/child">
* <p>This is the child screen.</p>
* <Navigator.BackButton>
* Go back
* </Navigator.BackButton>
* </Navigator.Screen>
* </Navigator>
* );
* ```
*/
export const Navigator = Object.assign( TopLevelNavigator, {
/**
* The `Navigator.Screen` component represents a single view/screen/panel and
* should be used in combination with the `Navigator`, the `Navigator.Button`
* and the `Navigator.BackButton` components.
*
* @example
* ```jsx
* import { Navigator } from '@wordpress/components';
*
* const MyNavigation = () => (
* <Navigator initialPath="/">
* <Navigator.Screen path="/">
* <p>This is the home screen.</p>
* <Navigator.Button path="/child">
* Navigate to child screen.
* </Navigator.Button>
* </Navigator.Screen>
*
* <Navigator.Screen path="/child">
* <p>This is the child screen.</p>
* <Navigator.BackButton>
* Go back
* </Navigator.BackButton>
* </Navigator.Screen>
* </Navigator>
* );
* ```
*/
Screen: Object.assign( NavigatorScreen, {
displayName: 'Navigator.Screen',
} ),
/**
* The `Navigator.Button` component can be used to navigate to a screen and
* should be used in combination with the `Navigator`, the `Navigator.Screen`
* and the `Navigator.BackButton` components.
*
* @example
* ```jsx
* import { Navigator } from '@wordpress/components';
*
* const MyNavigation = () => (
* <Navigator initialPath="/">
* <Navigator.Screen path="/">
* <p>This is the home screen.</p>
* <Navigator.Button path="/child">
* Navigate to child screen.
* </Navigator.Button>
* </Navigator.Screen>
*
* <Navigator.Screen path="/child">
* <p>This is the child screen.</p>
* <Navigator.BackButton>
* Go back
* </Navigator.BackButton>
* </Navigator.Screen>
* </Navigator>
* );
* ```
*/
Button: Object.assign( NavigatorButton, {
displayName: 'Navigator.Button',
} ),
/**
* The `Navigator.BackButton` component can be used to navigate to a screen and
* should be used in combination with the `Navigator`, the `Navigator.Screen`
* and the `Navigator.Button` components.
*
* @example
* ```jsx
* import { Navigator } from '@wordpress/components';
*
* const MyNavigation = () => (
* <Navigator initialPath="/">
* <Navigator.Screen path="/">
* <p>This is the home screen.</p>
* <Navigator.Button path="/child">
* Navigate to child screen.
* </Navigator.Button>
* </Navigator.Screen>
*
* <Navigator.Screen path="/child">
* <p>This is the child screen.</p>
* <Navigator.BackButton>
* Go back
* </Navigator.BackButton>
* </Navigator.Screen>
* </Navigator>
* );
* ```
*/
BackButton: Object.assign( NavigatorBackButton, {
displayName: 'Navigator.BackButton',
} ),
} );
Loading

1 comment on commit 6f0fd8e

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 6f0fd8e.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/11073085179
📝 Reported issues:

Please sign in to comment.