Skip to content

Commit

Permalink
Revert "Add support for nested submenus to ActionMenu (#4386)" (#4472)
Browse files Browse the repository at this point in the history
This reverts commit 4e281b2.
  • Loading branch information
siddharthkp authored Apr 6, 2024
1 parent 84e2662 commit 82072eb
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 347 deletions.
5 changes: 0 additions & 5 deletions .changeset/wild-students-bow.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type ContextProps = {
// eslint-disable-next-line @typescript-eslint/ban-types
afterSelect?: Function
enableFocusZone?: boolean
defaultTrailingVisual?: React.ReactElement
}

export const ActionListContainerContext = React.createContext<ContextProps>({})
14 changes: 3 additions & 11 deletions packages/react/src/ActionList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,14 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
inlineDescription: [Description, props => props.variant !== 'block'],
})

const {container, afterSelect, selectionAttribute, defaultTrailingVisual} =
React.useContext(ActionListContainerContext)

// Be sure to avoid rendering the container unless there is a default
const wrappedDefaultTrailingVisual = defaultTrailingVisual ? (
<TrailingVisual>{defaultTrailingVisual}</TrailingVisual>
) : null
const trailingVisual = slots.trailingVisual ?? wrappedDefaultTrailingVisual

const {
variant: listVariant,
role: listRole,
showDividers,
selectionVariant: listSelectionVariant,
} = React.useContext(ListContext)
const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
const inactive = Boolean(inactiveText)
const showInactiveIndicator = inactive && container === undefined

Expand Down Expand Up @@ -316,7 +308,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
>
<ConditionalWrapper
if={Boolean(trailingVisual) || (showInactiveIndicator && !slots.leadingVisual)}
if={Boolean(slots.trailingVisual) || (showInactiveIndicator && !slots.leadingVisual)}
sx={{display: 'flex', flexGrow: 1}}
>
<ConditionalWrapper
Expand Down Expand Up @@ -346,7 +338,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
) : (
// If it's not inactive, or it has a leading visual that can be replaced,
// just render the trailing visual slot.
trailingVisual
slots.trailingVisual
)
}
</ConditionalWrapper>
Expand Down
53 changes: 1 addition & 52 deletions packages/react/src/ActionMenu/ActionMenu.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import React from 'react'
import {ActionMenu, ActionList, Box} from '../'
import {
WorkflowIcon,
ArchiveIcon,
GearIcon,
CopyIcon,
RocketIcon,
CommentIcon,
BookIcon,
SparkleFillIcon,
} from '@primer/octicons-react'
import {WorkflowIcon, ArchiveIcon, GearIcon, CopyIcon, RocketIcon, CommentIcon, BookIcon} from '@primer/octicons-react'

export default {
title: 'Components/ActionMenu/Features',
Expand Down Expand Up @@ -190,45 +181,3 @@ export const InactiveItems = () => (
</ActionMenu.Overlay>
</ActionMenu>
)

export const Submenus = () => (
<ActionMenu>
<ActionMenu.Button>Edit</ActionMenu.Button>
<ActionMenu.Overlay>
<ActionList>
<ActionList.Item>Cut</ActionList.Item>
<ActionList.Item>Copy</ActionList.Item>
<ActionList.Item>Paste</ActionList.Item>
<ActionMenu>
<ActionMenu.Anchor>
<ActionList.Item>
<ActionList.LeadingVisual>
<SparkleFillIcon />
</ActionList.LeadingVisual>
Paste special
</ActionList.Item>
</ActionMenu.Anchor>
<ActionMenu.Overlay>
<ActionList>
<ActionList.Item>Paste plain text</ActionList.Item>
<ActionList.Item>Paste formulas</ActionList.Item>
<ActionList.Item>Paste with formatting</ActionList.Item>
<ActionMenu>
<ActionMenu.Anchor>
<ActionList.Item>Paste from</ActionList.Item>
</ActionMenu.Anchor>
<ActionMenu.Overlay>
<ActionList>
<ActionList.Item>Current clipboard</ActionList.Item>
<ActionList.Item>History</ActionList.Item>
<ActionList.Item>Another device</ActionList.Item>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
)
96 changes: 14 additions & 82 deletions packages/react/src/ActionMenu/ActionMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useCallback, useContext, useMemo} from 'react'
import {TriangleDownIcon, ChevronRightIcon} from '@primer/octicons-react'
import React from 'react'
import {TriangleDownIcon} from '@primer/octicons-react'
import type {AnchoredOverlayProps} from '../AnchoredOverlay'
import {AnchoredOverlay} from '../AnchoredOverlay'
import type {OverlayProps} from '../Overlay'
Expand All @@ -13,16 +13,11 @@ import type {MandateProps} from '../utils/types'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {Tooltip} from '../TooltipV2/Tooltip'

export type MenuCloseHandler = (
gesture: 'anchor-click' | 'click-outside' | 'escape' | 'tab' | 'item-select' | 'arrow-left',
) => void

