diff --git a/example/src/Examples/DialogExample.tsx b/example/src/Examples/DialogExample.tsx
index 3c572d5239..7a929c0ade 100644
--- a/example/src/Examples/DialogExample.tsx
+++ b/example/src/Examples/DialogExample.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { StyleSheet } from 'react-native';
+import { Platform, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
@@ -7,6 +7,7 @@ import { useExampleTheme } from '..';
import ScreenWrapper from '../ScreenWrapper';
import {
DialogWithCustomColors,
+ DialogWithDismissableBackButton,
DialogWithIcon,
DialogWithLoadingIndicator,
DialogWithLongText,
@@ -73,6 +74,15 @@ const DialogExample = () => {
With icon
)}
+ {Platform.OS === 'android' && (
+
+ )}
{
close={_toggleDialog('dialog6')}
/>
)}
+
);
};
diff --git a/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx
new file mode 100644
index 0000000000..32512dc91d
--- /dev/null
+++ b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+
+import { Button, Portal, Dialog, MD2Colors } from 'react-native-paper';
+
+import { TextComponent } from './DialogTextComponent';
+
+const DialogWithDismissableBackButton = ({
+ visible,
+ close,
+}: {
+ visible: boolean;
+ close: () => void;
+}) => (
+
+
+
+);
+
+export default DialogWithDismissableBackButton;
diff --git a/example/src/Examples/Dialogs/index.tsx b/example/src/Examples/Dialogs/index.tsx
index c7df1b308c..7af735d036 100644
--- a/example/src/Examples/Dialogs/index.tsx
+++ b/example/src/Examples/Dialogs/index.tsx
@@ -4,3 +4,4 @@ export { default as DialogWithLongText } from './DialogWithLongText';
export { default as DialogWithRadioBtns } from './DialogWithRadioBtns';
export { default as UndismissableDialog } from './UndismissableDialog';
export { default as DialogWithIcon } from './DialogWithIcon';
+export { default as DialogWithDismissableBackButton } from './DialogWithDismissableBackButton';
diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx
index 8c2680a4c0..fd5baada08 100644
--- a/src/components/Dialog/Dialog.tsx
+++ b/src/components/Dialog/Dialog.tsx
@@ -22,6 +22,10 @@ export type Props = {
* Determines whether clicking outside the dialog dismiss it.
*/
dismissable?: boolean;
+ /**
+ * Determines whether clicking Android hardware back button dismiss dialog.
+ */
+ dismissableBackButton?: boolean;
/**
* Callback that is called when the user dismisses the dialog.
*/
@@ -95,6 +99,7 @@ const DIALOG_ELEVATION: number = 24;
const Dialog = ({
children,
dismissable = true,
+ dismissableBackButton = dismissable,
onDismiss,
visible = false,
style,
@@ -116,6 +121,7 @@ const Dialog = ({
return (
{},
@@ -170,7 +175,7 @@ function Modal({
}
const onHardwareBackPress = () => {
- if (dismissable) {
+ if (dismissable || dismissableBackButton) {
hideModal();
}
@@ -183,7 +188,7 @@ function Modal({
onHardwareBackPress
);
return () => subscription.remove();
- }, [dismissable, hideModal, visible]);
+ }, [dismissable, dismissableBackButton, hideModal, visible]);
const prevVisible = React.useRef(null);
diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx
index 7d049caa01..eef2dd9fea 100644
--- a/src/components/__tests__/Dialog.test.tsx
+++ b/src/components/__tests__/Dialog.test.tsx
@@ -1,5 +1,11 @@
import React from 'react';
-import { Text, StyleSheet } from 'react-native';
+import {
+ Text,
+ StyleSheet,
+ Platform,
+ BackHandler as RNBackHandler,
+ BackHandlerStatic as RNBackHandlerStatic,
+} from 'react-native';
import { act, fireEvent, render } from '@testing-library/react-native';
@@ -10,6 +16,17 @@ jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: () => ({ bottom: 44, left: 0, right: 0, top: 37 }),
}));
+jest.mock('react-native/Libraries/Utilities/BackHandler', () =>
+ // eslint-disable-next-line jest/no-mocks-import
+ require('react-native/Libraries/Utilities/__mocks__/BackHandler')
+);
+
+interface BackHandlerStatic extends RNBackHandlerStatic {
+ mockPressBack(): void;
+}
+
+const BackHandler = RNBackHandler as BackHandlerStatic;
+
describe('Dialog', () => {
it('should render passed children', () => {
const { getByTestId } = render(
@@ -37,6 +54,51 @@ describe('Dialog', () => {
expect(onDismiss).toHaveBeenCalledTimes(1);
});
+ it('should not call onDismiss when dismissable is false', () => {
+ const onDismiss = jest.fn();
+ const { getByTestId } = render(
+
+ );
+
+ fireEvent.press(getByTestId('dialog-backdrop'));
+
+ act(() => {
+ jest.runAllTimers();
+ });
+ expect(onDismiss).toHaveBeenCalledTimes(0);
+ });
+
+ it('should call onDismiss on Android back button when dismissable is false but dismissableBackButton is true', () => {
+ Platform.OS = 'android';
+ const onDismiss = jest.fn();
+ const { getByTestId } = render(
+
+ );
+
+ fireEvent.press(getByTestId('dialog-backdrop'));
+
+ act(() => {
+ jest.runAllTimers();
+ });
+ expect(onDismiss).toHaveBeenCalledTimes(0);
+
+ act(() => {
+ BackHandler.mockPressBack();
+ jest.runAllTimers();
+ });
+ expect(onDismiss).toHaveBeenCalledTimes(1);
+ });
+
it('should apply top margin to the first child if the dialog is V3', () => {
const { getByTestId } = render(