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
---

overlayProps passthrough from ActionMenu and DropdownMenu to AnchoredOverlay.
36 changes: 35 additions & 1 deletion src/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,40 @@ import Button, {ButtonProps} from './Button'
import React, {useCallback, useEffect, useRef} from 'react'
import {AnchoredOverlay} from './AnchoredOverlay'
import {useProvidedStateOrCreate} from './hooks/useProvidedStateOrCreate'
import {OverlayProps} from './Overlay'
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?: Partial<OverlayProps>
}

const ActionMenuItem = (props: ItemProps) => <Item role="menuitem" {...props} />
Expand All @@ -25,6 +52,7 @@ const ActionMenuBase = ({
onAction,
open,
setOpen,
overlayProps,
...listProps
}: ActionMenuProps): JSX.Element => {
const pendingActionRef = useRef<() => unknown>()
Expand Down Expand Up @@ -69,7 +97,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
13 changes: 7 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,11 @@ 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

/**
* Props to be spread on the internal `Overlay` component.
*/
overlayProps?: Partial<OverlayProps>
}

/**
Expand All @@ -43,6 +44,7 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
onOpen,
onClose,
height,
overlayProps,
width
}) => {
const anchorRef = useRef<HTMLElement>(null)
Expand Down Expand Up @@ -129,11 +131,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
15 changes: 14 additions & 1 deletion src/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {List, GroupedListProps, ListPropsBase, ItemInput} from '../ActionList/Li
import {DropdownButton, DropdownButtonProps} from './DropdownButton'
import {Item} from '../ActionList/Item'
import {AnchoredOverlay} from '../AnchoredOverlay'
import {OverlayProps} from '../Overlay'

export interface DropdownMenuProps extends Partial<Omit<GroupedListProps, keyof ListPropsBase>>, ListPropsBase {
/**
Expand All @@ -27,6 +28,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?: Partial<OverlayProps>
}

/**
Expand All @@ -40,6 +46,7 @@ export function DropdownMenu({
placeholder,
selectedItem,
onChange,
overlayProps,
...listProps
}: DropdownMenuProps): JSX.Element {
const [open, setOpen] = useState(false)
Expand Down Expand Up @@ -79,7 +86,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 internal Overlay can be useful for controlling event interactions', e)
}}
items={[
{text: 'New file', trailingText: '⌘O', disabled: true, leadingVisual: ProjectIcon},
ActionList.Divider,
Expand Down