Skip to content

Commit ba08a42

Browse files
committed
Add NavigatorLink and NavigatorBackLink, replace usages across codebase
1 parent 3af4385 commit ba08a42

File tree

12 files changed

+260
-76
lines changed

12 files changed

+260
-76
lines changed

packages/components/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export { default as __experimentalNavigationMenu } from './navigation/menu';
100100
export {
101101
NavigatorProvider as __experimentalNavigatorProvider,
102102
NavigatorScreen as __experimentalNavigatorScreen,
103+
NavigatorLink as __experimentalNavigatorLink,
104+
NavigatorBackLink as __experimentalNavigatorBackLink,
103105
useNavigator as __experimentalUseNavigator,
104106
} from './navigator';
105107
export { default as Notice } from './notice';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export { default as NavigatorProvider } from './navigator-provider';
22
export { default as NavigatorScreen } from './navigator-screen';
3+
export { default as NavigatorLink } from './navigator-link';
4+
export { default as NavigatorBackLink } from './navigator-back-link';
35
export { default as useNavigator } from './use-navigator';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `NavigatorBackLink`
2+
3+
<div class="callout callout-alert">
4+
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5+
</div>
6+
7+
**TODO**
8+
9+
The `Navigator*` family of components is _not_ opinionated in terms of UI, and can be composed with any UI components to navigate between the nested screens.
10+
11+
## Usage
12+
13+
**TODO**
14+
15+
## Props
16+
17+
The component accepts the following props:
18+
19+
**TODO**
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* External dependencies
3+
*/
4+
// eslint-disable-next-line no-restricted-imports
5+
import type { Ref } from 'react';
6+
7+
/**
8+
* WordPress dependencies
9+
*/
10+
import { useCallback } from '@wordpress/element';
11+
12+
/**
13+
* Internal dependencies
14+
*/
15+
import {
16+
contextConnect,
17+
useContextSystem,
18+
WordPressComponentProps,
19+
} from '../../ui/context';
20+
import { View } from '../../view';
21+
import useNavigator from '../use-navigator';
22+
import type { NavigatorBackLinkProps } from '../types';
23+
24+
function NavigatorBackLink(
25+
props: WordPressComponentProps< NavigatorBackLinkProps, 'button' >,
26+
forwardedRef: Ref< any >
27+
) {
28+
const {
29+
children,
30+
onClick,
31+
as = 'button',
32+
...otherProps
33+
} = useContextSystem( props, 'NavigatorBackLink' );
34+
35+
const { pop } = useNavigator();
36+
const handleClick: React.MouseEventHandler< HTMLElement > = useCallback(
37+
( e ) => {
38+
e.preventDefault();
39+
pop();
40+
onClick?.( e );
41+
},
42+
[ pop, onClick ]
43+
);
44+
45+
return (
46+
<View
47+
as={ as }
48+
ref={ forwardedRef }
49+
onClick={ handleClick }
50+
{ ...otherProps }
51+
>
52+
{ children }
53+
</View>
54+
);
55+
}
56+
57+
/**
58+
* TODO: add example
59+
*/
60+
const ConnectedNavigatorBackLink = contextConnect(
61+
NavigatorBackLink,
62+
'NavigatorBackLink'
63+
);
64+
65+
export default ConnectedNavigatorBackLink;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './component';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `NavigatorLink`
2+
3+
<div class="callout callout-alert">
4+
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5+
</div>
6+
7+
**TODO**
8+
9+
The `Navigator*` family of components is _not_ opinionated in terms of UI, and can be composed with any UI components to navigate between the nested screens.
10+
11+
## Usage
12+
13+
**TODO**
14+
15+
## Props
16+
17+
The component accepts the following props:
18+
19+
**TODO**
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* External dependencies
3+
*/
4+
// eslint-disable-next-line no-restricted-imports
5+
import type { Ref } from 'react';
6+
7+
/**
8+
* WordPress dependencies
9+
*/
10+
import { useCallback } from '@wordpress/element';
11+
12+
/**
13+
* Internal dependencies
14+
*/
15+
import {
16+
contextConnect,
17+
useContextSystem,
18+
WordPressComponentProps,
19+
} from '../../ui/context';
20+
import { View } from '../../view';
21+
import useNavigator from '../use-navigator';
22+
import type { NavigatorLinkProps } from '../types';
23+
24+
const defaultAttributeName = 'data-navigator-focusable-id';
25+
const defaultSelectorFactory = ( attrName: string, attrValue: string ) =>
26+
`[${ attrName }="${ attrValue }"]`;
27+
28+
function NavigatorLink(
29+
props: WordPressComponentProps< NavigatorLinkProps, 'a' >,
30+
forwardedRef: Ref< any >
31+
) {
32+
const {
33+
path,
34+
children,
35+
onClick,
36+
as = 'button',
37+
attributeName = defaultAttributeName,
38+
selectorFactory = defaultSelectorFactory,
39+
...otherProps
40+
} = useContextSystem( props, 'NavigatorLink' );
41+
42+
const { push } = useNavigator();
43+
const focusTargetSelector = selectorFactory( attributeName, path );
44+
const handleClick: React.MouseEventHandler< HTMLElement > = useCallback(
45+
( e ) => {
46+
e.preventDefault();
47+
push( path, { focusTargetSelector } );
48+
onClick?.( e );
49+
},
50+
[ push, selectorFactory, onClick ]
51+
);
52+
53+
const linkProps = { [ attributeName ]: path, path, ...otherProps };
54+
55+
return (
56+
<View
57+
as={ as }
58+
ref={ forwardedRef }
59+
onClick={ handleClick }
60+
{ ...linkProps }
61+
>
62+
{ children }
63+
</View>
64+
);
65+
}
66+
67+
/**
68+
* TODO: add example
69+
*/
70+
const ConnectedNavigatorLink = contextConnect( NavigatorLink, 'NavigatorLink' );
71+
72+
export default ConnectedNavigatorLink;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './component';

