Skip to content

Commit

Permalink
Navigation Component: Hide navigation item if target menu is empty (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
david-szabo97 authored Nov 17, 2020
1 parent 1673ed6 commit fc67e52
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 2 deletions.
16 changes: 15 additions & 1 deletion packages/components/src/navigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,19 @@ The parent menu slug; used by nested menus to indicate their parent menu.

When `hasSearch` is active and `onSearch` is provided, this controls the value of the search input. Required when the `onSearch` prop is provided.

### `isEmpty`

- Type: `boolean`
- Required: No

Indicates whether the menu is empty or not. Used together with the `hideIfTargetMenuEmpty` prop of Navigation Item.

### `title`

- Type: `string`
- Required: No

The menu title. It's also the field used by the menu search function.
The menu title. It's also the field used by the menu search function.

## Navigation Group Props

Expand Down Expand Up @@ -201,6 +208,13 @@ The unique identifier of the item.

The child menu slug. If provided, clicking on the item will navigate to the target menu.

### `hideIfTargetMenuEmpty`

- Type: `boolean`
- Required: No

Indicates whether this item should be hidden if the menu specified in `navigateToMenu` is marked as empty in the `isEmpty` prop. Used together with the `isEmpty` prop of Navigation Menu.

### `onClick`

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

navigationTree: {
items: {},
Expand All @@ -28,6 +29,9 @@ export const NavigationContext = createContext( {
getMenu: noop,
addMenu: noop,
removeMenu: noop,
childMenu: {},
traverseMenu: noop,
isMenuEmpty: noop,
},
} );
export const useNavigationContext = () => useContext( NavigationContext );
12 changes: 12 additions & 0 deletions packages/components/src/navigation/item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function NavigationItem( props ) {
navigateToMenu,
onClick = noop,
title,
hideIfTargetMenuEmpty,
...restProps
} = props;

Expand All @@ -46,6 +47,17 @@ export default function NavigationItem( props ) {
return null;
}

// If hideIfTargetMenuEmpty prop is true
// And the menu we are supposed to navigate to
// Is marked as empty, then we skip rendering the item
if (
hideIfTargetMenuEmpty &&
navigateToMenu &&
navigationTree.isMenuEmpty( navigateToMenu )
) {
return null;
}

const classes = classnames( 'components-navigation__item', className, {
'is-active': item && activeItem === item,
} );
Expand Down
57 changes: 57 additions & 0 deletions packages/components/src/navigation/stories/hide-if-empty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Internal dependencies
*/
import Navigation from '..';
import NavigationItem from '../item';
import NavigationMenu from '../menu';

export function HideIfEmptyStory() {
return (
<>
<Navigation className="navigation-story">
<NavigationMenu title="Home" menu="root" isEmpty={ false }>
<NavigationItem
navigateToMenu="root-sub-1"
title="To sub 1 (hidden)"
hideIfTargetMenuEmpty
/>

<NavigationItem
navigateToMenu="root-sub-2"
title="To sub 2 (visible)"
hideIfTargetMenuEmpty
/>

<NavigationItem
navigateToMenu="root-sub-1-sub-1"
title="To sub 1-1 (hidden)"
hideIfTargetMenuEmpty
/>
</NavigationMenu>

<NavigationMenu
menu="root-sub-1"
parentMenu="root"
isEmpty={ true }
/>
<NavigationMenu
menu="root-sub-2"
parentMenu="root"
isEmpty={ false }
>
<NavigationItem title="This menu is visible" />
</NavigationMenu>
<NavigationMenu
menu="root-sub-1-sub-1"
parentMenu="root-sub-1"
isEmpty={ true }
/>
</Navigation>

<p>
This story contains 3 navigation items and 4 menus. You should
only see one item: <strong>To sub 2 (visible)</strong>
</p>
</>
);
}
2 changes: 2 additions & 0 deletions packages/components/src/navigation/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GroupStory } from './group';
import { ControlledStateStory } from './controlled-state';
import { SearchStory } from './search';
import { MoreExamplesStory } from './more-examples';
import { HideIfEmptyStory } from './hide-if-empty';
import './style.css';

export default {
Expand All @@ -29,3 +30,4 @@ export const controlledState = () => <ControlledStateStory />;
export const groups = () => <GroupStory />;
export const search = () => <SearchStory />;
export const moreExamples = () => <MoreExamplesStory />;
export const hideIfEmpty = () => <HideIfEmptyStory />;
68 changes: 67 additions & 1 deletion packages/components/src/navigation/use-create-navigation-tree.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
Expand All @@ -18,6 +23,50 @@ export const useCreateNavigationTree = () => {
removeNode: removeMenu,
} = useNavigationTreeNodes();

/**
* Stores direct nested menus of menus
* This makes it easy to traverse menu tree
*
* Key is the menu prop of the menu
* Value is an array of menu keys
*/
const [ childMenu, setChildMenu ] = useState( {} );
const getChildMenu = ( menu ) => childMenu[ menu ] || [];

const traverseMenu = ( startMenu, callback ) => {
const visited = [];
let queue = [ startMenu ];
let current;

while ( queue.length > 0 ) {
current = getMenu( queue.shift() );

if ( ! current || visited.includes( current.menu ) ) {
continue;
}

visited.push( current.menu );
queue = [ ...queue, ...getChildMenu( current.menu ) ];

if ( callback( current ) === false ) {
break;
}
}
};

const isMenuEmpty = ( menuToCheck ) => {
let isEmpty = true;

traverseMenu( menuToCheck, ( current ) => {
if ( ! current.isEmpty ) {
isEmpty = false;
return false;
}
} );

return isEmpty;
};

return {
items,
getItem,
Expand All @@ -26,7 +75,24 @@ export const useCreateNavigationTree = () => {

menus,
getMenu,
addMenu,
addMenu: ( key, value ) => {
setChildMenu( ( state ) => {
const newState = { ...state };

if ( ! newState[ value.parentMenu ] ) {
newState[ value.parentMenu ] = [];
}

newState[ value.parentMenu ].push( key );

return newState;
} );

addMenu( key, value );
},
removeMenu,
childMenu,
traverseMenu,
isMenuEmpty,
};
};

0 comments on commit fc67e52

Please sign in to comment.