Skip to content

Commit

Permalink
add outside-click to Dialog itself
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinMalfait committed Feb 18, 2021
1 parent 2785733 commit 8296ead
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
82 changes: 82 additions & 0 deletions packages/@headlessui-react/src/components/dialog/dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
assertDialogTitle,
getDialog,
getDialogOverlay,
getByText,
assertActiveElement,
} from '../../test-utils/accessibility-assertions'
import { click, press, Keys } from '../../test-utils/interactions'
import { Props } from '../../types'
Expand Down Expand Up @@ -67,6 +69,17 @@ describe('Safe guards', () => {

describe('Rendering', () => {
describe('Dialog', () => {
it(
'should complain when the `open` and `onClose` prop are missing',
suppressConsoleLogs(async () => {
// @ts-expect-error
expect(() => render(<Dialog as="div" />)).toThrowErrorMatchingInlineSnapshot(
`"You have to provide an \`open\` and an \`onClose\` prop to the \`Dialog\` component."`
)
expect.hasAssertions()
})
)

it(
'should complain when an `open` prop is provided without an `onClose` prop',
suppressConsoleLogs(async () => {
Expand Down Expand Up @@ -392,4 +405,73 @@ describe('Mouse interactions', () => {
assertDialog({ state: DialogState.InvisibleUnmounted })
})
)

it(
'should be possible to close the dialog, and re-focus the button when we click outside on the body element',
suppressConsoleLogs(async () => {
function Example() {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
<Dialog open={isOpen} onClose={setIsOpen}>
Contents
<TabSentinel />
</Dialog>
</>
)
}
render(<Example />)

// Open dialog
await click(getByText('Trigger'))

// Verify it is open
assertDialog({ state: DialogState.Visible })

// Click the body to close
await click(document.body)

// Verify it is closed
assertDialog({ state: DialogState.InvisibleUnmounted })

// Verify the button is focused
assertActiveElement(getByText('Trigger'))
})
)

it(
'should be possible to close the dialog, and keep focus on the focusable element',
suppressConsoleLogs(async () => {
function Example() {
let [isOpen, setIsOpen] = useState(false)
return (
<>
<button>Hello</button>
<button onClick={() => setIsOpen(v => !v)}>Trigger</button>
<Dialog open={isOpen} onClose={setIsOpen}>
Contents
<TabSentinel />
</Dialog>
</>
)
}
render(<Example />)

// Open dialog
await click(getByText('Trigger'))

// Verify it is open
assertDialog({ state: DialogState.Visible })

// Click the button to close (outside click)
await click(getByText('Hello'))

// Verify it is closed
assertDialog({ state: DialogState.InvisibleUnmounted })

// Verify the button is focused
assertActiveElement(getByText('Hello'))
})
)
})
21 changes: 21 additions & 0 deletions packages/@headlessui-react/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ let DialogRoot = forwardRefWithAs(function Dialog<
// Validations
let hasOpen = props.hasOwnProperty('open')
let hasOnClose = props.hasOwnProperty('onClose')
if (!hasOpen && !hasOnClose) {
throw new Error(
`You have to provide an \`open\` and an \`onClose\` prop to the \`Dialog\` component.`
)
}

if (!hasOpen) {
throw new Error(
`You provided an \`onClose\` prop to the \`Dialog\`, but forgot an \`open\` prop.`
Expand Down Expand Up @@ -161,6 +167,21 @@ let DialogRoot = forwardRefWithAs(function Dialog<
[dispatch]
)

// Handle outside click
useEffect(() => {
function handler(event: MouseEvent) {
let target = event.target as HTMLElement

if (dialogState !== DialogStates.Open) return
if (internalDialogRef.current?.contains(target)) return

close()
}

window.addEventListener('mousedown', handler)
return () => window.removeEventListener('mousedown', handler)
}, [dialogState, internalDialogRef, close])

// Handle `Escape` to close
useEffect(() => {
function handler(event: KeyboardEvent) {
Expand Down

0 comments on commit 8296ead

Please sign in to comment.