packages/components/src/navigator/stories/index.js

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,24 @@ import { Card, CardBody, CardFooter, CardHeader } from '../../card';
1111
import { HStack } from '../../h-stack';
1212
import { Flyout } from '../../flyout';
1313
import { useCx } from '../../utils/hooks/use-cx';
14-
import { NavigatorProvider, NavigatorScreen, useNavigator } from '../';
14+
import {
15+
NavigatorProvider,
16+
NavigatorScreen,
17+
NavigatorLink,
18+
NavigatorBackLink,
19+
} from '../';
1520

1621
export default {
1722
title: 'Components (Experimental)/Navigator',
1823
component: NavigatorProvider,
1924
};
2025

21-
function NavigatorButton( { path, ...props } ) {
22-
const { push } = useNavigator();
23-
const dataAttrName = 'data-navigator-focusable-id';
24-
const dataAttrValue = path;
25-
26-
const dataAttrCssSelector = `[${ dataAttrName }="${ dataAttrValue }"]`;
27-
28-
const buttonProps = {
29-
...props,
30-
[ dataAttrName ]: dataAttrValue,
31-
};
32-
33-
return (
34-
<Button
35-
variant="secondary"
36-
onClick={ () =>
37-
push( path, { focusTargetSelector: dataAttrCssSelector } )
38-
}
39-
{ ...buttonProps }
40-
/>
41-
);
26+
function NavigatorButton( props ) {
27+
return <NavigatorLink as={ Button } variant="secondary" { ...props } />;
4228
}
4329

44-
function NavigatorBackButton( { ...props } ) {
45-
const { pop } = useNavigator();
46-
return <Button variant="secondary" onClick={ pop } { ...props } />;
30+
function NavigatorBackButton( props ) {
31+
return <NavigatorBackLink as={ Button } variant="secondary" { ...props } />;
4732
}
4833

4934
const MyNavigation = () => {

packages/components/src/navigator/types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,38 @@ export type NavigatorScreenProps = {
4444
*/
4545
children: ReactNode;
4646
};
47+
48+
export type NavigatorBackLinkProps = {
49+
/**
50+
* The children elements.
51+
*/
52+
children: ReactNode;
53+
/**
54+
* The callback called in response to a `click` event.
55+
*/
56+
onClick?: React.MouseEventHandler< HTMLElement >;
57+
};
58+
59+
export type NavigatorLinkProps = NavigatorBackLinkProps & {
60+
/**
61+
* The path to navigate to.
62+
*/
63+
path: string;
64+
/**
65+
* The HTML attribute used to identify the `NavigatorLink`, which is used
66+
* by `Navigator` to restore focus.
67+
*
68+
* @default 'data-navigator-focusable-id'
69+
*/
70+
attributeName?: string;
71+
/**
72+
* A function that generates the CSS selector used ny `Navigator` when
73+
* restoring focus.
74+
*
75+
* @default ( attrName, attrValue ) => `[${ attrName }="${ attrValue }"]`;
76+
*/
77+
selectorFactory?: (
78+
attributeName: string,
79+
attributeValue: string
80+
) => string;
81+
};

packages/edit-post/src/components/preferences-modal/index.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { get } from 'lodash';
99
import {
1010
__experimentalNavigatorProvider as NavigatorProvider,
1111
__experimentalNavigatorScreen as NavigatorScreen,
12-
__experimentalUseNavigator as useNavigator,
12+
__experimentalNavigatorLink as NavigatorLink,
13+
__experimentalNavigatorBackLink as NavigatorBackLink,
1314
__experimentalItemGroup as ItemGroup,
1415
__experimentalItem as Item,
1516
__experimentalHStack as HStack,
@@ -55,33 +56,33 @@ import BlockManager from '../block-manager';
5556
const MODAL_NAME = 'edit-post/preferences';
5657
const PREFERENCES_MENU = 'preferences-menu';
5758

58-
function NavigationButton( { as: Tag = Button, path, ...props } ) {
59-
const { push } = useNavigator();
59+
// function NavigationButton( { as: Tag = Button, path, ...props } ) {
60+
// const { push } = useNavigator();
6061

61-
const dataAttrName = 'data-navigator-focusable-id';
62-
const dataAttrValue = path;
62+
// const dataAttrName = 'data-navigator-focusable-id';
63+
// const dataAttrValue = path;
6364

64-
const dataAttrCssSelector = `[${ dataAttrName }="${ dataAttrValue }"]`;
65+
// const dataAttrCssSelector = `[${ dataAttrName }="${ dataAttrValue }"]`;
6566

66-
const tagProps = {
67-
...props,
68-
[ dataAttrName ]: dataAttrValue,
69-
};
67+
// const tagProps = {
68+
// ...props,
69+
// [ dataAttrName ]: dataAttrValue,
70+
// };
7071

71-
return (
72-
<Tag
73-
onClick={ () =>
74-
push( path, { focusTargetSelector: dataAttrCssSelector } )
75-
}
76-
{ ...tagProps }
77-
/>
78-
);
79-
}
72+
// return (
73+
// <Tag
74+
// onClick={ () =>
75+
// push( path, { focusTargetSelector: dataAttrCssSelector } )
76+
// }
77+
// { ...tagProps }
78+
// />
79+
// );
80+
// }
8081

81-
function NavigationBackButton( { as: Tag = Button, ...props } ) {
82-
const { pop } = useNavigator();
83-
return <Tag onClick={ pop } { ...props } />;
84-
}
82+
// function NavigationBackButton( { as: Tag = Button, ...props } ) {
83+
// const { pop } = useNavigator();
84+
// return <Tag onClick={ pop } { ...props } />;
85+
// }
8586

8687
export default function PreferencesModal() {
8788
const isLargeViewport = useViewportMatch( 'medium' );
@@ -347,7 +348,7 @@ export default function PreferencesModal() {
347348
<ItemGroup>
348349
{ tabs.map( ( tab ) => {
349350
return (
350-
<NavigationButton
351+
<NavigatorLink
351352
key={ tab.name }
352353
path={ tab.name }
353354
as={ Item }
@@ -369,7 +370,7 @@ export default function PreferencesModal() {
369370
/>
370371
</FlexItem>
371372
</HStack>
372-
</NavigationButton>
373+
</NavigatorLink>
373374
);
374375
} ) }
375376
</ItemGroup>
@@ -389,7 +390,8 @@ export default function PreferencesModal() {
389390
size="small"
390391
gap="6"
391392
>
392-
<NavigationBackButton
393+
<NavigatorBackLink
394+
as={ Button }
393395
icon={
394396
isRTL() ? chevronRight : chevronLeft
395397
}

0 commit comments

Comments
 (0)