Skip to content

Commit d78ad6d

Browse files
authored
feat: Add clickOutside prop to Modal (#2214)
For the Ruff settings editor, I wanted to close the modal when pressing escape, but not clicking outside. Turns out this wasn't possible with our previous modal component.
1 parent 7331976 commit d78ad6d

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

packages/components/src/modal/Modal.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ function makeModal({
88
className,
99
children,
1010
keyboard,
11+
clickOutside,
1112
isOpen,
1213
centered,
1314
onOpened,
@@ -17,6 +18,7 @@ function makeModal({
1718
className?: string;
1819
children?;
1920
keyboard?: boolean;
21+
clickOutside?: boolean;
2022
isOpen?: boolean;
2123
centered?: boolean;
2224
onOpened?: () => void;
@@ -27,6 +29,7 @@ function makeModal({
2729
<Modal
2830
className={className}
2931
keyboard={keyboard}
32+
clickOutside={clickOutside}
3033
isOpen={isOpen}
3134
centered={centered}
3235
onOpened={onOpened}
@@ -103,6 +106,32 @@ it('closes only when clicking outside the modal', async () => {
103106
expect(toggle).toBeCalledTimes(0);
104107
});
105108

109+
it('does not close on mouseUp outside the modal when initiated from inside the modal', async () => {
110+
const user = userEvent.setup();
111+
const toggle = jest.fn();
112+
render(makeModal({ isOpen: true, toggle }));
113+
114+
const modalContent = document.querySelector('.modal-content')!;
115+
await user.pointer({ target: modalContent, keys: '[MouseLeft>]' });
116+
expect(toggle).toBeCalledTimes(0);
117+
118+
await user.pointer({
119+
target: screen.getByRole('dialog'),
120+
keys: '[/MouseLeft]',
121+
});
122+
expect(toggle).toBeCalledTimes(0);
123+
});
124+
125+
it('does not close when clicking outside when clickOutside is false', async () => {
126+
const user = userEvent.setup();
127+
const toggle = jest.fn();
128+
render(makeModal({ isOpen: true, toggle, clickOutside: false }));
129+
130+
// note that outer div covers the entire screen
131+
await user.click(screen.getByRole('dialog'));
132+
expect(toggle).toBeCalledTimes(0);
133+
});
134+
106135
it('calls onOpen when opens', () => {
107136
const onOpened = jest.fn();
108137
const toggle = jest.fn();

packages/components/src/modal/Modal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface ModalProps {
1616
children?: ReactNode;
1717
role?: string;
1818
keyboard?: boolean;
19+
clickOutside?: boolean;
1920
isOpen?: boolean;
2021
centered?: boolean;
2122
size?: 'sm' | 'lg' | 'xl' | undefined;
@@ -30,6 +31,7 @@ function Modal({
3031
children,
3132
role = 'role',
3233
keyboard = true,
34+
clickOutside = true,
3335
isOpen = false,
3436
centered = false,
3537
size,
@@ -146,6 +148,7 @@ function Modal({
146148
onMouseUp={e => {
147149
if (
148150
backgroundClicked &&
151+
clickOutside &&
149152
e.target === background.current &&
150153
toggle !== undefined
151154
) {

0 commit comments

Comments
 (0)