11import { ChevronDownIcon } from '@primer/octicons-react'
2+ import { ForwardRefComponent as PolymorphicForwardRefComponent } from '@radix-ui/react-polymorphic'
23import { useSSRSafeId } from '@react-aria/ssr'
34import React , { isValidElement } from 'react'
45import styled from 'styled-components'
@@ -36,9 +37,8 @@ export type NavListItemProps = {
3637 'aria-current' ?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean
3738} & SxProp
3839
39- // TODO: as prop
4040const Item = React . forwardRef < HTMLAnchorElement , NavListItemProps > (
41- ( { href , 'aria-current' : ariaCurrent , children, sx : sxProp = { } } , ref ) => {
41+ ( { 'aria-current' : ariaCurrent , children, sx : sxProp = { } , ... props } , ref ) => {
4242 const { depth} = React . useContext ( SubNavContext )
4343
4444 // Get SubNav from children
@@ -51,13 +51,8 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
5151
5252 // Render ItemWithSubNav if SubNav is present
5353 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-
5954 return (
60- < ItemWithSubNav subNav = { subNav } subNavContainsCurrentItem = { Boolean ( currentItem ) } sx = { sxProp } >
55+ < ItemWithSubNav subNav = { subNav } sx = { sxProp } >
6156 { childrenWithoutSubNav }
6257 </ ItemWithSubNav >
6358 )
@@ -66,7 +61,6 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
6661 return (
6762 < ActionList . LinkItem
6863 ref = { ref }
69- href = { href }
7064 aria-current = { ariaCurrent }
7165 active = { Boolean ( ariaCurrent ) && ariaCurrent !== 'false' }
7266 sx = { merge < SxProp [ 'sx' ] > (
@@ -77,12 +71,13 @@ const Item = React.forwardRef<HTMLAnchorElement, NavListItemProps>(
7771 } ,
7872 sxProp
7973 ) }
74+ { ...props }
8075 >
8176 { children }
8277 </ ActionList . LinkItem >
8378 )
8479 }
85- )
80+ ) as PolymorphicForwardRefComponent < 'a' , NavListItemProps >
8681
8782Item . displayName = 'NavList.Item'
8883
@@ -92,36 +87,48 @@ Item.displayName = 'NavList.Item'
9287type ItemWithSubNavProps = {
9388 children : React . ReactNode
9489 subNav : React . ReactNode
95- subNavContainsCurrentItem : boolean
9690} & SxProp
9791
98- const ItemWithSubNavContext = React . createContext < { buttonId : string ; subNavId : string } > ( {
92+ const ItemWithSubNavContext = React . createContext < { buttonId : string ; subNavId : string ; isOpen : boolean } > ( {
9993 buttonId : '' ,
100- subNavId : ''
94+ subNavId : '' ,
95+ isOpen : false
10196} )
10297
10398// TODO: ref prop
10499// TODO: Animate open/close transition
105- function ItemWithSubNav ( { children, subNav, subNavContainsCurrentItem , sx : sxProp = { } } : ItemWithSubNavProps ) {
100+ function ItemWithSubNav ( { children, subNav, sx : sxProp = { } } : ItemWithSubNavProps ) {
106101 const buttonId = useSSRSafeId ( )
107102 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 ] )
110117
111118 return (
112- < ItemWithSubNavContext . Provider value = { { buttonId, subNavId} } >
119+ < ItemWithSubNavContext . Provider value = { { buttonId, subNavId, isOpen } } >
113120 < Box as = "li" aria-labelledby = { buttonId } sx = { { listStyle : 'none' } } >
114121 < ActionList . Item
115122 as = "button"
116123 id = { buttonId }
117124 aria-expanded = { isOpen }
118125 aria-controls = { subNavId }
119126 // When the subNav is closed, how should we indicated that the subNav contains the current item?
120- active = { ! isOpen && subNavContainsCurrentItem }
127+ active = { ! isOpen && containsCurrentItem }
121128 onClick = { ( ) => setIsOpen ( open => ! open ) }
122129 sx = { merge < SxProp [ 'sx' ] > (
123130 {
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
125132 } ,
126133 sxProp
127134 ) }
@@ -138,7 +145,7 @@ function ItemWithSubNav({children, subNav, subNavContainsCurrentItem, sx: sxProp
138145 </ ActionList . TrailingVisual >
139146 </ ActionList . Item >
140147
141- { isOpen ? subNav : null }
148+ < div ref = { subNavRef } > { subNav } </ div >
142149 </ Box >
143150 </ ItemWithSubNavContext . Provider >
144151 )
@@ -156,7 +163,7 @@ const SubNavContext = React.createContext<{depth: number}>({depth: 0})
156163// TODO: ref prop
157164// NOTE: SubNav must be a direct child of an Item
158165const SubNav = ( { children, sx : sxProp = { } } : NavListSubNavProps ) => {
159- const { buttonId, subNavId} = React . useContext ( ItemWithSubNavContext )
166+ const { buttonId, subNavId, isOpen } = React . useContext ( ItemWithSubNavContext )
160167 const { depth} = React . useContext ( SubNavContext )
161168
162169 if ( ! buttonId || ! subNavId ) {
@@ -179,7 +186,8 @@ const SubNav = ({children, sx: sxProp = {}}: NavListSubNavProps) => {
179186 sx = { merge < SxProp [ 'sx' ] > (
180187 {
181188 padding : 0 ,
182- margin : 0
189+ margin : 0 ,
190+ display : isOpen ? 'block' : 'none'
183191 } ,
184192 sxProp
185193 ) }
0 commit comments