Skip to content

Commit

Permalink
Allow MouseEventHandler types to passed close from Popovers (#1696
Browse files Browse the repository at this point in the history
)

* fix: adds the MouseEventHandler type to close

* WIP

* WIP

* Update changelog

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
  • Loading branch information
eddysims and thecrypticace committed Aug 2, 2022
1 parent 4d88dd9 commit db736d8
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 12 deletions.
4 changes: 4 additions & 0 deletions packages/@headlessui-react/CHANGELOG.md
Expand Up @@ -28,6 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make form components uncontrollable ([#1683](https://github.com/tailwindlabs/headlessui/pull/1683))
- Improve `Combobox` re-opening keyboard issue on mobile ([#1732](https://github.com/tailwindlabs/headlessui/pull/1732))

## Changed

- Allow `Popover` `close` to be passed directly to `onClick` handlers ([#1696](https://github.com/tailwindlabs/headlessui/pull/1696))

## [1.6.6] - 2022-07-07

### Fixed
Expand Down
42 changes: 42 additions & 0 deletions packages/@headlessui-react/src/components/popover/popover.test.tsx
Expand Up @@ -260,6 +260,48 @@ describe('Rendering', () => {
})
)

it(
'should expose a close function that closes the popover and takes an event',
suppressConsoleLogs(async () => {
function Example() {
return (
<>
<Popover>
{({ close }) => (
<>
<Popover.Button>Trigger</Popover.Button>
<Popover.Panel>
<button onClick={close}>Close me</button>
</Popover.Panel>
</>
)}
</Popover>
</>
)
}

render(<Example />)

// Focus the button
await focus(getPopoverButton())

// Ensure the button is focused
assertActiveElement(getPopoverButton())

// Open the popover
await click(getPopoverButton())

// Ensure we can click the close button
await click(getByText('Close me'))

// Ensure the popover is closed
assertPopoverPanel({ state: PopoverState.InvisibleUnmounted })

// Ensure the Popover.Button got the restored focus
assertActiveElement(getByText('Trigger'))
})
)

describe('refs', () => {
it(
'should be possible to get a ref to the Popover',
Expand Down
39 changes: 27 additions & 12 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Expand Up @@ -17,6 +17,7 @@ import React, {
MouseEvent as ReactMouseEvent,
MutableRefObject,
Ref,
MouseEventHandler,
} from 'react'

import { Props } from '../../types'
Expand Down Expand Up @@ -44,6 +45,8 @@ import { useEvent } from '../../hooks/use-event'
import { useTabDirection, Direction as TabDirection } from '../../hooks/use-tab-direction'
import { microTask } from '../../utils/micro-task'

type MouseEvent<T> = Parameters<MouseEventHandler<T>>[0]

enum PopoverStates {
Open,
Closed,
Expand Down Expand Up @@ -128,7 +131,9 @@ function usePopoverContext(component: string) {
}

let PopoverAPIContext = createContext<{
close(focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>): void
close(
focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null> | MouseEvent<HTMLElement>
): void
isPortalled: boolean
} | null>(null)
PopoverAPIContext.displayName = 'PopoverAPIContext'
Expand Down Expand Up @@ -176,7 +181,9 @@ function stateReducer(state: StateDefinition, action: Actions) {
let DEFAULT_POPOVER_TAG = 'div' as const
interface PopoverRenderPropArg {
open: boolean
close(focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>): void
close(
focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null> | MouseEvent<HTMLElement>
): void
}

let PopoverRoot = forwardRefWithAs(function Popover<
Expand Down Expand Up @@ -271,19 +278,27 @@ let PopoverRoot = forwardRefWithAs(function Popover<
popoverState === PopoverStates.Open
)

let close = useEvent((focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => {
dispatch({ type: ActionTypes.ClosePopover })
let close = useEvent(
(
focusableElement?:
| HTMLElement
| MutableRefObject<HTMLElement | null>
| MouseEvent<HTMLElement>
) => {
dispatch({ type: ActionTypes.ClosePopover })

let restoreElement = (() => {
if (!focusableElement) return button
if (focusableElement instanceof HTMLElement) return focusableElement
if (focusableElement.current instanceof HTMLElement) return focusableElement.current
let restoreElement = (() => {
if (!focusableElement) return button
if (focusableElement instanceof HTMLElement) return focusableElement
if ('current' in focusableElement && focusableElement.current instanceof HTMLElement)
return focusableElement.current

return button
})()
return button
})()

restoreElement?.focus()
})
restoreElement?.focus()
}
)

let api = useMemo<ContextType<typeof PopoverAPIContext>>(
() => ({ close, isPortalled }),
Expand Down

0 comments on commit db736d8

Please sign in to comment.