Skip to content

Commit

Permalink
Trigger onClose when Dialog backdrop is clicked (#4565)
Browse files Browse the repository at this point in the history
* Trigger onClose when Dialog backdrop is clicked

* Update docs with new backdrop gesture
  • Loading branch information
dgreif committed May 8, 2024
1 parent 02a2a2b commit 564db1d
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/popular-jokes-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

`Dialog` and `ConfirmationDialog` can now be closed by clicking on the backdrop surrounding the dialog. This will cause `onClose` to be called with a new `'backdrop'` gesture.
14 changes: 7 additions & 7 deletions docs/content/drafts/Dialog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ render(<ShorthandExample2 />)
### ConfirmationDialogProps
| Prop name | Type | Default | Description |
| :------------------- | :-------------------------------------------------------------------- | :--------- | :---------------------------------------------------------------------------------------------------------------------------- |
| title | `React.ReactNode` | | Required. Sets the title of the dialog, which by default is also used as the `aria-labelledby` attribute. |
| onClose | `(gesture: 'confirm''cancel''close-button''escape') => void` | | Required. This callback is invoked when a gesture to close the dialog is performed. The first argument indicates the gesture. |
| cancelButtonContent | `React.ReactNode` | `"Cancel"` | The content to use for the cancel button. |
| confirmButtonContent | `React.ReactNode` | `"OK"` | The content to use for the confirm button. |
| confirmButtonType | `"normal""primary""danger"` | `Button` | The type of button to use for the confirm button. |
| Prop name | Type | Default | Description |
| :------------------- | :----------------------------------------------- | :----------------------------- | :-------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| title | `React.ReactNode` | | Required. Sets the title of the dialog, which by default is also used as the `aria-labelledby` attribute. |
| onClose | `(gesture: 'confirm''cancel''close-button' | 'backdrop 'escape') => void` | | Required. This callback is invoked when a gesture to close the dialog is performed. The first argument indicates the gesture. |
| cancelButtonContent | `React.ReactNode` | `"Cancel"` | The content to use for the cancel button. |
| confirmButtonContent | `React.ReactNode` | `"OK"` | The content to use for the confirm button. |
| confirmButtonType | `"normal" │ "primary" │ "danger"` | `Button` | The type of button to use for the confirm button. |
### ConfirmOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ConfirmationDialogProps {
* Required. This callback is invoked when a gesture to close the dialog
* is performed. The first argument indicates the gesture.
*/
onClose: (gesture: 'confirm' | 'close-button' | 'cancel' | 'escape') => void
onClose: (gesture: 'confirm' | 'close-button' | 'backdrop' | 'cancel' | 'escape') => void

/**
* Required. The title of the ConfirmationDialog. This is usually a brief
Expand Down
19 changes: 17 additions & 2 deletions packages/react/src/Dialog/Dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,22 @@ describe('Dialog', () => {

await user.click(getByLabelText('Close'))

expect(onClose).toHaveBeenCalled()
expect(onClose).toHaveBeenCalledWith('close-button')
expect(onClose).toHaveBeenCalledTimes(1) // Ensure it's not called with a backdrop gesture as well
})

it('calls `onClose` when clicking the backdrop', async () => {
const user = userEvent.setup()
const onClose = jest.fn()
const {getByRole} = render(<Dialog onClose={onClose}>Pay attention to me</Dialog>)

expect(onClose).not.toHaveBeenCalled()

const dialog = getByRole('dialog')
const backdrop = dialog.parentElement!
await user.click(backdrop)

expect(onClose).toHaveBeenCalledWith('backdrop')
})

it('calls `onClose` when keying "Escape"', async () => {
Expand All @@ -80,7 +95,7 @@ describe('Dialog', () => {

await user.keyboard('{Escape}')

expect(onClose).toHaveBeenCalled()
expect(onClose).toHaveBeenCalledWith('escape')
})

it('changes the <body> style for `overflow` if it is not set to "hidden"', () => {
Expand Down
18 changes: 13 additions & 5 deletions packages/react/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import React, {useCallback, useEffect, useRef, useState, type SyntheticEvent} from 'react'
import styled from 'styled-components'
import type {ButtonProps} from '../Button'
import {Button} from '../Button'
Expand Down Expand Up @@ -98,11 +98,11 @@ export interface DialogProps extends SxProp {

/**
* This method is invoked when a gesture to close the dialog is used (either
* an Escape key press or clicking the "X" in the top-right corner). The
* an Escape key press, clicking the backdrop, or clicking the "X" in the top-right corner). The
* gesture argument indicates the gesture that was used to close the dialog
* (either 'close-button' or 'escape').
* ('close-button', 'backdrop', or 'escape').
*/
onClose: (gesture: 'close-button' | 'escape') => void
onClose: (gesture: 'close-button' | 'backdrop' | 'escape') => void

/**
* Default: "dialog". The ARIA role to assign to this dialog.
Expand Down Expand Up @@ -414,6 +414,14 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
}
}
const defaultedProps = {...props, title, subtitle, role, dialogLabelId, dialogDescriptionId}
const onBackdropClick = useCallback(
(e: SyntheticEvent) => {
if (e.target === e.currentTarget) {
onClose('backdrop')
}
},
[onClose],
)

const dialogRef = useRef<HTMLDivElement>(null)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
Expand Down Expand Up @@ -465,7 +473,7 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
return (
<>
<Portal>
<Backdrop ref={backdropRef} {...positionDataAttributes}>
<Backdrop ref={backdropRef} {...positionDataAttributes} onClick={onBackdropClick}>
<StyledDialog
width={width}
height={height}
Expand Down

0 comments on commit 564db1d

Please sign in to comment.