1- import  React  from  'react' 
2- import  { TriangleDownIcon }  from  '@primer/octicons-react' 
1+ import  React ,   { useCallback ,   useContext ,   useMemo }  from  'react' 
2+ import  { TriangleDownIcon ,   ChevronRightIcon }  from  '@primer/octicons-react' 
33import  type  { AnchoredOverlayProps }  from  '../AnchoredOverlay' 
44import  { AnchoredOverlay }  from  '../AnchoredOverlay' 
55import  type  { OverlayProps }  from  '../Overlay' 
@@ -13,11 +13,16 @@ import type {MandateProps} from '../utils/types'
1313import  type  { ForwardRefComponent  as  PolymorphicForwardRefComponent }  from  '../utils/polymorphic' 
1414import  { Tooltip }  from  '../TooltipV2/Tooltip' 
1515
16+ export  type  MenuCloseHandler  =  ( 
17+   gesture : 'anchor-click'  |  'click-outside'  |  'escape'  |  'tab'  |  'item-select'  |  'arrow-left' , 
18+ )  =>  void 
19+ 
1620export  type  MenuContextProps  =  Pick < 
1721  AnchoredOverlayProps , 
1822  'anchorRef'  |  'renderAnchor'  |  'open'  |  'onOpen'  |  'anchorId' 
1923>  &  { 
20-   onClose ?: ( gesture : 'anchor-click'  |  'click-outside'  |  'escape'  |  'tab' )  =>  void 
24+   onClose ?: MenuCloseHandler 
25+   isSubmenu ?: boolean 
2126} 
2227const  MenuContext  =  React . createContext < MenuContextProps > ( { renderAnchor : null ,  open : false } ) 
2328
@@ -44,9 +49,23 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
4449  onOpenChange, 
4550  children, 
4651} : ActionMenuProps )  =>  { 
52+   const  parentMenuContext  =  useContext ( MenuContext ) 
53+ 
4754  const  [ combinedOpenState ,  setCombinedOpenState ]  =  useProvidedStateOrCreate ( open ,  onOpenChange ,  false ) 
4855  const  onOpen  =  React . useCallback ( ( )  =>  setCombinedOpenState ( true ) ,  [ setCombinedOpenState ] ) 
49-   const  onClose  =  React . useCallback ( ( )  =>  setCombinedOpenState ( false ) ,  [ setCombinedOpenState ] ) 
56+   const  onClose : MenuCloseHandler  =  React . useCallback ( 
57+     gesture  =>  { 
58+       setCombinedOpenState ( false ) 
59+ 
60+       // Close the parent stack when an item is selected or the user tabs out of the menu entirely 
61+       switch  ( gesture )  { 
62+         case  'tab' :
63+         case  'item-select' :
64+           parentMenuContext . onClose ?.( gesture ) 
65+       } 
66+     } , 
67+     [ setCombinedOpenState ,  parentMenuContext ] , 
68+   ) 
5069
5170  const  menuButtonChild  =  React . Children . toArray ( children ) . find ( 
5271    child  =>  React . isValidElement < ActionMenuButtonProps > ( child )  &&  ( child . type  ===  MenuButton  ||  child . type  ===  Anchor ) , 
@@ -100,15 +119,59 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
100119  } ) 
101120
102121  return  ( 
103-     < MenuContext . Provider  value = { { anchorRef,  renderAnchor,  anchorId,  open : combinedOpenState ,  onOpen,  onClose} } > 
122+     < MenuContext . Provider 
123+       value = { { 
124+         anchorRef, 
125+         renderAnchor, 
126+         anchorId, 
127+         open : combinedOpenState , 
128+         onOpen, 
129+         onClose, 
130+         // will be undefined for the outermost level, then false for the top menu, then true inside that 
131+         isSubmenu : parentMenuContext . isSubmenu  !==  undefined , 
132+       } } 
133+     > 
104134      { contents } 
105135    </ MenuContext . Provider > 
106136  ) 
107137} 
108138
109139export  type  ActionMenuAnchorProps  =  { children : React . ReactElement ;  id ?: string } 
110140const  Anchor  =  React . forwardRef < HTMLElement ,  ActionMenuAnchorProps > ( ( { children,  ...anchorProps } ,  anchorRef )  =>  { 
111-   return  React . cloneElement ( children ,  { ...anchorProps ,  ref : anchorRef } ) 
141+   const  { onOpen,  isSubmenu}  =  React . useContext ( MenuContext ) 
142+ 
143+   const  openSubmenuOnRightArrow : React . KeyboardEventHandler < HTMLElement >  =  useCallback ( 
144+     event  =>  { 
145+       children . props . onKeyDown ?.( event ) 
146+       if  ( isSubmenu  &&  event . key  ===  'ArrowRight'  &&  ! event . defaultPrevented )  onOpen ?.( 'anchor-key-press' ) 
147+     } , 
148+     [ children ,  isSubmenu ,  onOpen ] , 
149+   ) 
150+ 
151+   // Add right chevron icon to submenu anchors rendered using `ActionList.Item` 
152+   const  parentActionListContext  =  useContext ( ActionListContainerContext ) 
153+   const  thisActionListContext  =  useMemo ( 
154+     ( )  => 
155+       isSubmenu 
156+         ? { 
157+             ...parentActionListContext , 
158+             defaultTrailingVisual : < ChevronRightIcon  /> , 
159+             // Default behavior is to close after selecting; we want to open the submenu instead 
160+             afterSelect : ( )  =>  onOpen ?.( 'anchor-click' ) , 
161+           } 
162+         : parentActionListContext , 
163+     [ isSubmenu ,  onOpen ,  parentActionListContext ] , 
164+   ) 
165+ 
166+   return  ( 
167+     < ActionListContainerContext . Provider  value = { thisActionListContext } > 
168+       { React . cloneElement ( children ,  { 
169+         ...anchorProps , 
170+         ref : anchorRef , 
171+         onKeyDown : openSubmenuOnRightArrow , 
172+       } ) } 
173+     </ ActionListContainerContext . Provider > 
174+   ) 
112175} ) 
113176
114177/** this component is syntactical sugar 🍭 */ 
@@ -133,19 +196,24 @@ type MenuOverlayProps = Partial<OverlayProps> &
133196const  Overlay : React . FC < React . PropsWithChildren < MenuOverlayProps > >  =  ( { 
134197  children, 
135198  align =  'start' , 
136-   side  =   'outside-bottom' , 
199+   side, 
137200  'aria-labelledby' : ariaLabelledby , 
138201  ...overlayProps 
139202} )  =>  { 
140203  // we typecast anchorRef as required instead of optional 
141204  // because we know that we're setting it in context in Menu 
142-   const  { anchorRef,  renderAnchor,  anchorId,  open,  onOpen,  onClose}  =  React . useContext ( MenuContext )  as  MandateProps < 
143-     MenuContextProps , 
144-     'anchorRef' 
145-   > 
205+   const  { 
206+     anchorRef, 
207+     renderAnchor, 
208+     anchorId, 
209+     open, 
210+     onOpen, 
211+     onClose, 
212+     isSubmenu =  false , 
213+   }  =  React . useContext ( MenuContext )  as  MandateProps < MenuContextProps ,  'anchorRef' > 
146214
147215  const  containerRef  =  React . useRef < HTMLDivElement > ( null ) 
148-   useMenuKeyboardNavigation ( open ,  onClose ,  containerRef ,  anchorRef ) 
216+   useMenuKeyboardNavigation ( open ,  onClose ,  containerRef ,  anchorRef ,   isSubmenu ) 
149217
150218  return  ( 
151219    < AnchoredOverlay 
@@ -156,7 +224,7 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
156224      onOpen = { onOpen } 
157225      onClose = { onClose } 
158226      align = { align } 
159-       side = { side } 
227+       side = { side   ??   ( isSubmenu  ?  'outside-right'  :  'outside-bottom' ) } 
160228      overlayProps = { overlayProps } 
161229      focusZoneSettings = { { focusOutBehavior : 'wrap' } } 
162230    > 
@@ -167,7 +235,7 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
167235            listRole : 'menu' , 
168236            listLabelledBy : ariaLabelledby  ||  anchorId , 
169237            selectionAttribute : 'aria-checked' ,  // Should this be here? 
170-             afterSelect : onClose , 
238+             afterSelect : ( )   =>   onClose ?. ( 'item-select' ) , 
171239          } } 
172240        > 
173241          { children } 
0 commit comments