Skip to content

Commit

Permalink
fix: When clicking an 'AnchoredOverlay' trigger, don’t close—then imm…
Browse files Browse the repository at this point in the history
…ediately reopen—the 'AnchoredOverlay' (#1312)

* feat: When clicking an 'AnchoredOverlay' trigger, don’t close then immediately reopen the 'AnchoredOverlay'

* Create smart-hats-kiss.md

* fix: Distinguish anchor-targeted close requests from outside-click-originating requests.

* docs: Add a story demonstrating a double-clickable anchor

* fix: Use 'AnchoredOverlay'’s type, rather than one prone to falling out-of-sync
  • Loading branch information
smockle committed Jun 18, 2021
1 parent 6f0535d commit 76a3843
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/smart-hats-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/components": patch
---

Ensure clicking an `AnchoredOverlay`’s trigger allows it to close without immediately reopening.
12 changes: 9 additions & 3 deletions src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface AnchoredOverlayBaseProps extends Pick<OverlayProps, 'height' | 'width'
/**
* A callback which is called whenever the overlay is currently open and a "close gesture" is detected.
*/
onClose?: (gesture: 'click-outside' | 'escape') => unknown
onClose?: (gesture: 'anchor-click' | 'click-outside' | 'escape') => unknown

/**
* Props to be spread on the internal `Overlay` component.
Expand Down Expand Up @@ -104,11 +104,16 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
)
const onAnchorClick = useCallback(
(event: React.MouseEvent<HTMLElement>) => {
if (!event.defaultPrevented && event.button === 0 && !open) {
if (event.defaultPrevented || event.button !== 0) {
return
}
if (!open) {
onOpen?.('anchor-click')
} else {
onClose?.('anchor-click')
}
},
[open, onOpen]
[open, onOpen, onClose]
)

const {position} = useAnchoredPosition(
Expand Down Expand Up @@ -145,6 +150,7 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
<Overlay
returnFocusRef={anchorRef}
onClickOutside={onClickOutside}
ignoreClickRefs={[anchorRef]}
onEscape={onEscape}
ref={updateOverlayRef}
role="listbox"
Expand Down
2 changes: 1 addition & 1 deletion src/SelectPanel/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function SelectPanel({

const onOpen: AnchoredOverlayProps['onOpen'] = useCallback(gesture => onOpenChange(true, gesture), [onOpenChange])
const onClose = useCallback(
(gesture: 'click-outside' | 'escape' | 'selection') => {
(gesture: Parameters<Exclude<AnchoredOverlayProps['onClose'], undefined>>[0] | 'selection') => {
onOpenChange(false, gesture)
},
[onOpenChange]
Expand Down
50 changes: 49 additions & 1 deletion src/stories/ActionMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import {Meta} from '@storybook/react'
import React, {useCallback, useState, useRef} from 'react'
import styled from 'styled-components'
import {ThemeProvider} from '..'
import {ActionMenu} from '../ActionMenu'
import {ActionMenu, ActionMenuProps} from '../ActionMenu'
import Link, {LinkProps} from '../Link'
import Button from '../Button'
import {ActionList, ItemProps} from '../ActionList'
import BaseStyles from '../BaseStyles'
import {DropdownButton} from '../DropdownMenu'

const meta: Meta = {
title: 'Composite components/ActionMenu',
Expand Down Expand Up @@ -272,3 +273,50 @@ export function ActionMenuWithExternalAnchor(): JSX.Element {
</>
)
}

const DoubleClickableAnchor: Exclude<ActionMenuProps['renderAnchor'], null | undefined> = ({
onClick: callback,
...rest
}) => {
const onClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
if (event.detail === 2) {
event.preventDefault()
}
callback?.(event)
},
[callback]
)
return <DropdownButton onClick={onClick} {...rest} />
}
export function ActionMenuWithDoubleClickStory(): JSX.Element {
return (
<>
<h1>Actions</h1>
<ErsatzOverlay>
<ActionMenu
renderAnchor={DoubleClickableAnchor}
anchorContent={<ServerIcon />}
items={[
{
leadingVisual: ServerIcon,
text: 'Open current Codespace',
description:
"Your existing Codespace will be opened to its previous state, and you'll be asked to manually switch to new-branch.",
descriptionVariant: 'block',
trailingText: '⌘O'
},
{
leadingVisual: PlusCircleIcon,
text: 'Create new Codespace',
description: 'Create a brand new Codespace with a fresh image and checkout this branch.',
descriptionVariant: 'block',
trailingText: '⌘C'
}
]}
/>
</ErsatzOverlay>
</>
)
}
ActionMenuWithDoubleClickStory.storyName = 'ActionMenu with double-clickable anchor'

1 comment on commit 76a3843

@vercel
Copy link

@vercel vercel bot commented on 76a3843 Jun 18, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.