1- import React , { useRef , forwardRef , useCallback , useState , MutableRefObject , RefObject } from 'react'
1+ import React , { useRef , forwardRef , useCallback , useState , MutableRefObject , RefObject , useEffect } from 'react'
22import Box from '../Box'
33import sx , { merge , BetterSystemStyleObject , SxProp } from '../sx'
44import { UnderlineNavContext } from './UnderlineNavContext'
@@ -12,6 +12,11 @@ import styled from 'styled-components'
1212import { LoadingCounter } from './LoadingCounter'
1313import { Button } from '../Button'
1414import Link from '../Link'
15+ import { useFocusZone } from '../hooks/useFocusZone'
16+ import { FocusKeys , getAnchoredPosition } from '@primer/behaviors'
17+ import { ChevronDownIcon , TriangleDownIcon } from '@primer/octicons-react'
18+ import { useOnEscapePress } from '../hooks/useOnEscapePress'
19+ import { useOnOutsideClick } from '../hooks/useOnOutsideClick'
1520
1621export type UnderlineNavProps = {
1722 'aria-label' ?: React . AriaAttributes [ 'aria-label' ]
@@ -35,10 +40,6 @@ const NavigationList = styled.ul`
3540 ${ sx } ;
3641`
3742
38- const DisclosureBox = styled . ul `
39- ${ sx } ;
40- `
41-
4243const MoreMenuListItem = styled . li `
4344 display: flex;
4445`
@@ -135,8 +136,8 @@ export const UnderlineNav = forwardRef(
135136 const navRef = ( forwardedRef ?? backupRef ) as MutableRefObject < HTMLElement >
136137 const listRef = useRef < HTMLUListElement > ( null )
137138 const moreMenuRef = useRef < HTMLLIElement > ( null )
138- const containerRef = useRef < HTMLUListElement > ( null )
139139 const moreMenuBtnRef = useRef < HTMLButtonElement > ( null )
140+ const containerRef = React . useRef < HTMLUListElement > ( null )
140141
141142 const { theme} = useTheme ( )
142143
@@ -247,17 +248,37 @@ export const UnderlineNav = forwardRef(
247248 // eslint-disable-next-line no-console
248249 console . warn ( 'Use the `aria-label` prop to provide an accessible label for assistive technology' )
249250 }
250-
251251 const [ isWidgetOpen , setIsWidgetOpen ] = useState ( false )
252252
253- const onAnchorKeyDown = useCallback (
254- ( event : React . KeyboardEvent < HTMLButtonElement > ) => {
255- if ( ! event . defaultPrevented ) {
256- if ( ! isWidgetOpen && [ 'ArrowDown' , 'ArrowUp' , ' ' , 'Enter' ] . includes ( event . key ) ) {
257- setIsWidgetOpen ( true )
258- event . preventDefault ( )
259- }
253+ const closeOverlay = React . useCallback ( ( ) => {
254+ setIsWidgetOpen ( false )
255+ } , [ setIsWidgetOpen ] )
256+
257+ const toggleOverlay = React . useCallback ( ( ) => {
258+ setIsWidgetOpen ( ! isWidgetOpen )
259+ } , [ setIsWidgetOpen , isWidgetOpen ] )
260+
261+ useOnOutsideClick ( { onClickOutside : closeOverlay , containerRef, ignoreClickRefs : [ moreMenuBtnRef ] } )
262+
263+ useFocusZone ( {
264+ containerRef : backupRef ,
265+ bindKeys : FocusKeys . ArrowVertical | FocusKeys . ArrowHorizontal | FocusKeys . HomeAndEnd | FocusKeys . Tab
266+ } )
267+
268+ useFocusZone ( {
269+ containerRef,
270+ bindKeys : FocusKeys . ArrowVertical | FocusKeys . ArrowHorizontal | FocusKeys . HomeAndEnd ,
271+ focusInStrategy : 'first'
272+ } )
273+
274+ useOnEscapePress (
275+ ( event : KeyboardEvent ) => {
276+ if ( isWidgetOpen ) {
277+ event . preventDefault ( )
278+ closeOverlay ( )
260279 }
280+ // onClose('escape')
281+ // event.preventDefault()
261282 } ,
262283 [ isWidgetOpen ]
263284 )
@@ -266,21 +287,11 @@ export const UnderlineNav = forwardRef(
266287 if ( event . defaultPrevented || event . button !== 0 ) {
267288 return
268289 }
269- setIsWidgetOpen ( ! isWidgetOpen )
290+ toggleOverlay ( )
270291 } ,
271- [ isWidgetOpen ]
292+ [ toggleOverlay ]
272293 )
273294
274- const handleMenuItemKeyDown = useCallback ( ( event : React . KeyboardEvent < HTMLAnchorElement > ) => {
275- if ( ! event . defaultPrevented ) {
276- if ( event . key === 'ArrowDown' ) {
277- // manage focus on children
278-
279- event . preventDefault ( )
280- }
281- }
282- } , [ ] )
283-
284295 return (
285296 < UnderlineNavContext . Provider
286297 value = { {
@@ -315,64 +326,60 @@ export const UnderlineNav = forwardRef(
315326 sx = { moreBtnStyles }
316327 aria-controls = "disclosure-widget"
317328 aria-expanded = { isWidgetOpen }
318- onKeyDown = { onAnchorKeyDown }
319329 onClick = { onAnchorClick }
330+ trailingIcon = { TriangleDownIcon }
320331 >
321332 More
322333 </ Button >
323334
324- { isWidgetOpen && (
325- < DisclosureBox
326- ref = { containerRef }
327- // onKeyDown={keyPressHandler}
328- id = "disclosure-widget"
329- sx = { {
330- position : 'absolute' ,
331- top : '100%' ,
332- boxShadow : '0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)' ,
333- borderRadius : '12px' ,
334- right : '100px' ,
335- backgroundColor : `${ theme ?. colors . canvas . overlay } ` ,
336- listStyle : 'none' ,
337- padding : '8px' ,
338- minWidth : '192px' ,
339- maxWidth : '640px'
340- } }
341- >
342- { actions . map ( ( action , index ) => {
343- const { children : actionElementChildren , ...actionElementProps } = action . props
344- return (
345- < Box key = { index } as = "li" >
346- < a
347- href = "#"
348- // sx={menuItemStyles}
349- // as={asNavItem}
350- onClick = { ( event : React . MouseEvent < HTMLAnchorElement > ) => {
351- swapMenuItemWithListItem ( action , index , event , updateListAndMenu )
352- setSelectEvent ( event )
353- setIsWidgetOpen ( false )
354- moreMenuBtnRef . current ?. focus ( )
355- } }
356- onKeyDown = { handleMenuItemKeyDown }
357- >
358- < Box
359- as = "span"
360- sx = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } }
361- >
362- { actionElementChildren }
363-
364- { loadingCounters ? (
365- < LoadingCounter />
366- ) : (
367- < CounterLabel > { actionElementProps . counter } </ CounterLabel >
368- ) }
369- </ Box >
370- </ a >
371- </ Box >
372- )
373- } ) }
374- </ DisclosureBox >
375- ) }
335+ < Box
336+ as = "ul"
337+ ref = { containerRef }
338+ id = "disclosure-widget"
339+ sx = { {
340+ position : 'absolute' ,
341+ top : '90%' ,
342+ boxShadow : '0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)' ,
343+ borderRadius : '12px' ,
344+ right : '0' ,
345+ backgroundColor : `${ theme ?. colors . canvas . overlay } ` ,
346+ listStyle : 'none' ,
347+ padding : '8px' ,
348+ minWidth : '192px' ,
349+ maxWidth : '640px' ,
350+ display : isWidgetOpen ? 'block' : 'none'
351+ } }
352+ // onEscape={() => setIsWidgetOpen(false)}
353+ >
354+ { actions . map ( ( action , index ) => {
355+ const { children : actionElementChildren , ...actionElementProps } = action . props
356+ return (
357+ < Box key = { index } as = "li" >
358+ < a
359+ href = "#hello"
360+ // sx={menuItemStyles}
361+ // as={asNavItem}
362+ onClick = { ( event : React . MouseEvent < HTMLAnchorElement > ) => {
363+ swapMenuItemWithListItem ( action , index , event , updateListAndMenu )
364+ setSelectEvent ( event )
365+ closeOverlay ( )
366+ moreMenuBtnRef . current ?. focus ( )
367+ } }
368+ >
369+ < Box as = "span" sx = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } } >
370+ { actionElementChildren }
371+
372+ { loadingCounters ? (
373+ < LoadingCounter />
374+ ) : (
375+ < CounterLabel > { actionElementProps . counter } </ CounterLabel >
376+ ) }
377+ </ Box >
378+ </ a >
379+ </ Box >
380+ )
381+ } ) }
382+ </ Box >
376383 </ MoreMenuListItem >
377384 ) }
378385 </ NavigationList >
0 commit comments