export type MenuContextProps = Pick<
AnchoredOverlayProps,
'anchorRef' | 'renderAnchor' | 'open' | 'onOpen' | 'anchorId'
> & {
onClose?: MenuCloseHandler
isSubmenu?: boolean
onClose?: (gesture: 'anchor-click' | 'click-outside' | 'escape' | 'tab') => void
}
const MenuContext = React.createContext<MenuContextProps>({renderAnchor: null, open: false})

Expand All @@ -49,23 +44,9 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
onOpenChange,
children,
}: ActionMenuProps) => {
const parentMenuContext = useContext(MenuContext)

const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, onOpenChange, false)
const onOpen = React.useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
const onClose: MenuCloseHandler = React.useCallback(
gesture => {
setCombinedOpenState(false)

// Close the parent stack when an item is selected or the user tabs out of the menu entirely
switch (gesture) {
case 'tab':
case 'item-select':
parentMenuContext.onClose?.(gesture)
}
},
[setCombinedOpenState, parentMenuContext],
)
const onClose = React.useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])

const menuButtonChild = React.Children.toArray(children).find(
child => React.isValidElement<ActionMenuButtonProps>(child) && (child.type === MenuButton || child.type === Anchor),
Expand Down Expand Up @@ -119,59 +100,15 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
})

return (
<MenuContext.Provider
value={{
anchorRef,
renderAnchor,
anchorId,
open: combinedOpenState,
onOpen,
onClose,
// will be undefined for the outermost level, then false for the top menu, then true inside that
isSubmenu: parentMenuContext.isSubmenu !== undefined,
}}
>
<MenuContext.Provider value={{anchorRef, renderAnchor, anchorId, open: combinedOpenState, onOpen, onClose}}>
{contents}
</MenuContext.Provider>
)
}

export type ActionMenuAnchorProps = {children: React.ReactElement; id?: string}
const Anchor = React.forwardRef<HTMLElement, ActionMenuAnchorProps>(({children, ...anchorProps}, anchorRef) => {
const {onOpen, isSubmenu} = React.useContext(MenuContext)

const openSubmenuOnRightArrow: React.KeyboardEventHandler<HTMLElement> = useCallback(
event => {
children.props.onKeyDown?.(event)
if (isSubmenu && event.key === 'ArrowRight' && !event.defaultPrevented) onOpen?.('anchor-key-press')
},
[children, isSubmenu, onOpen],
)

// Add right chevron icon to submenu anchors rendered using `ActionList.Item`
const parentActionListContext = useContext(ActionListContainerContext)
const thisActionListContext = useMemo(
() =>
isSubmenu
? {
...parentActionListContext,
defaultTrailingVisual: <ChevronRightIcon />,
// Default behavior is to close after selecting; we want to open the submenu instead
afterSelect: () => onOpen?.('anchor-click'),
}
: parentActionListContext,
[isSubmenu, onOpen, parentActionListContext],
)

return (
<ActionListContainerContext.Provider value={thisActionListContext}>
{React.cloneElement(children, {
...anchorProps,
ref: anchorRef,
onKeyDown: openSubmenuOnRightArrow,
})}
</ActionListContainerContext.Provider>
)
return React.cloneElement(children, {...anchorProps, ref: anchorRef})
})

/** this component is syntactical sugar 🍭 */
Expand All @@ -196,24 +133,19 @@ type MenuOverlayProps = Partial<OverlayProps> &
const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
children,
align = 'start',
side,
side = 'outside-bottom',
'aria-labelledby': ariaLabelledby,
...overlayProps
}) => {
// we typecast anchorRef as required instead of optional
// because we know that we're setting it in context in Menu
const {
anchorRef,
renderAnchor,
anchorId,
open,
onOpen,
onClose,
isSubmenu = false,
} = React.useContext(MenuContext) as MandateProps<MenuContextProps, 'anchorRef'>
const {anchorRef, renderAnchor, anchorId, open, onOpen, onClose} = React.useContext(MenuContext) as MandateProps<
MenuContextProps,
'anchorRef'
>

const containerRef = React.useRef<HTMLDivElement>(null)
useMenuKeyboardNavigation(open, onClose, containerRef, anchorRef, isSubmenu)
useMenuKeyboardNavigation(open, onClose, containerRef, anchorRef)

return (
<AnchoredOverlay
Expand All @@ -224,7 +156,7 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
onOpen={onOpen}
onClose={onClose}
align={align}
side={side ?? (isSubmenu ? 'outside-right' : 'outside-bottom')}
side={side}
overlayProps={overlayProps}
focusZoneSettings={{focusOutBehavior: 'wrap'}}
>
Expand All @@ -235,7 +167,7 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
listRole: 'menu',
listLabelledBy: ariaLabelledby || anchorId,
selectionAttribute: 'aria-checked', // Should this be here?
afterSelect: () => onClose?.('item-select'),
afterSelect: onClose,
}}
>
{children}
Expand Down
Loading

0 comments on commit 82072eb

Please sign in to comment.