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

Navigation component: Store navigation tree in context v2 #25340

Merged
merged 17 commits into from
Sep 17, 2020
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
9 changes: 8 additions & 1 deletion packages/components/src/navigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Optional className for the `NavigationMenu` component.
- Required: No
- Default: "root"

The menu slug.
The unique identifier of the menu. The root menu can omit this, and it will default to "root"; all other menus need to specify it.

### `parentMenu`

Expand Down Expand Up @@ -158,6 +158,13 @@ Optional className for the `NavigationItem` component.

If provided, renders `a` instead of `button`.

### `item`

- Type: `string`
- Required: Yes

The unique identifier of the item.

### `navigateToMenu`

- Type: `string`
Expand Down
12 changes: 12 additions & 0 deletions packages/components/src/navigation/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,17 @@ export const NavigationContext = createContext( {
activeItem: undefined,
activeMenu: ROOT_MENU,
setActiveMenu: noop,

navigationTree: {
items: {},
getItem: noop,
addItem: noop,
removeItem: noop,

menus: {},
getMenu: noop,
addMenu: noop,
removeMenu: noop,
},
} );
export const useNavigationContext = () => useContext( NavigationContext );
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ import classnames from 'classnames';
/**
* Internal dependencies
*/
import { GroupTitleUI } from './styles/navigation-styles';
import { GroupTitleUI } from '../styles/navigation-styles';
import { useNavigationMenuContext } from '../menu/context';

