Skip to content

Commit d05d4d0

Browse files
authored
Add warning for missing aria-current
1 parent 6a43b0e commit d05d4d0

File tree

1 file changed

+39
-7
lines changed

1 file changed

+39
-7
lines changed

src/NavList/NavList.tsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, {isValidElement} from 'react'
55
import {ActionList} from '../ActionList'
66
import Box from '../Box'
77
import 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
1739
const 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

Comments
 (0)