@@ -5,6 +5,7 @@ import React, {isValidElement} from 'react'
55import { ActionList } from '../ActionList'
66import Box from '../Box'
77import StyledOcticon from '../StyledOcticon'
8+ import { useProvidedRefOrCreate } from '../hooks'
89
910// ----------------------------------------------------------------------------
1011// NavList
@@ -13,10 +14,34 @@ export type NavListProps = {
1314 children : React . ReactNode
1415} & React . ComponentProps < 'nav' >
1516
17+ const useWarningForMissingAriaCurrent = containerRef => {
18+ React . useEffect (
19+ function checkForAriaCurrentOnMount ( ) {
20+ const ariaCurrentEl = containerRef . current ?. querySelector ( '[aria-current]' )
21+
22+ if ( ! ariaCurrentEl )
23+ throw new Error ( `
24+ aria-current not found on NavList.Item
25+
26+ To create an accessible navigation, it is required to add aria-current to the current anchor element.
27+
28+ See https://primer.style/react/NavList#accessibilty for more info
29+
30+ If you are extending NavList.Item to create a navigation element, make sure you are passing aria-current at the site of usage.
31+ See https://primer.style/react/NavList#extend for more info.
32+ ` )
33+ } ,
34+ [ containerRef ]
35+ )
36+ }
37+
1638// TODO: sx prop
1739const Root = React . forwardRef < HTMLElement , NavListProps > ( ( { children, ...props } , ref ) => {
40+ const ensureRef = useProvidedRefOrCreate ( ref )
41+ useWarningForMissingAriaCurrent ( ensureRef )
42+
1843 return (
19- < nav ref = { ref } { ...props } >
44+ < nav ref = { ensureRef } { ...props } >
2045 < ActionList > { children } </ ActionList >
2146 </ nav >
2247 )
@@ -52,13 +77,20 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
5277 const currentItem = React . Children . toArray ( subNav . props . children ) . find ( child => {
5378 if ( ! isValidElement ( child ) ) return false
5479
55- // when direct child is SubNav.Item
80+ // if direct child is SubNav.Item
5681 if ( child . type === Item ) return child . props [ 'aria-current' ]
57-
58- // when direct child isn't SubNav.Item,
59- // it's probably a NextJSLikeLink, go one level deeper
60- const wrappedItem = child . props . children
61- if ( typeof wrappedItem === 'object' ) return wrappedItem . props [ 'aria-current' ]
82+ //
83+ // if direct child isn't SubNav.Item
84+ // it's either a custom NavItem or a NextJSLikeLink
85+
86+ // custom NavItem requires aria-current on the direct child
87+ if ( child . props [ 'aria-current' ] ) return true
88+
89+ // for NextJSLikeLink, go one level deeper
90+ if ( typeof child . props . children === 'object' ) {
91+ const wrappedItem = child . props . children
92+ return wrappedItem . props [ 'aria-current' ]
93+ }
6294
6395 // we don't recognise this API usage
6496 return false
0 commit comments