Skip to content

Commit

Permalink
Add animations to navigation component (#24771)
Browse files Browse the repository at this point in the history
* Navigation Component: Remove setActiveLevel child function arg (#24704)

* remove setActiveLevel from public api

* change to NavigationBackButton

* return null for backButton if no parentLevel

* Navigation Component: Expose __experimentalNavigation component (#24706)

* Expose __experimentalNavigation component

* fix typo

* expose menut and menu-item as well

* Loop over navigation levels and wrap with animate

* Use map and set to organize items and levels

* Simplify level and item logic

* Remove unnecessary key prop

* Fix parent level assignment

* Add back in key prop to force animation

* Use useMemo to map item data

Co-authored-by: Paul Sealock <[email protected]>
  • Loading branch information
joshuatf and psealock authored Aug 26, 2020
1 parent 67ff7e2 commit b5508b4
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 41 deletions.
2 changes: 2 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Introduce `Navigation` component as `__experimentalNavigation` for displaying a heirarchy of items.

## 10.0.0 (2020-07-07)

### Breaking Change
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export { default as MenuItemsChoice } from './menu-items-choice';
export { default as Modal } from './modal';
export { default as ScrollLock } from './scroll-lock';
export { NavigableMenu, TabbableContainer } from './navigable-container';
export {
default as __experimentalNavigation,
NavigationMenu as __experimentalNavigationMenu,
NavigationMenuItem as __experimentalNavigationMenuItem,
} from './navigation';
export { default as Notice } from './notice';
export { default as __experimentalNumberControl } from './number-control';
export { default as NoticeList } from './notice/list';
Expand Down
117 changes: 89 additions & 28 deletions packages/components/src/navigation/index.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,115 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useEffect, useState } from '@wordpress/element';
import { useEffect, useMemo, useState } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';

/**
* Internal dependencies
*/
import Animate from '../animate';
import { Root } from './styles/navigation-styles';
import Button from '../button';

const Navigation = ( { activeItemId, children, data, rootTitle } ) => {
const [ activeLevel, setActiveLevel ] = useState( 'root' );

const mapItemData = ( items ) => {
return items.map( ( item ) => {
const itemChildren = data.filter( ( i ) => i.parent === item.id );
return {
...item,
children: itemChildren,
parent: item.parent || 'root',
isActive: item.id === activeItemId,
hasChildren: itemChildren.length > 0,
};
const [ activeLevelId, setActiveLevelId ] = useState( 'root' );

const appendItemData = ( item ) => {
return {
...item,
children: [],
parent: item.id === 'root' ? null : item.parent || 'root',
isActive: item.id === activeItemId,
setActiveLevelId,
};
};

const mapItems = ( itemData ) => {
const items = new Map(
[
{ id: 'root', parent: null, title: rootTitle },
...itemData,
].map( ( item ) => [ item.id, appendItemData( item ) ] )
);

items.forEach( ( item ) => {
const parentItem = items.get( item.parent );
if ( parentItem ) {
parentItem.children.push( item );
parentItem.hasChildren = true;
}
} );

return items;
};
const items = [ { id: 'root', title: rootTitle }, ...mapItemData( data ) ];

const activeItem = items.find( ( item ) => item.id === activeItemId );
const level = items.find( ( item ) => item.id === activeLevel );
const levelItems = items.filter( ( item ) => item.parent === level.id );
const parentLevel =
level.id === 'root'
? null
: items.find( ( item ) => item.id === level.parent );
const items = useMemo( () => mapItems( data ), [
data,
activeItemId,
rootTitle,
] );
const activeItem = items.get( activeItemId );
const previousActiveLevelId = usePrevious( activeLevelId );
const level = items.get( activeLevelId );
const parentLevel = level && items.get( level.parent );
const isNavigatingBack =
previousActiveLevelId &&
items.get( previousActiveLevelId ).parent === activeLevelId;

useEffect( () => {
if ( activeItem ) {
setActiveLevel( activeItem.parent );
setActiveLevelId( activeItem.parent );
}
}, [] );

const NavigationBackButton = ( { children: backButtonChildren } ) => {
if ( ! parentLevel ) {
return null;
}

return (
<Button
isPrimary
onClick={ () => setActiveLevelId( parentLevel.id ) }
>
{ backButtonChildren }
</Button>
);
};

return (
<Root className="components-navigation">
{ children( {
level,
levelItems,
parentLevel,
setActiveLevel,
} ) }
<Animate
key={ level.id }
type="slide-in"
options={ {
origin: isNavigatingBack ? 'right' : 'left',
} }
>
{ ( { className: animateClassName } ) => (
<div
className={ classnames(
'components-navigation__level',
animateClassName
) }
>
{ children( {
level,
NavigationBackButton,
parentLevel,
} ) }
</div>
) }
</Animate>
</Root>
);
};

export default Navigation;
export { default as NavigationMenu } from './menu';
export { default as NavigationMenuItem } from './menu-item';
4 changes: 2 additions & 2 deletions packages/components/src/navigation/menu-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const NavigationMenuItem = ( props ) => {
LinkComponent,
linkProps,
onClick,
setActiveLevel,
setActiveLevelId,
title,
} = props;
const classes = classnames( 'components-navigation__menu-item', {
Expand All @@ -35,7 +35,7 @@ const NavigationMenuItem = ( props ) => {

const handleClick = () => {
if ( children.length ) {
setActiveLevel( id );
setActiveLevelId( id );
return;
}
onClick( props );
Expand Down
33 changes: 22 additions & 11 deletions packages/components/src/navigation/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { Button } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { Icon, arrowLeft } from '@wordpress/icons';

/**
* Internal dependencies
Expand Down Expand Up @@ -48,6 +49,21 @@ const data = [
id: 'child-2',
parent: 'item-3',
},
{
title: 'Nested Category',
id: 'child-3',
parent: 'item-3',
},
{
title: 'Sub Child 1',
id: 'sub-child-1',
parent: 'child-3',
},
{
title: 'Sub Child 2',
id: 'sub-child-2',
parent: 'child-3',
},
{
title: 'External link',
id: 'item-4',
Expand All @@ -68,30 +84,25 @@ function Example() {

return (
<Navigation activeItemId={ active } data={ data } rootTitle="Home">
{ ( { level, levelItems, parentLevel, setActiveLevel } ) => {
{ ( { level, parentLevel, NavigationBackButton } ) => {
return (
<>
{ parentLevel && (
<Button
isPrimary
onClick={ () =>
setActiveLevel( parentLevel.id )
}
>
Back
</Button>
<NavigationBackButton>
<Icon icon={ arrowLeft } />
{ parentLevel.title }
</NavigationBackButton>
) }
<h1>{ level.title }</h1>
<NavigationMenu>
{ levelItems.map( ( item ) => {
{ level.children.map( ( item ) => {
return (
<NavigationMenuItem
{ ...item }
key={ item.id }
onClick={ ( selected ) =>
setActive( selected.id )
}
setActiveLevel={ setActiveLevel }
/>
);
} ) }
Expand Down

0 comments on commit b5508b4

Please sign in to comment.