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
56 changes: 55 additions & 1 deletion docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,35 @@ e.g.:
</Modal>
```

The modal content now has the `dialog` role, so it needs an accessible name. You can provide one with the new `aria-label` prop:

```diff
-<Modal visible={visible} onDismiss={hideModal}>
+<Modal visible={visible} onDismiss={hideModal} aria-label="Example modal">
<Text>Content</Text>
</Modal>
```

The overlay behind the content is now hidden from assistive technology. Instead, when the modal is `dismissable`, screen reader users can dismiss it with a visually hidden button inside the dialog.

The `overlayAccessibilityLabel` prop was renamed to `dismissAccessibilityLabel`, which is used for the button's accessibility label:

```diff
<Modal
visible={visible}
onDismiss={hideModal}
- overlayAccessibilityLabel="Close"
+ dismissAccessibilityLabel="Close"
>
<Text>Content</Text>
</Modal>
```

Previously, the Android back button dismissed the modal when `dismissable` was `true`, even if `dismissableBackButton` was `false`. The `dismissableBackButton` prop can now prevent the modal from being dismissed via the back button independently of the `dismissable` prop.

### Dialog

`Dialog` now uses a `Modal` internally and doesn't require an explicit `Portal` wrapper. So you need to remove any existing `Portal` wrappers around `Dialog`:
`Dialog` now uses a `Portal` internally and doesn't require an explicit `Portal` wrapper. So you need to remove any existing `Portal` wrappers around `Dialog`:

```diff
-<Portal>
Expand All @@ -210,9 +236,37 @@ e.g.:
+</Dialog>
```

The dialog now has the `dialog` role. On web, the dialog's accessible name is set automatically by `Dialog.Title`. You can specify a different name with the new `aria-label` prop, e.g. when the dialog has no title:

```jsx
<Dialog visible={visible} onDismiss={hideDialog} aria-label="Delete file">
<Dialog.Content>
<Text>Are you sure?</Text>
</Dialog.Content>
</Dialog>
```

When the dialog is `dismissable`, screen reader users can dismiss it with a visually hidden button inside the dialog. You can change the button's accessibility label with the new `dismissAccessibilityLabel` prop.

- The default elevation changed from level `1` to level `3`.
- The `style` prop no longer configures the background color or border radius. You can override `theme.colors.surfaceContainerHigh` and `theme.shapes.corner.extraLarge` using the `theme` prop instead.

### Menu

The `overlayAccessibilityLabel` prop was renamed to `dismissAccessibilityLabel`:

```diff
<Menu
visible={visible}
onDismiss={closeMenu}
anchor={anchor}
- overlayAccessibilityLabel="Close"
+ dismissAccessibilityLabel="Close"
>
<Menu.Item title="Item" />
</Menu>
```

### Searchbar

The misspelled `traileringIcon` props have been renamed:
Expand Down
14 changes: 14 additions & 0 deletions example/src/Examples/DialogExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DialogWithLoadingIndicator,
DialogWithLongText,
DialogWithRadioBtns,
DialogWithUndismissableBackButton,
UndismissableDialog,
} from './Dialogs';
import ScreenWrapper from '../ScreenWrapper';
Expand Down Expand Up @@ -79,6 +80,15 @@ const DialogExample = () => {
Dismissable back button
</Button>
)}
{Platform.OS === 'android' && (
<Button
mode="outlined"
onPress={_toggleDialog('dialog8')}
style={styles.button}
>
Undismissable back button
</Button>
)}
<DialogWithLongText
visible={_getVisible('dialog1')}
close={_toggleDialog('dialog1')}
Expand Down Expand Up @@ -107,6 +117,10 @@ const DialogExample = () => {
visible={_getVisible('dialog7')}
close={_toggleDialog('dialog7')}
/>
<DialogWithUndismissableBackButton
visible={_getVisible('dialog8')}
close={_toggleDialog('dialog8')}
/>
</ScreenWrapper>
);
};
Expand Down
29 changes: 29 additions & 0 deletions example/src/Examples/Dialogs/DialogWithUndismissableBackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Button, Dialog, Palette } from 'react-native-paper';

import { TextComponent } from './DialogTextComponent';

const DialogWithUndismissableBackButton = ({
visible,
close,
}: {
visible: boolean;
close: () => void;
}) => (
<Dialog onDismiss={close} visible={visible} dismissableBackButton={false}>
<Dialog.Title>Alert</Dialog.Title>
<Dialog.Content>
<TextComponent>
This dialog can be dismissed by tapping outside, however the hardware
back button will not close it!
</TextComponent>
</Dialog.Content>
<Dialog.Actions>
<Button textColor={Palette.tertiary50} disabled>
Disagree
</Button>
<Button onPress={close}>Agree</Button>
</Dialog.Actions>
</Dialog>
);

export default DialogWithUndismissableBackButton;
1 change: 1 addition & 0 deletions example/src/Examples/Dialogs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { default as DialogWithRadioBtns } from './DialogWithRadioBtns';
export { default as UndismissableDialog } from './UndismissableDialog';
export { default as DialogWithIcon } from './DialogWithIcon';
export { default as DialogWithDismissableBackButton } from './DialogWithDismissableBackButton';
export { default as DialogWithUndismissableBackButton } from './DialogWithUndismissableBackButton';
38 changes: 28 additions & 10 deletions src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useInternalTheme } from '../../core/theming';
import type { Elevation, ThemeProp } from '../../theme/types';
import Modal from '../Modal';
import type { SurfaceStyle } from '../Surface';
import { DialogTitleIdContext } from './utils';
import type { DialogChildProps } from './utils';

export type Props = {
Expand All @@ -28,6 +29,14 @@ export type Props = {
* Callback that is called when the user dismisses the dialog.
*/
onDismiss?: () => void;
/**
* Accessibility label for dismissing the dialog if it's `dismissable`.
*/
dismissAccessibilityLabel?: string;
/**
* Accessible name for the dialog. On web, defaults to the text of `Dialog.Title`.
*/
'aria-label'?: string;
/**
* Determines Whether the dialog is visible.
*/
Expand Down Expand Up @@ -95,6 +104,8 @@ const Dialog = ({
dismissable = true,
dismissableBackButton = dismissable,
onDismiss,
dismissAccessibilityLabel,
'aria-label': ariaLabel,
visible = false,
style,
theme: themeOverrides,
Expand All @@ -108,11 +119,16 @@ const Dialog = ({

const backgroundColor = theme.colors.surfaceContainerHigh;

const titleId = React.useId();

return (
<Modal
aria-label={ariaLabel}
aria-labelledby={ariaLabel == null ? titleId : undefined}
dismissable={dismissable}
dismissableBackButton={dismissableBackButton}
onDismiss={onDismiss}
dismissAccessibilityLabel={dismissAccessibilityLabel}
visible={visible}
contentBackgroundColor={backgroundColor}
contentBorderRadius={borderRadius}
Expand All @@ -128,17 +144,19 @@ const Dialog = ({
testID={testID}
overlayTestID={overlayTestID}
>
{React.Children.toArray(children)
.filter((child) => child != null && typeof child !== 'boolean')
.map((child, i) => {
if (i === 0 && React.isValidElement<DialogChildProps>(child)) {
return React.cloneElement(child, {
style: [{ marginTop: 24 }, child.props.style],
});
}
<DialogTitleIdContext.Provider value={titleId}>
{React.Children.toArray(children)
.filter((child) => child != null && typeof child !== 'boolean')
.map((child, i) => {
if (i === 0 && React.isValidElement<DialogChildProps>(child)) {
return React.cloneElement(child, {
style: [{ marginTop: 24 }, child.props.style],
});
}

return child;
})}
return child;
})}
</DialogTitleIdContext.Provider>
</Modal>
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/Dialog/DialogTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { StyleSheet } from 'react-native';
import type { StyleProp, TextStyle } from 'react-native';

import { DialogTitleIdContext } from './utils';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../theme/types';
import Text from '../Typography/Text';
Expand Down Expand Up @@ -48,10 +49,12 @@ const DialogTitle = ({
children,
theme: themeOverrides,
style,
nativeID,
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
const { colors, fonts } = theme;
const titleId = React.useContext(DialogTitleIdContext);

const headerTextStyle = {
color: colors.onSurface,
Expand All @@ -62,6 +65,7 @@ const DialogTitle = ({
<Text
variant="headlineSmall"
role="heading"
nativeID={titleId ?? nativeID}
style={[styles.text, styles.v3Text, headerTextStyle, style]}
{...rest}
>
Expand Down
5 changes: 5 additions & 0 deletions src/components/Dialog/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as React from 'react';
import type { StyleProp, ViewStyle } from 'react-native';

export const DialogTitleIdContext = React.createContext<string | undefined>(
undefined
);

export type DialogChildProps = {
style?: StyleProp<ViewStyle>;
};
Expand Down
6 changes: 3 additions & 3 deletions src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type Props = {
/**
* Accessibility label for the overlay. This is read by the screen reader when the user taps outside the menu.
*/
overlayAccessibilityLabel?: string;
dismissAccessibilityLabel?: string;
/**
* testID for the overlay that is displayed behind the menu.
*/
Expand Down Expand Up @@ -189,7 +189,7 @@ const isCoordinate = (anchor: any): anchor is { x: number; y: number } =>
const Menu = ({
visible,
statusBarHeight,
overlayAccessibilityLabel = 'Close menu',
dismissAccessibilityLabel = 'Close menu',
overlayTestID,
testID,
anchor,
Expand Down Expand Up @@ -681,7 +681,7 @@ const Menu = ({
{rendered ? (
<Portal>
<Pressable
aria-label={overlayAccessibilityLabel}
aria-label={dismissAccessibilityLabel}
role="button"
onPress={onDismiss}
pointerEvents={visible ? 'auto' : 'none'}
Expand Down
Loading
Loading