Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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