1
1
import { ChevronDownIcon } from '@primer/octicons-react'
2
+ import { ForwardRefComponent as PolymorphicForwardRefComponent } from '@radix-ui/react-polymorphic'
2
3
import { useSSRSafeId } from '@react-aria/ssr'
3
4
import React , { isValidElement } from 'react'
4
5
import styled from 'styled-components'
@@ -36,9 +37,8 @@ export type NavListItemProps = {
36
37
'aria-current' ?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean
37
38
} & SxProp
38
39
39
- // TODO: as prop
40
40
const Item = React . forwardRef < HTMLAnchorElement , NavListItemProps > (
41
- ( { href , 'aria-current' : ariaCurrent , children, sx : sxProp = { } } , ref ) => {
41
+ ( { 'aria-current' : ariaCurrent , children, sx : sxProp = { } , ... props } , ref ) => {
42
42
const { depth} = React . useContext ( SubNavContext )
43
43
44
44
// Get SubNav from children
@@ -51,13 +51,8 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
51
51
52
52
// Render ItemWithSubNav if SubNav is present
53
53
if ( subNav && isValidElement ( subNav ) && depth < 1 ) {
54
- // Search SubNav children for current Item
55
- const currentItem = React . Children . toArray ( subNav . props . children ) . find (
56
- child => isValidElement ( child ) && child . props [ 'aria-current' ]
57
- )
58
-
59
54
return (
60
- < ItemWithSubNav subNav = { subNav } subNavContainsCurrentItem = { Boolean ( currentItem ) } sx = { sxProp } >
55
+ < ItemWithSubNav subNav = { subNav } sx = { sxProp } >
61
56
{ childrenWithoutSubNav }
62
57
</ ItemWithSubNav >
63
58
)
@@ -66,7 +61,6 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
66
61
return (
67
62
< ActionList . LinkItem
68
63
ref = { ref }
69
- href = { href }
70
64
aria-current = { ariaCurrent }
71
65
active = { Boolean ( ariaCurrent ) && ariaCurrent !== 'false' }
72
66
sx = { merge < SxProp [ 'sx' ] > (
@@ -77,12 +71,13 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
77
71
} ,
78
72
sxProp
79
73
) }
74
+ { ...props }
80
75
>
81
76
{ children }
82
77
</ ActionList . LinkItem >
83
78
)
84
79
}
85
- )
80
+ ) as PolymorphicForwardRefComponent < 'a' , NavListItemProps >
86
81
87
82
Item . displayName = 'NavList.Item'
88
83
@@ -92,36 +87,48 @@ Item.displayName = 'NavList.Item'
92
87
type ItemWithSubNavProps = {
93
88
children : React . ReactNode
94
89
subNav : React . ReactNode
95
- subNavContainsCurrentItem : boolean
96
90
} & SxProp
97
91
98
- const ItemWithSubNavContext = React . createContext < { buttonId : string ; subNavId : string } > ( {
92
+ const ItemWithSubNavContext = React . createContext < { buttonId : string ; subNavId : string ; isOpen : boolean } > ( {
99
93
buttonId : '' ,
100
- subNavId : ''
94
+ subNavId : '' ,
95
+ isOpen : false
101
96
} )
102
97
103
98
// TODO: ref prop
104
99
// TODO: Animate open/close transition
105
- function ItemWithSubNav ( { children, subNav, subNavContainsCurrentItem , sx : sxProp = { } } : ItemWithSubNavProps ) {
100
+ function ItemWithSubNav ( { children, subNav, sx : sxProp = { } } : ItemWithSubNavProps ) {
106
101
const buttonId = useSSRSafeId ( )
107
102
const subNavId = useSSRSafeId ( )
108
- // SubNav starts open if current item is in it
109
- const [ isOpen , setIsOpen ] = React . useState ( subNavContainsCurrentItem )
103
+ const [ isOpen , setIsOpen ] = React . useState ( false )
104
+ const subNavRef = React . useRef < HTMLDivElement > ( null )
105
+ const [ containsCurrentItem , setContainsCurrentItem ] = React . useState ( false )
106
+
107
+ React . useLayoutEffect ( ( ) => {
108
+ if ( subNavRef . current ) {
109
+ // Check if SubNav contains current item
110
+ const currentItem = subNavRef . current . querySelector ( '[aria-current]' )
111
+ if ( currentItem && currentItem . getAttribute ( 'aria-current' ) !== 'false' ) {
112
+ setContainsCurrentItem ( true )
113
+ setIsOpen ( true )
114
+ }
115
+ }
116
+ } , [ subNav ] )
110
117
111
118
return (
112
- < ItemWithSubNavContext . Provider value = { { buttonId, subNavId} } >
119
+ < ItemWithSubNavContext . Provider value = { { buttonId, subNavId, isOpen } } >
113
120
< Box as = "li" aria-labelledby = { buttonId } sx = { { listStyle : 'none' } } >
114
121
< ActionList . Item
115
122
as = "button"
116
123
id = { buttonId }
117
124
aria-expanded = { isOpen }
118
125
aria-controls = { subNavId }
119
126
// When the subNav is closed, how should we indicated that the subNav contains the current item?
120
- active = { ! isOpen && subNavContainsCurrentItem }
127
+ active = { ! isOpen && containsCurrentItem }
121
128
onClick = { ( ) => setIsOpen ( open => ! open ) }
122
129
sx = { merge < SxProp [ 'sx' ] > (
123
130
{
124
- fontWeight : subNavContainsCurrentItem ? 'bold' : null // Parent item is bold if any of it's sub-items are current
131
+ fontWeight : containsCurrentItem ? 'bold' : null // Parent item is bold if any of it's sub-items are current
125
132
} ,
126
133
sxProp
127
134
) }
@@ -138,7 +145,7 @@ function ItemWithSubNav({children, subNav, subNavContainsCurrentItem, sx: sxProp
138
145
</ ActionList . TrailingVisual >
139
146
</ ActionList . Item >
140
147
141
- { isOpen ? subNav : null }
148
+ < div ref = { subNavRef } > { subNav } </ div >
142
149
</ Box >
143
150
</ ItemWithSubNavContext . Provider >
144
151
)
@@ -156,7 +163,7 @@ const SubNavContext = React.createContext<{depth: number}>({depth: 0})
156
163
// TODO: ref prop
157
164
// NOTE: SubNav must be a direct child of an Item
158
165
const SubNav = ( { children, sx : sxProp = { } } : NavListSubNavProps ) => {
159
- const { buttonId, subNavId} = React . useContext ( ItemWithSubNavContext )
166
+ const { buttonId, subNavId, isOpen } = React . useContext ( ItemWithSubNavContext )
160
167
const { depth} = React . useContext ( SubNavContext )
161
168
162
169
if ( ! buttonId || ! subNavId ) {
@@ -179,7 +186,8 @@ const SubNav = ({children, sx: sxProp = {}}: NavListSubNavProps) => {
179
186
sx = { merge < SxProp [ 'sx' ] > (
180
187
{
181
188
padding : 0 ,
182
- margin : 0
189
+ margin : 0 ,
190
+ display : isOpen ? 'block' : 'none'
183
191
} ,
184
192
sxProp
185
193
) }
0 commit comments