export default function NavigationGroup( { children, className, title } ) {
const { isActive } = useNavigationMenuContext();

// Keep the children rendered to make sure inactive items are included in the navigation tree
if ( ! isActive ) {
return children;
}

const classes = classnames( 'components-navigation__group', className );

return (
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/navigation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Animate from '../animate';
import { ROOT_MENU } from './constants';
import { NavigationContext } from './context';
import { NavigationUI } from './styles/navigation-styles';
import { useCreateNavigationTree } from './use-create-navigation-tree';

export default function Navigation( {
activeItem,
Expand All @@ -26,6 +27,7 @@ export default function Navigation( {
} ) {
const [ menu, setMenu ] = useState( activeMenu );
const [ slideOrigin, setSlideOrigin ] = useState();
const navigationTree = useCreateNavigationTree();

const setActiveMenu = ( menuId, slideInOrigin = 'left' ) => {
setSlideOrigin( slideInOrigin );
Expand All @@ -51,6 +53,7 @@ export default function Navigation( {
activeItem,
activeMenu: menu,
setActiveMenu,
navigationTree,
};

const classes = classnames( 'components-navigation', className );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,34 @@ import { Icon, chevronRight } from '@wordpress/icons';
/**
* Internal dependencies
*/
import Button from '../button';
import { useNavigationContext } from './context';
import { ItemBadgeUI, ItemTitleUI, ItemUI } from './styles/navigation-styles';
import Button from '../../button';
import { useNavigationContext } from '../context';
import { ItemBadgeUI, ItemTitleUI, ItemUI } from '../styles/navigation-styles';
import { useNavigationTreeItem } from './use-navigation-tree-item';
import { useNavigationMenuContext } from '../menu/context';

export default function NavigationItem( {
badge,
children,
className,
href,
item,
navigateToMenu,
onClick = noop,
title,
...props
} ) {
export default function NavigationItem( props ) {
const {
badge,
children,
className,
href,
item,
navigateToMenu,
onClick = noop,
title,
...restProps
} = props;
useNavigationTreeItem( props );
const { activeItem, setActiveMenu } = useNavigationContext();
const { isActive } = useNavigationMenuContext();

// If this item is in an inactive menu, then we skip rendering
// We need to make sure this component gets mounted though
// To make sure inactive items are included in the navigation tree
if ( ! isActive ) {
return null;
}

const classes = classnames( 'components-navigation__item', className, {
'is-active': item && activeItem === item,
Expand All @@ -44,7 +56,7 @@ export default function NavigationItem( {
return (
<ItemUI className={ classes }>
{ children || (
<Button href={ href } onClick={ onItemClick } { ...props }>
<Button href={ href } onClick={ onItemClick } { ...restProps }>
{ title && (
<ItemTitleUI
className="components-navigation__item-title"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { useNavigationContext } from '../context';
import { useNavigationMenuContext } from '../menu/context';

export const useNavigationTreeItem = ( props ) => {
const {
navigationTree: { addItem, removeItem },
} = useNavigationContext();
const { menu } = useNavigationMenuContext();

const key = props.item;
useEffect( () => {
addItem( key, { ...props, menu } );

return () => {
removeItem( key );
};
}, [] );
};
63 changes: 0 additions & 63 deletions packages/components/src/navigation/menu.js

This file was deleted.

11 changes: 11 additions & 0 deletions packages/components/src/navigation/menu/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* WordPress dependencies
*/
import { createContext, useContext } from '@wordpress/element';

export const NavigationMenuContext = createContext( {
menu: undefined,
isActive: false,
} );
export const useNavigationMenuContext = () =>
useContext( NavigationMenuContext );
80 changes: 80 additions & 0 deletions packages/components/src/navigation/menu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Icon, chevronLeft } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { ROOT_MENU } from '../constants';
import { useNavigationContext } from '../context';
import {
MenuBackButtonUI,
MenuTitleUI,
MenuUI,
} from '../styles/navigation-styles';
import { NavigationMenuContext } from './context';
import { useNavigationTreeMenu } from './use-navigation-tree-menu';

export default function NavigationMenu( props ) {
const {
backButtonLabel,
children,
className,
menu = ROOT_MENU,
parentMenu,
title,
} = props;
useNavigationTreeMenu( props );
const { activeMenu, setActiveMenu } = useNavigationContext();
const isActive = activeMenu === menu;

const classes = classnames( 'components-navigation__menu', className );

const context = {
menu,
isActive,
};

// Keep the children rendered to make sure inactive items are included in the navigation tree
if ( ! isActive ) {
return (
<NavigationMenuContext.Provider value={ context }>
{ children }
</NavigationMenuContext.Provider>
);
}

return (
<NavigationMenuContext.Provider value={ context }>
<MenuUI className={ classes }>
{ parentMenu && (
<MenuBackButtonUI
className="components-navigation__back-button"
isTertiary
onClick={ () => setActiveMenu( parentMenu, 'right' ) }
>
<Icon icon={ chevronLeft } />
{ backButtonLabel || __( 'Back' ) }
</MenuBackButtonUI>
) }
{ title && (
<MenuTitleUI
as="h2"
className="components-navigation__menu-title"
variant="subtitle"
>
{ title }
</MenuTitleUI>
) }
<ul>{ children }</ul>
</MenuUI>
</NavigationMenuContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { useNavigationContext } from '../context';
import { ROOT_MENU } from '../constants';

export const useNavigationTreeMenu = ( props ) => {
const {
navigationTree: { addMenu, removeMenu },
} = useNavigationContext();

const key = props.menu || ROOT_MENU;
useEffect( () => {
addMenu( key, { ...props, menu: key } );

return () => {
removeMenu( key );
};
}, [] );
};
32 changes: 32 additions & 0 deletions packages/components/src/navigation/use-create-navigation-tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Internal dependencies
*/
import { useNavigationTreeNodes } from './use-navigation-tree-nodes';

export const useCreateNavigationTree = () => {
const {
nodes: items,
getNode: getItem,
addNode: addItem,
removeNode: removeItem,
} = useNavigationTreeNodes();

const {
nodes: menus,
getNode: getMenu,
addNode: addMenu,
removeNode: removeMenu,
} = useNavigationTreeNodes();

return {
items,
getItem,
addItem,
removeItem,

menus,
getMenu,
addMenu,
removeMenu,
};
};
Loading