-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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: Composition Proposal #25057
Changes from 28 commits
53382ec
c08901d
63736b4
dbd96d6
81e8a5b
16141e0
e479ab9
c77b558
80273e2
5897bfa
e652c32
9cce14d
5991899
1365d0d
d5031eb
b60bc5c
6a34450
016dd82
c1e351c
21887bc
1a88aea
e78beb4
1bc526b
1b28149
3f5f943
140893d
3352d0c
0818991
e2d4bca
bf45b48
4f48fb4
49c9059
66370a7
ca124af
f9c1c10
8906756
6e2aa46
0985ad9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
# Navigation | ||
|
||
Render a navigation list with optional groupings and hierarchy. | ||
|
||
## Usage | ||
|
||
```jsx | ||
import { | ||
__experimentalNavigation as Navigation, | ||
__experimentalNavigationGroup as NavigationGroup, | ||
__experimentalNavigationItem as NavigationItem, | ||
__experimentalNavigationLevel as NavigationLevel, | ||
Copons marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} from '@wordpress/components'; | ||
|
||
const MyNavigation = () => { | ||
const [ activeItem, setActiveItem ] = useState( 'item-1' ); | ||
const [ activeLevel, setActiveLevel ] = useState( 'root' ); | ||
|
||
return ( | ||
<Navigation | ||
activeItem={ activeItem } | ||
activeLevel={ activeLevel } | ||
setActiveItem={ setActiveItem } | ||
setActiveLevel={ setActiveLevel } | ||
> | ||
<NavigationLevel title="Home"> | ||
<NavigationGroup title="Group 1"> | ||
<NavigationItem item="item-1" title="Item 1" /> | ||
<NavigationItem item="item-2" title="Item 2" /> | ||
</NavigationGroup> | ||
<NavigationGroup title="Group 2"> | ||
<NavigationItem | ||
item="item-3" | ||
navigateToLevel="category" | ||
title="Category" | ||
/> | ||
</NavigationGroup> | ||
</NavigationLevel> | ||
|
||
<NavigationLevel | ||
level="category" | ||
parentLevel="root" | ||
parentLevelTitle="Home" | ||
title="Category" | ||
> | ||
<ul> | ||
<NavigationItem | ||
badge="1" | ||
item="child-1" | ||
title="Child 1" | ||
/> | ||
<NavigationItem item="child-2" title="Child 2" /> | ||
</ul> | ||
</NavigationLevel> | ||
</Navigation> | ||
); | ||
}; | ||
``` | ||
|
||
## Navigation Props | ||
|
||
`Navigation` supports the following props. | ||
|
||
### `activeItem` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The active item slug. | ||
|
||
### `activeLevel` | ||
Copons marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- Type: `string` | ||
- Required: No | ||
- Default: "root" | ||
|
||
The active level slug. | ||
|
||
### `setActiveItem` | ||
|
||
- Type: `function` | ||
- Required: No | ||
|
||
Sync the active item between the external state and the Navigation's internal state. | ||
|
||
### `setActiveLevel` | ||
|
||
- Type: `function` | ||
- Required: No | ||
|
||
Sync the active level between the external state and the Navigation's internal state. | ||
|
||
## Navigation Level | ||
|
||
`NavigationLevel` supports the following props. | ||
|
||
### `level` | ||
|
||
- Type: `string` | ||
- Required: No | ||
- Default: "root" | ||
|
||
The level slug. | ||
|
||
### `parentLevel` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The parent level slug; used by nested levels to indicate their parent level. | ||
|
||
### `parentLevelTitle` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally it would be sufficient to set the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, the Inferring it from the |
||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The parent level title; used as back button label by nested levels. | ||
|
||
### `title` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The level title. | ||
|
||
## Navigation Group | ||
|
||
`NavigationGroup` supports the following props. | ||
|
||
### `title` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The group title. | ||
|
||
## Navigation Item | ||
|
||
`NavigationItem` supports the following props. | ||
|
||
### `badge` | ||
|
||
- Type: `string|Number` | ||
- Required: No | ||
|
||
The item badge content. | ||
|
||
### `href` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
If provided, renders `a` instead of `button`. | ||
|
||
### `navigateToLevel` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The child level slug. If provided, clicking on the item will navigate to the target level. | ||
|
||
### `onClick` | ||
|
||
- Type: `function` | ||
- Required: No | ||
|
||
A callback to handle clicking on a menu item. | ||
|
||
### `title` | ||
|
||
- Type: `string` | ||
- Required: No | ||
|
||
The item title. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { noop } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createContext, useContext } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { DEFAULT_LEVEL } from './utils'; | ||
|
||
export const NavigationContext = createContext( { | ||
activeItem: undefined, | ||
activeLevel: DEFAULT_LEVEL, | ||
setActiveItem: noop, | ||
setActiveLevel: noop, | ||
} ); | ||
export const useNavigationContext = () => useContext( NavigationContext ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { MenuGroupTitleUI } from './styles/navigation-styles'; | ||
|
||
export default function NavigationGroup( { children, title } ) { | ||
return ( | ||
<div className="components-navigation__menu-group"> | ||
Copons marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ title && ( | ||
<MenuGroupTitleUI | ||
as="h3" | ||
className="components-navigation__menu-group-title" | ||
variant="caption" | ||
> | ||
{ title } | ||
</MenuGroupTitleUI> | ||
) } | ||
<ul>{ children }</ul> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
import { noop } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useEffect, useRef, useState } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import Animate from '../animate'; | ||
import { NavigationContext } from './context'; | ||
import { DEFAULT_LEVEL } from './utils'; | ||
import { Root } from './styles/navigation-styles'; | ||
|
||
export default function Navigation( { | ||
children, | ||
activeItem, | ||
activeLevel = DEFAULT_LEVEL, | ||
setActiveItem = noop, | ||
setActiveLevel = noop, | ||
Copons marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} ) { | ||
const [ item, setItem ] = useState( activeItem ); | ||
const [ level, setLevel ] = useState( activeLevel ); | ||
const [ slideOrigin, setSlideOrigin ] = useState(); | ||
|
||
const activateItem = ( itemId ) => { | ||
setItem( itemId ); | ||
setActiveItem( itemId ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably shouldn't fire if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes total sense, but I'd delay to a follow up. It would require the same children-traversing logic that we'd need to automatize the back button: doable but possibly long. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, agreed that we can iterate on this later. |
||
}; | ||
|
||
const activateLevel = ( levelId, slideInOrigin = 'left' ) => { | ||
setSlideOrigin( slideInOrigin ); | ||
setLevel( levelId ); | ||
setActiveLevel( levelId ); | ||
}; | ||
|
||
const isMounted = useRef( false ); | ||
Copons marked this conversation as resolved.
Show resolved
Hide resolved
|
||
useEffect( () => { | ||
if ( ! isMounted.current ) { | ||
isMounted.current = true; | ||
} | ||
}, [] ); | ||
|
||
useEffect( () => { | ||
if ( activeItem !== item ) { | ||
activateItem( activeItem ); | ||
} | ||
if ( activeLevel !== level ) { | ||
activateLevel( activeLevel ); | ||
} | ||
}, [ activeItem, activeLevel ] ); | ||
|
||
const context = { | ||
activeItem: item, | ||
activeLevel: level, | ||
setActiveItem: activateItem, | ||
setActiveLevel: activateLevel, | ||
}; | ||
|
||
return ( | ||
<Root className="components-navigation"> | ||
<Animate | ||
key={ level } | ||
type="slide-in" | ||
options={ { origin: slideOrigin } } | ||
> | ||
{ ( { className: animateClassName } ) => ( | ||
<div | ||
className={ classnames( { | ||
[ animateClassName ]: | ||
isMounted.current && slideOrigin, | ||
} ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A className would be helpful here. Otherwise, another |
||
> | ||
<NavigationContext.Provider value={ context }> | ||
{ children } | ||
</NavigationContext.Provider> | ||
</div> | ||
) } | ||
</Animate> | ||
</Root> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, experimental features shouldn't appear in the CHANGELOG:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this was correctly processed because it's under the
Unreleased
"version" but doesn't have a header like "Breaking Change" or "New Feature". This appears to have been released in10.2.0
but has been stuck here…cc: @gziolo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think there is a bug in the script that processes changelogs. It's primitive so I'm not surprised 😂
In this case you are correct that it should not be listed although as far as I remember React includes experimental APIs as well. It's all subjective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened #27436 that fixes the bug you discovered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking care of this, I'll be more careful next time!