Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onMouseDown passthrough in ActionMenu and AnchoredOverlay #1217

Merged
merged 9 commits into from
May 10, 2021
5 changes: 5 additions & 0 deletions .changeset/wet-plums-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/components": patch
---

onMouseDown passthrough from ActionMenu to AnchoredOverlay.
35 changes: 34 additions & 1 deletion src/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,38 @@ import React, {useCallback, useEffect, useRef} from 'react'
import {AnchoredOverlay} from './AnchoredOverlay'
import {useProvidedStateOrCreate} from './hooks/useProvidedStateOrCreate'
export interface ActionMenuProps extends Partial<Omit<GroupedListProps, keyof ListPropsBase>>, ListPropsBase {
/**
* A custom function component used to render the anchor element.
* Will receive the `anchoredContent` prop as `children` prop.
* Uses a `Button` by default.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderAnchor?: (props: any) => JSX.Element

/**
* Content that is passed into the renderAnchor component, which is a button by default.
*/
anchorContent?: React.ReactNode

/**
* A callback that triggers both on clicks and keyboard events. This callback will be overridden by item level `onAction` callbacks.
*/
onAction?: (props: ItemProps, event?: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void

/**
* If defined, will control the open/closed state of the overlay. Must be used in conjuction with `setOpen`.
*/
open?: boolean

/**
* If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
*/
setOpen?: (s: boolean) => void

/**
* Props to be spread on the internal `Overlay` component.
*/
overlayProps?: Record<string, unknown>
}

const ActionMenuItem = (props: ItemProps) => <Item role="menuitem" {...props} />
Expand All @@ -25,6 +51,7 @@ const ActionMenuBase = ({
onAction,
open,
setOpen,
overlayProps,
...listProps
}: ActionMenuProps): JSX.Element => {
const pendingActionRef = useRef<() => unknown>()
Expand Down Expand Up @@ -69,7 +96,13 @@ const ActionMenuBase = ({
}, [open])

return (
<AnchoredOverlay renderAnchor={renderMenuAnchor} open={combinedOpenState} onOpen={onOpen} onClose={onClose}>
<AnchoredOverlay
renderAnchor={renderMenuAnchor}
open={combinedOpenState}
onOpen={onOpen}
onClose={onClose}
overlayProps={overlayProps}
>
<List {...listProps} role="menu" renderItem={renderMenuItem} />
</AnchoredOverlay>
)
Expand Down
18 changes: 12 additions & 6 deletions src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import {useFocusZone} from '../hooks/useFocusZone'
import {useAnchoredPosition, useRenderForcingRef} from '../hooks'
import {uniqueId} from '../utils/uniqueId'

function preventDefault(event: React.UIEvent) {
event.preventDefault()
}

export interface AnchoredOverlayProps extends Pick<OverlayProps, 'height' | 'width'> {
/**
* A custom function component used to render the anchor element.
Expand All @@ -30,6 +26,16 @@ export interface AnchoredOverlayProps extends Pick<OverlayProps, 'height' | 'wid
* A callback which is called whenever the overlay is currently open and a "close gesture" is detected.
*/
onClose?: (gesture: 'click-outside' | 'escape') => unknown

/**
* A callback that occurs when a user presses a mouse button over an element.
*/
onMouseDown?: (event: React.MouseEvent) => unknown
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would match the ActionMenu and use onOverlayMouseDown here since we still technically have a split between anchor and Overlay in this component


/**
* Props to be spread on the internal `Overlay` component.
*/
overlayProps?: Record<string, unknown>
}

/**
Expand All @@ -43,6 +49,7 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
onOpen,
onClose,
height,
overlayProps,
width
}) => {
const anchorRef = useRef<HTMLElement>(null)
Expand Down Expand Up @@ -129,11 +136,10 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
ref={updateOverlayRef}
role="listbox"
visibility={position ? 'visible' : 'hidden'}
onMouseDown={preventDefault}
onClick={preventDefault}
height={height}
width={width}
{...overlayPosition}
{...overlayProps}
>
{children}
</Overlay>
Expand Down
14 changes: 13 additions & 1 deletion src/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export interface DropdownMenuProps extends Partial<Omit<GroupedListProps, keyof
* `selectedItem`, `undefined` will be passed.
*/
onChange?: (item?: ItemInput) => unknown

/**
* Props to be spread on the internal `Overlay` component.
*/
overlayProps?: Record<string, unknown>
}

/**
Expand All @@ -40,6 +45,7 @@ export function DropdownMenu({
placeholder,
selectedItem,
onChange,
overlayProps,
...listProps
}: DropdownMenuProps): JSX.Element {
const [open, setOpen] = useState(false)
Expand Down Expand Up @@ -79,7 +85,13 @@ export function DropdownMenu({
)

return (
<AnchoredOverlay renderAnchor={renderMenuAnchor} open={open} onOpen={onOpen} onClose={onClose}>
<AnchoredOverlay
renderAnchor={renderMenuAnchor}
open={open}
onOpen={onOpen}
onClose={onClose}
overlayProps={overlayProps}
>
<List {...listProps} role="listbox" renderItem={renderMenuItem} />
</AnchoredOverlay>
)
Expand Down
5 changes: 5 additions & 0 deletions src/stories/ActionMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ export function SimpleListStory(): JSX.Element {
<ActionMenu
onAction={onAction}
anchorContent="Menu"
overlayProps={{
onMouseDown: (e: React.MouseEvent) =>
// eslint-disable-next-line no-console
console.log('onMouseDown in the interal Overlay can be useful for controlling event interactions', e)
}}
items={[
{text: 'New file', trailingText: '⌘O', disabled: true, leadingVisual: ProjectIcon},
ActionList.Divider,
Expand Down