From d5b1361121986a72e9a436f46925ba6c21cd5de8 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 20 Aug 2026 09:26:48 +0200 Subject: [PATCH 01/13] refactor: updated Dialog to comply with Material3 guidelines --- .../6.x/docs/components/Dialog/DialogIcon.mdx | 9 +---- docs/src/data/componentDocs6x.json | 4 +-- .../src/Examples/Dialogs/DialogWithIcon.tsx | 13 ++----- src/components/Dialog/Dialog.tsx | 34 +++++++++++++++---- src/components/Dialog/DialogIcon.tsx | 9 +---- src/components/Dialog/DialogScrollArea.tsx | 2 +- src/components/Dialog/utils.ts | 4 +-- 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/6.x/docs/components/Dialog/DialogIcon.mdx b/docs/6.x/docs/components/Dialog/DialogIcon.mdx index 34777210ca..a736d9d384 100644 --- a/docs/6.x/docs/components/Dialog/DialogIcon.mdx +++ b/docs/6.x/docs/components/Dialog/DialogIcon.mdx @@ -20,7 +20,6 @@ A component to show an icon in a Dialog. ## Usage ```js import * as React from 'react'; -import { StyleSheet } from 'react-native'; import { Dialog, Portal, Text } from 'react-native-paper'; const MyComponent = () => { @@ -32,7 +31,7 @@ const MyComponent = () => { - This is a title + This is a title This is simple dialog @@ -41,12 +40,6 @@ const MyComponent = () => { ); }; -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - }, -}) - export default MyComponent; ``` diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index 300c315b99..374b82a0dc 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5400,10 +5400,10 @@ "Dialog/DialogIcon": { "filepath": "Dialog/DialogIcon.tsx", "title": "Dialog.Icon", - "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { StyleSheet } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nconst styles = StyleSheet.create({\n title: {\n textAlign: 'center',\n },\n})\n\nexport default MyComponent;\n```", + "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-icon", "data": { - "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { StyleSheet } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nconst styles = StyleSheet.create({\n title: {\n textAlign: 'center',\n },\n})\n\nexport default MyComponent;\n```", + "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.Icon", "methods": [], "statics": [], diff --git a/example/src/Examples/Dialogs/DialogWithIcon.tsx b/example/src/Examples/Dialogs/DialogWithIcon.tsx index 6281e9bf6c..9a5b6477c1 100644 --- a/example/src/Examples/Dialogs/DialogWithIcon.tsx +++ b/example/src/Examples/Dialogs/DialogWithIcon.tsx @@ -1,5 +1,3 @@ -import { StyleSheet } from 'react-native'; - import { Button, Portal, Dialog, Palette } from 'react-native-paper'; import { TextComponent } from './DialogTextComponent'; @@ -15,11 +13,11 @@ const DialogWithIcon = ({ - Dialog with Icon + Dialog with Icon - This is a dialog with new component called DialogIcon. When icon is - displayed you should center the header. + This is a dialog with a component called DialogIcon. When the icon + is displayed, the title is centered automatically. @@ -33,9 +31,4 @@ const DialogWithIcon = ({ ); }; -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - }, -}); export default DialogWithIcon; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index a7b082fe89..dc2745485c 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -103,6 +103,12 @@ const Dialog = ({ const borderRadius = theme.shapes.corner.extraLarge; const backgroundColor = theme.colors.surfaceContainerHigh; + const dialogChildren = React.Children.toArray(children).filter( + (child) => child != null && typeof child !== 'boolean' + ); + const hasIcon = dialogChildren.some( + (child) => React.isValidElement(child) && child.type === DialogIcon + ); return ( - {React.Children.toArray(children) - .filter((child) => child != null && typeof child !== 'boolean') - .map((child, i) => { - if (i === 0 && React.isValidElement(child)) { + {dialogChildren.map((child, i) => { + if (React.isValidElement(child)) { + const topMarginStyle = + i === 0 && child.type !== DialogIcon + ? { marginTop: 24 } + : undefined; + const titleAlignmentStyle = + hasIcon && child.type === DialogTitle + ? styles.titleWithIcon + : undefined; + + if (topMarginStyle || titleAlignmentStyle) { return React.cloneElement(child, { - style: [{ marginTop: 24 }, child.props.style], + style: [topMarginStyle, child.props.style, titleAlignmentStyle], }); } return child; - })} + } + + return child; + })} ); }; @@ -160,6 +177,11 @@ const styles = StyleSheet.create({ marginVertical: Platform.OS === 'android' ? 44 : 0, elevation: DIALOG_ELEVATION, justifyContent: 'flex-start', + minWidth: 280, + maxWidth: 560, + }, + titleWithIcon: { + textAlign: 'center', }, }); diff --git a/src/components/Dialog/DialogIcon.tsx b/src/components/Dialog/DialogIcon.tsx index 3ced9e12b0..5c533f877e 100644 --- a/src/components/Dialog/DialogIcon.tsx +++ b/src/components/Dialog/DialogIcon.tsx @@ -32,7 +32,6 @@ export type Props = { * ## Usage * ```js * import * as React from 'react'; - * import { StyleSheet } from 'react-native'; * import { Dialog, Portal, Text } from 'react-native-paper'; * * const MyComponent = () => { @@ -44,7 +43,7 @@ export type Props = { * * * - * This is a title + * This is a title * * This is simple dialog * @@ -53,12 +52,6 @@ export type Props = { * ); * }; * - * const styles = StyleSheet.create({ - * title: { - * textAlign: 'center', - * }, - * }) - * * export default MyComponent; * ``` */ diff --git a/src/components/Dialog/DialogScrollArea.tsx b/src/components/Dialog/DialogScrollArea.tsx index a46824be45..5418504783 100644 --- a/src/components/Dialog/DialogScrollArea.tsx +++ b/src/components/Dialog/DialogScrollArea.tsx @@ -52,7 +52,7 @@ const DialogScrollArea = (props: Props) => { const theme = useInternalTheme(props.theme); const { colors } = theme; const borderStyles = { - borderColor: colors.surfaceVariant, + borderColor: colors.outline, borderTopWidth: 1, borderBottomWidth: 1, }; diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts index 64ccec77cc..5413d4c612 100644 --- a/src/components/Dialog/utils.ts +++ b/src/components/Dialog/utils.ts @@ -1,7 +1,7 @@ -import type { StyleProp, ViewStyle } from 'react-native'; +import type { StyleProp, TextStyle, ViewStyle } from 'react-native'; export type DialogChildProps = { - style?: StyleProp; + style?: StyleProp; }; export type DialogActionChildProps = DialogChildProps & { From 49442efee47ef512bd8d0f5a3f555e7adb0412ed Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:54:22 +0200 Subject: [PATCH 02/13] refactor: introduced new Dialog API, updated docs --- docs/6.x/docs/components/Dialog/Dialog.mdx | 59 ++++- docs/src/data/componentDocs6x.json | 4 +- src/components/Dialog/Dialog.tsx | 267 +++++++++++++++++---- 3 files changed, 271 insertions(+), 59 deletions(-) diff --git a/docs/6.x/docs/components/Dialog/Dialog.mdx b/docs/6.x/docs/components/Dialog/Dialog.mdx index e871f7f750..8a9f1381c8 100644 --- a/docs/6.x/docs/components/Dialog/Dialog.mdx +++ b/docs/6.x/docs/components/Dialog/Dialog.mdx @@ -11,6 +11,19 @@ import ExtendedExample from '@docs/components/ExtendedExample.tsx'; Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks. To render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal/Portal) component. +## Recommended props + +| Prop | Type | Description | +| --- | --- | --- | +| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. | +| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. | +| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | +| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and Button props. | +| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. | +| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. | +| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. | +| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. | + @@ -21,7 +34,7 @@ To render the `Dialog` above other components, you'll need to wrap it with the [ ```js import * as React from 'react'; import { View } from 'react-native'; -import { Button, Dialog, Portal, PaperProvider, Text } from 'react-native-paper'; +import { Button, Dialog, Portal, PaperProvider } from 'react-native-paper'; const MyComponent = () => { const [visible, setVisible] = React.useState(false); @@ -35,15 +48,13 @@ const MyComponent = () => { - - Alert - - This is simple dialog - - - - - + @@ -53,6 +64,32 @@ const MyComponent = () => { export default MyComponent; ``` +## Compound composition + +`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and +`Dialog.Actions` remain available for custom composition within `Dialog`. +Passing them through `children` is deprecated; prefer the props above. + +## Migrating from children + +```js +// Before + + Alert + Something happened. + + + +// After + +``` + ## Props @@ -93,7 +130,7 @@ export default MyComponent;
-### children (required) +### children (depracated, use props instead)
diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index 374b82a0dc..f6d7ca856c 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5206,10 +5206,10 @@ "Dialog/Dialog": { "filepath": "Dialog/Dialog.tsx", "title": "Dialog", - "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n Alert\n \n This is simple dialog\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Recommended props\n\n| Prop | Type | Description |\n| --- | --- | --- |\n| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. |\n| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. |\n| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. |\n| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. |\n| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. |\n| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. |\n| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. |\n| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. |\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```\n\n## Compound composition\n\n`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and\n`Dialog.Actions` remain available for custom composition within `Dialog`.\nPassing them through `children` is deprecated; prefer the props above.\n\n## Migrating from children\n\n```js\n// Before\n\n Alert\n Something happened.\n \n\n\n// After\n\n```", "link": "dialog", "data": { - "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n Alert\n \n This is simple dialog\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Recommended props\n\n| Prop | Type | Description |\n| --- | --- | --- |\n| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. |\n| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. |\n| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. |\n| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. |\n| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. |\n| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. |\n| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. |\n| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. |\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```\n\n## Compound composition\n\n`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and\n`Dialog.Actions` remain available for custom composition within `Dialog`.\nPassing them through `children` is deprecated; prefer the props above.\n\n## Migrating from children\n\n```js\n// Before\n\n Alert\n Something happened.\n \n\n\n// After\n\n```", "displayName": "Dialog", "methods": [], "statics": [], diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index dc2745485c..cce05e9a47 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,20 +1,26 @@ import * as React from 'react'; -import { Animated, Platform, StyleSheet } from 'react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; +import { Animated, Platform, ScrollView, StyleSheet } from 'react-native'; +import type { ScrollViewProps, StyleProp, ViewStyle } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import DialogActions from './DialogActions'; import DialogContent from './DialogContent'; +import type { Props as DialogContentProps } from './DialogContent'; import DialogIcon from './DialogIcon'; import DialogScrollArea from './DialogScrollArea'; +import type { Props as DialogScrollAreaProps } from './DialogScrollArea'; import DialogTitle from './DialogTitle'; +import type { DialogChildProps } from './utils'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; +import Button from '../Button/Button'; +import type { Props as ButtonProps } from '../Button/Button'; +import type { IconSource } from '../Icon'; import Modal from '../Modal'; -import type { DialogChildProps } from './utils'; +import Text from '../Typography/Text'; -export type Props = { +type CommonProps = { /** * Determines whether clicking outside the dialog dismiss it. */ @@ -31,10 +37,6 @@ export type Props = { * Determines Whether the dialog is visible. */ visible: boolean; - /** - * Content of the `Dialog`. - */ - children: React.ReactNode; style?: Animated.WithAnimatedValue>; /** * @optional @@ -46,17 +48,120 @@ export type Props = { testID?: string; }; +type DialogActionsProps = Omit & { label: string }; + +type SlotProps = { + /** + * Icon to display above the dialog title. + */ + icon?: IconSource; + /** + * Title of the dialog. + */ + title?: React.ReactNode; + /** + * Content of the dialog. Non-empty strings are rendered as Material 3 + * supporting text. + */ + content: React.ReactNode; + /** + * Action buttons displayed at the bottom of the dialog. + * Keep their order stable between renders. + */ + actions: DialogActionsProps[]; + /** + * Whether to render the content in a `ScrollView` within the dialog scroll + * area. + */ + scrollable?: boolean; + /** + * Props passed to `Dialog.Content` when `scrollable` is not enabled. + */ + contentProps?: Omit; + /** + * Props passed to `Dialog.ScrollArea` when `scrollable` is enabled. + */ + scrollAreaProps?: Omit; + /** + * Props passed to the `ScrollView` when `scrollable` is enabled. + */ + scrollViewProps?: Omit; + children?: never; +}; + +type LegacyProps = { + /** + * Content of the `Dialog` composed with `Dialog.Icon`, `Dialog.Title`, + * `Dialog.Content`, `Dialog.ScrollArea`, and `Dialog.Actions`. + * + * @deprecated Use the `icon`, `title`, `content`, `actions`, and `scrollable` + * props instead. Compound children remain available for custom composition. + */ + children: React.ReactNode; + icon?: never; + title?: never; + content?: never; + actions?: never; + scrollable?: never; + contentProps?: never; + scrollAreaProps?: never; + scrollViewProps?: never; +}; + +export type Props = CommonProps & (SlotProps | LegacyProps); + const DIALOG_ELEVATION: number = 24; +const renderChildren = (children: React.ReactNode) => { + const dialogChildren = React.Children.toArray(children).filter( + (child) => child != null && typeof child !== 'boolean' + ); + const hasIcon = dialogChildren.some( + (child) => React.isValidElement(child) && child.type === DialogIcon + ); + + return dialogChildren.map((child, i) => { + if (React.isValidElement(child)) { + const topMarginStyle = + i === 0 && child.type !== DialogIcon ? styles.firstChild : undefined; + const titleAlignmentStyle = + hasIcon && child.type === DialogTitle + ? styles.titleWithIcon + : undefined; + + if (topMarginStyle || titleAlignmentStyle) { + return React.cloneElement(child, { + style: [topMarginStyle, child.props.style, titleAlignmentStyle], + }); + } + } + + return child; + }); +}; + /** * Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks. * To render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component. * + * ## Recommended props + * + * | Prop | Type | Description | + * | --- | --- | --- | + * | `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. | + * | `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. | + * | `content` | `string | ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | + * | `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. | + * | `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. | + * | `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. | + * | `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. | + * | `scrollViewProps` | `ScrollViewProps` | Props forwarded to the generated `ScrollView`. | + * * ## Usage * ```js * import * as React from 'react'; * import { View } from 'react-native'; - * import { Button, Dialog, Portal, PaperProvider, Text } from 'react-native-paper'; + * import { Button, Dialog, Portal, PaperProvider } from 'react-native-paper'; * * const MyComponent = () => { * const [visible, setVisible] = React.useState(false); @@ -70,15 +175,13 @@ const DIALOG_ELEVATION: number = 24; * * * - * - * Alert - * - * This is simple dialog - * - * - * - * - * + * * * * @@ -87,6 +190,32 @@ const DIALOG_ELEVATION: number = 24; * * export default MyComponent; * ``` + * + * ## Compound composition + * + * `Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and + * `Dialog.Actions` remain available for custom composition within `Dialog`. + * Passing them through `children` is deprecated; prefer the props above. + * + * ## Migrating from children + * + * ```js + * // Before + * + * Alert + * Something happened. + * + * + * + * // After + * + * ``` */ const Dialog = ({ children, @@ -97,18 +226,82 @@ const Dialog = ({ style, theme: themeOverrides, testID, + ...props }: Props) => { const { right, left } = useSafeAreaInsets(); const theme = useInternalTheme(themeOverrides); const borderRadius = theme.shapes.corner.extraLarge; const backgroundColor = theme.colors.surfaceContainerHigh; - const dialogChildren = React.Children.toArray(children).filter( - (child) => child != null && typeof child !== 'boolean' - ); - const hasIcon = dialogChildren.some( - (child) => React.isValidElement(child) && child.type === DialogIcon - ); + + const _children = React.useMemo(() => { + if (children) return children; + + const { + actions, + content, + icon, + scrollable, + contentProps, + scrollAreaProps, + scrollViewProps, + title, + } = props; + + const dialogIcon = icon ? ( + + ) : null; + const dialogTitle = title ? {title} : null; + + const contentNode = + typeof content === 'string' ? ( + + {content} + + ) : ( + content + ); + + const dialogContent = scrollable ? ( + + {contentNode} + + ) : ( + + {contentNode} + + ); + + const dialogActions = actions?.length ? ( + + {actions.map( + ({ label, onPress: onActionPress, ...buttonProps }, index) => ( + + ) + )} + + ) : null; + + return [dialogIcon, dialogTitle, dialogContent, dialogActions]; + }, [children, props, theme.colors.onSurfaceVariant]); return ( - {dialogChildren.map((child, i) => { - if (React.isValidElement(child)) { - const topMarginStyle = - i === 0 && child.type !== DialogIcon - ? { marginTop: 24 } - : undefined; - const titleAlignmentStyle = - hasIcon && child.type === DialogTitle - ? styles.titleWithIcon - : undefined; - - if (topMarginStyle || titleAlignmentStyle) { - return React.cloneElement(child, { - style: [topMarginStyle, child.props.style, titleAlignmentStyle], - }); - } - - return child; - } - - return child; - })} + {renderChildren(_children)} ); }; @@ -183,6 +355,9 @@ const styles = StyleSheet.create({ titleWithIcon: { textAlign: 'center', }, + firstChild: { + marginTop: 24, + }, }); export default Dialog; From ab72a9c2020fe17faf64503aadf9df6104da36b6 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:55:03 +0200 Subject: [PATCH 03/13] refactor: added new test cases for new Dialog API --- src/components/Dialog/DialogIcon.tsx | 2 +- src/components/__tests__/Dialog.test.tsx | 47 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/components/Dialog/DialogIcon.tsx b/src/components/Dialog/DialogIcon.tsx index 5c533f877e..d3e5abcfab 100644 --- a/src/components/Dialog/DialogIcon.tsx +++ b/src/components/Dialog/DialogIcon.tsx @@ -64,7 +64,7 @@ const DialogIcon = ({ const theme = useInternalTheme(themeOverrides); const { colors } = theme; - //@ts-ignore + // @ts-ignore const iconColor = color || colors.secondary; return ( diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 32477e5fe1..7cfe7af6f6 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -107,6 +107,53 @@ describe('Dialog', () => { marginTop: 24, }); }); + + it('should render a content', async () => { + await render( + + ); + + expect(screen.getByText('Content')).toBeOnTheScreen(); + }); + + it('should render string content in a scroll area', async () => { + await render( + + ); + + expect(screen.getByTestId('dialog-scroll-area')).toBeOnTheScreen(); + expect(screen.getByTestId('dialog-scroll-view')).toBeOnTheScreen(); + expect(screen.getByText('Scrollable content')).toBeOnTheScreen(); + }); + + it('should render passed action', async () => { + await render( + + ); + + expect(screen.getByTestId('cancel-btn')).toBeOnTheScreen(); + }); }); describe('DialogActions', () => { From 4b92272f46e372fe94f45b4db2330e4063f9dbaf Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:56:44 +0200 Subject: [PATCH 04/13] refactor: updated Examples, duplicated dialogs using new Dialog API --- example/src/Examples/DialogExample.tsx | 96 ++++++++++++- .../Dialogs/NewDialogWithCustomColors.tsx | 47 +++++++ .../NewDialogWithDismissableBackButton.tsx | 31 +++++ .../Examples/Dialogs/NewDialogWithIcon.tsx | 29 ++++ .../Dialogs/NewDialogWithLoadingIndicator.tsx | 51 +++++++ .../Dialogs/NewDialogWithLongText.tsx | 38 ++++++ .../Dialogs/NewDialogWithRadioBtns.tsx | 126 ++++++++++++++++++ .../Dialogs/NewUndismissableDialog.tsx | 30 +++++ example/src/Examples/Dialogs/index.tsx | 7 + 9 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx create mode 100644 example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx create mode 100644 example/src/Examples/Dialogs/NewDialogWithIcon.tsx create mode 100644 example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx create mode 100644 example/src/Examples/Dialogs/NewDialogWithLongText.tsx create mode 100644 example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx create mode 100644 example/src/Examples/Dialogs/NewUndismissableDialog.tsx diff --git a/example/src/Examples/DialogExample.tsx b/example/src/Examples/DialogExample.tsx index d926ae6616..1c7e1b6686 100644 --- a/example/src/Examples/DialogExample.tsx +++ b/example/src/Examples/DialogExample.tsx @@ -10,6 +10,13 @@ import { DialogWithLoadingIndicator, DialogWithLongText, DialogWithRadioBtns, + NewDialogWithCustomColors, + NewDialogWithDismissableBackButton, + NewDialogWithIcon, + NewDialogWithLoadingIndicator, + NewDialogWithLongText, + NewDialogWithRadioBtns, + NewUndismissableDialog, UndismissableDialog, } from './Dialogs'; import ScreenWrapper from '../ScreenWrapper'; @@ -79,6 +86,57 @@ const DialogExample = () => { Dismissable back button )} + + + + + + + {Platform.OS === 'android' && ( + + )} { visible={_getVisible('dialog6')} close={_toggleDialog('dialog6')} /> - + )} + + + + + + + {Platform.OS === 'android' && ( + + )} ); }; diff --git a/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx b/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx new file mode 100644 index 0000000000..f7b700e43c --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx @@ -0,0 +1,47 @@ +import { StyleSheet } from 'react-native'; + +import { Dialog, Palette, Portal, Text } from 'react-native-paper'; + +const NewDialogWithCustomColors = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => ( + + + Alert + + } + content={ + + This is a dialog with custom colors + + } + actions={[ + { + label: 'Ok', + onPress: close, + textColor: Palette.primary95, + }, + ]} + /> + +); + +const styles = StyleSheet.create({ + dialog: { + backgroundColor: Palette.primary10, + }, + text: { + color: Palette.primary95, + }, +}); + +export default NewDialogWithCustomColors; diff --git a/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx b/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx new file mode 100644 index 0000000000..c53c10c0c2 --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx @@ -0,0 +1,31 @@ +import { Dialog, Palette, Portal } from 'react-native-paper'; + +const NewDialogWithDismissableBackButton = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => ( + + + +); + +export default NewDialogWithDismissableBackButton; diff --git a/example/src/Examples/Dialogs/NewDialogWithIcon.tsx b/example/src/Examples/Dialogs/NewDialogWithIcon.tsx new file mode 100644 index 0000000000..640fe244e1 --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithIcon.tsx @@ -0,0 +1,29 @@ +import { Dialog, Palette, Portal } from 'react-native-paper'; + +const NewDialogWithIcon = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => ( + + + +); + +export default NewDialogWithIcon; diff --git a/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx b/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx new file mode 100644 index 0000000000..ea7aaa9279 --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx @@ -0,0 +1,51 @@ +import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native'; + +import { Dialog, Palette, Portal, Text, useTheme } from 'react-native-paper'; + +const isIOS = Platform.OS === 'ios'; + +const NewDialogWithLoadingIndicator = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => { + const theme = useTheme(); + const textColor = { color: theme.colors.onSurfaceVariant }; + + return ( + + + + + Loading..... + + + } + actions={[]} + /> + + ); +}; + +const styles = StyleSheet.create({ + content: { + flexDirection: 'row', + alignItems: 'center', + }, + indicator: { + marginRight: 16, + }, +}); + +export default NewDialogWithLoadingIndicator; diff --git a/example/src/Examples/Dialogs/NewDialogWithLongText.tsx b/example/src/Examples/Dialogs/NewDialogWithLongText.tsx new file mode 100644 index 0000000000..6a5a76f5f8 --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithLongText.tsx @@ -0,0 +1,38 @@ +import { Dimensions, StyleSheet } from 'react-native'; + +import { Portal, Dialog } from 'react-native-paper'; + +const NewDialogWithLongText = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => ( + + + +); + +const styles = StyleSheet.create({ + scrollArea: { + paddingHorizontal: 0, + }, + scrollViewContent: { + paddingHorizontal: 24, + }, +}); + +export default NewDialogWithLongText; diff --git a/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx b/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx new file mode 100644 index 0000000000..01e8a35f26 --- /dev/null +++ b/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import { View, StyleSheet } from 'react-native'; + +import { + Portal, + Dialog, + RadioButton, + Text, + TouchableRipple, + useTheme, +} from 'react-native-paper'; + +type Props = { + visible: boolean; + close: () => void; +}; + +type CheckedState = 'normal' | 'first' | 'second' | 'third' | 'fourth'; + +const NewDialogWithRadioBtns = ({ visible, close }: Props) => { + const [checked, setChecked] = React.useState('normal'); + const theme = useTheme(); + const optionTextColor = { color: theme.colors.onSurfaceVariant }; + + return ( + + + setChecked('normal')}> + + + + + + Option 1 + + + + setChecked('second')}> + + + + + + Option 2 + + + + setChecked('third')}> + + + + + + Option 3 + + + + setChecked('fourth')}> + + + + + + Option 4 + + + + + } + actions={[ + { label: 'Cancel', onPress: close }, + { label: 'Ok', onPress: close }, + ]} + /> + + ); +}; + +export default NewDialogWithRadioBtns; + +const styles = StyleSheet.create({ + container: { + maxHeight: 170, + paddingHorizontal: 0, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 8, + }, + text: { + paddingLeft: 8, + }, +}); diff --git a/example/src/Examples/Dialogs/NewUndismissableDialog.tsx b/example/src/Examples/Dialogs/NewUndismissableDialog.tsx new file mode 100644 index 0000000000..ff0e984cab --- /dev/null +++ b/example/src/Examples/Dialogs/NewUndismissableDialog.tsx @@ -0,0 +1,30 @@ +import { Dialog, Palette, Portal } from 'react-native-paper'; + +const NewUndismissableDialog = ({ + visible, + close, +}: { + visible: boolean; + close: () => void; +}) => ( + + + +); + +export default NewUndismissableDialog; diff --git a/example/src/Examples/Dialogs/index.tsx b/example/src/Examples/Dialogs/index.tsx index 7af735d036..9b320a67fc 100644 --- a/example/src/Examples/Dialogs/index.tsx +++ b/example/src/Examples/Dialogs/index.tsx @@ -5,3 +5,10 @@ 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 NewDialogWithCustomColors } from './NewDialogWithCustomColors'; +export { default as NewDialogWithDismissableBackButton } from './NewDialogWithDismissableBackButton'; +export { default as NewDialogWithIcon } from './NewDialogWithIcon'; +export { default as NewDialogWithLoadingIndicator } from './NewDialogWithLoadingIndicator'; +export { default as NewDialogWithLongText } from './NewDialogWithLongText'; +export { default as NewDialogWithRadioBtns } from './NewDialogWithRadioBtns'; +export { default as NewUndismissableDialog } from './NewUndismissableDialog'; From bc47bf4d54fbffcf0fafabb500ab2a29f49fcbaa Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 12:01:09 +0200 Subject: [PATCH 05/13] docs: updated docs for Dialog compound components --- src/components/Dialog/Dialog.tsx | 4 ++-- src/components/Dialog/DialogActions.tsx | 15 +++++++++++++++ src/components/Dialog/DialogContent.tsx | 8 ++++++++ src/components/Dialog/DialogIcon.tsx | 8 ++++++++ src/components/Dialog/DialogScrollArea.tsx | 8 ++++++++ src/components/Dialog/DialogTitle.tsx | 8 ++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index cce05e9a47..561f70f035 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -150,12 +150,12 @@ const renderChildren = (children: React.ReactNode) => { * | --- | --- | --- | * | `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. | * | `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. | - * | `content` | `string | ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | + * | `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | * | `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. | * | `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. | * | `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. | * | `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. | - * | `scrollViewProps` | `ScrollViewProps` | Props forwarded to the generated `ScrollView`. | + * | `scrollViewProps` | `ScrollViewProps` | Props forwarded to the `ScrollView`. | * * ## Usage * ```js diff --git a/src/components/Dialog/DialogActions.tsx b/src/components/Dialog/DialogActions.tsx index 7e3799451e..10bf16a69b 100644 --- a/src/components/Dialog/DialogActions.tsx +++ b/src/components/Dialog/DialogActions.tsx @@ -31,6 +31,7 @@ export type Props = ViewProps & { * * const hideDialog = () => setVisible(false); * + * // Before * return ( * * @@ -41,6 +42,20 @@ export type Props = ViewProps & { * * * ); + * + * // V6 and later + * return ( + * + * console.log('Cancel'), label: 'Cancel' }, + * { onPress: () => console.log('Ok'), label: 'Ok' }, + * ]} + * /> + * + * ); * }; * * export default MyComponent; diff --git a/src/components/Dialog/DialogContent.tsx b/src/components/Dialog/DialogContent.tsx index a084188b32..fe953396a7 100644 --- a/src/components/Dialog/DialogContent.tsx +++ b/src/components/Dialog/DialogContent.tsx @@ -23,6 +23,7 @@ export type Props = ViewProps & { * * const hideDialog = () => setVisible(false); * + * // Before * return ( * * @@ -32,6 +33,13 @@ export type Props = ViewProps & { * * * ); + * + * // V6 and later + * return ( + * + * + * + * ); * }; * * export default MyComponent; diff --git a/src/components/Dialog/DialogIcon.tsx b/src/components/Dialog/DialogIcon.tsx index d3e5abcfab..a016f69995 100644 --- a/src/components/Dialog/DialogIcon.tsx +++ b/src/components/Dialog/DialogIcon.tsx @@ -39,6 +39,7 @@ export type Props = { * * const hideDialog = () => setVisible(false); * + * // Before * return ( * * @@ -50,6 +51,13 @@ export type Props = { * * * ); + * + * // V6 and later + * return ( + * + * + * + * ); * }; * * export default MyComponent; diff --git a/src/components/Dialog/DialogScrollArea.tsx b/src/components/Dialog/DialogScrollArea.tsx index 5418504783..2c552b6f32 100644 --- a/src/components/Dialog/DialogScrollArea.tsx +++ b/src/components/Dialog/DialogScrollArea.tsx @@ -32,6 +32,7 @@ export type Props = ViewProps & { * * const hideDialog = () => setVisible(false); * + * // Before * return ( * * @@ -43,6 +44,13 @@ export type Props = ViewProps & { * * * ); + * + * // V6 and later + * return ( + * + * + * + * ); * }; * * export default MyComponent; diff --git a/src/components/Dialog/DialogTitle.tsx b/src/components/Dialog/DialogTitle.tsx index ae553daee2..2a2acb5c1a 100644 --- a/src/components/Dialog/DialogTitle.tsx +++ b/src/components/Dialog/DialogTitle.tsx @@ -31,6 +31,7 @@ export type Props = React.ComponentPropsWithRef & { * * const hideDialog = () => setVisible(false); * + * // Before * return ( * * @@ -41,6 +42,13 @@ export type Props = React.ComponentPropsWithRef & { * * * ); + * + * // V6 and later + * return ( + * + * + * + * ); * }; * * export default MyComponent; From 34aa053e088a9051541e93b2bf585c039f3af692 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 27 Aug 2026 13:11:05 +0200 Subject: [PATCH 06/13] refactor: removed children from Dialog, updated Example, updated tests --- example/src/DrawerItems.tsx | 17 +- example/src/Examples/DialogExample.tsx | 88 ----------- .../Examples/Dialogs/DialogTextComponent.tsx | 18 --- .../Dialogs/DialogWithCustomColors.tsx | 64 ++++---- .../DialogWithDismissableBackButton.tsx | 31 ++-- .../src/Examples/Dialogs/DialogWithIcon.tsx | 45 +++--- .../Dialogs/DialogWithLoadingIndicator.tsx | 31 ++-- .../Examples/Dialogs/DialogWithLongText.tsx | 68 ++------ .../Examples/Dialogs/DialogWithRadioBtns.tsx | 145 ++++++++++-------- .../Dialogs/NewDialogWithCustomColors.tsx | 47 ------ .../NewDialogWithDismissableBackButton.tsx | 31 ---- .../Examples/Dialogs/NewDialogWithIcon.tsx | 29 ---- .../Dialogs/NewDialogWithLoadingIndicator.tsx | 51 ------ .../Dialogs/NewDialogWithLongText.tsx | 38 ----- .../Dialogs/NewDialogWithRadioBtns.tsx | 126 --------------- .../Dialogs/NewUndismissableDialog.tsx | 30 ---- .../Examples/Dialogs/UndismissableDialog.tsx | 32 ++-- example/src/Examples/Dialogs/index.tsx | 7 - src/components/Dialog/Dialog.tsx | 122 ++++----------- src/components/Dialog/DialogActions.tsx | 13 -- src/components/Dialog/DialogContent.tsx | 12 -- src/components/Dialog/DialogIcon.tsx | 14 -- src/components/Dialog/DialogScrollArea.tsx | 14 -- src/components/__tests__/Dialog.test.tsx | 121 ++++++++------- 24 files changed, 298 insertions(+), 896 deletions(-) delete mode 100644 example/src/Examples/Dialogs/DialogTextComponent.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithIcon.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithLongText.tsx delete mode 100644 example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx delete mode 100644 example/src/Examples/Dialogs/NewUndismissableDialog.tsx diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx index 94afa3136c..733d99cfac 100644 --- a/example/src/DrawerItems.tsx +++ b/example/src/DrawerItems.tsx @@ -6,7 +6,6 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import Constants, { ExecutionEnvironment } from 'expo-constants'; import { Badge, - Button, Dialog, Drawer, Palette, @@ -242,9 +241,11 @@ function DrawerItems() { )} - - Changing to RTL - + Due to Expo Go limitations it is impossible to change RTL dynamically. To do so, you need to create a development build of @@ -253,11 +254,9 @@ function DrawerItems() { app.json within{' '} example directory. - - - - - + } + actions={[{ onPress: _handleDismissRTLDialog, label: 'Ok' }]} + /> ); diff --git a/example/src/Examples/DialogExample.tsx b/example/src/Examples/DialogExample.tsx index 1c7e1b6686..4628d5944d 100644 --- a/example/src/Examples/DialogExample.tsx +++ b/example/src/Examples/DialogExample.tsx @@ -10,13 +10,6 @@ import { DialogWithLoadingIndicator, DialogWithLongText, DialogWithRadioBtns, - NewDialogWithCustomColors, - NewDialogWithDismissableBackButton, - NewDialogWithIcon, - NewDialogWithLoadingIndicator, - NewDialogWithLongText, - NewDialogWithRadioBtns, - NewUndismissableDialog, UndismissableDialog, } from './Dialogs'; import ScreenWrapper from '../ScreenWrapper'; @@ -86,57 +79,6 @@ const DialogExample = () => { Dismissable back button )} - - - - - - - {Platform.OS === 'android' && ( - - )} { close={_toggleDialog('dialog7')} /> )} - - - - - - - {Platform.OS === 'android' && ( - - )} ); }; diff --git a/example/src/Examples/Dialogs/DialogTextComponent.tsx b/example/src/Examples/Dialogs/DialogTextComponent.tsx deleted file mode 100644 index 9ad958f12e..0000000000 --- a/example/src/Examples/Dialogs/DialogTextComponent.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; - -import { Text, Text as NativeText, useTheme } from 'react-native-paper'; -type Props = React.ComponentProps & { - isSubheading?: boolean; -}; - -export const TextComponent = ({ isSubheading = false, ...props }: Props) => { - const theme = useTheme(); - - return ( - - ); -}; diff --git a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx index f3625b835e..b24a0d6dac 100644 --- a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx +++ b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx @@ -1,6 +1,6 @@ -import { Button, Portal, Dialog, Palette } from 'react-native-paper'; +import { StyleSheet } from 'react-native'; -import { TextComponent } from './DialogTextComponent'; +import { Dialog, Palette, Portal, Text } from 'react-native-paper'; const DialogWithCustomColors = ({ visible, @@ -8,30 +8,40 @@ const DialogWithCustomColors = ({ }: { visible: boolean; close: () => void; -}) => { - return ( - - - Alert - - - This is a dialog with custom colors - - - - - - - - ); -}; +}) => ( + + + Alert + + } + content={ + + This is a dialog with custom colors + + } + actions={[ + { + label: 'Ok', + onPress: close, + textColor: Palette.primary95, + }, + ]} + /> +
+); + +const styles = StyleSheet.create({ + dialog: { + backgroundColor: Palette.primary10, + }, + text: { + color: Palette.primary95, + }, +}); export default DialogWithCustomColors; diff --git a/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx index e9a7189068..825ac73684 100644 --- a/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx +++ b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx @@ -1,6 +1,4 @@ -import { Button, Portal, Dialog, Palette } from 'react-native-paper'; - -import { TextComponent } from './DialogTextComponent'; +import { Dialog, Palette, Portal } from 'react-native-paper'; const DialogWithDismissableBackButton = ({ visible, @@ -15,21 +13,18 @@ const DialogWithDismissableBackButton = ({ visible={visible} dismissable={false} dismissableBackButton - > - Alert - - - This is an undismissable dialog, however you can use hardware back - button to close it! - - - - - - -
+ title="Alert" + content="This is an undismissable dialog, however you can use hardware back button to close it!" + actions={[ + { + label: 'Disagree', + onPress: close, + disabled: true, + textColor: Palette.tertiary50, + }, + { label: 'Agree', onPress: close }, + ]} + />
); diff --git a/example/src/Examples/Dialogs/DialogWithIcon.tsx b/example/src/Examples/Dialogs/DialogWithIcon.tsx index 9a5b6477c1..3ab41285e1 100644 --- a/example/src/Examples/Dialogs/DialogWithIcon.tsx +++ b/example/src/Examples/Dialogs/DialogWithIcon.tsx @@ -1,6 +1,4 @@ -import { Button, Portal, Dialog, Palette } from 'react-native-paper'; - -import { TextComponent } from './DialogTextComponent'; +import { Dialog, Palette, Portal } from 'react-native-paper'; const DialogWithIcon = ({ visible, @@ -8,27 +6,24 @@ const DialogWithIcon = ({ }: { visible: boolean; close: () => void; -}) => { - return ( - - - - Dialog with Icon - - - This is a dialog with a component called DialogIcon. When the icon - is displayed, the title is centered automatically. - - - - - - - - - ); -}; +}) => ( + + + +); export default DialogWithIcon; diff --git a/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx b/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx index a28620e79c..892d24bed2 100644 --- a/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx +++ b/example/src/Examples/Dialogs/DialogWithLoadingIndicator.tsx @@ -1,8 +1,6 @@ import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native'; -import { Dialog, Palette, Portal } from 'react-native-paper'; - -import { TextComponent } from './DialogTextComponent'; +import { Dialog, Palette, Portal, Text, useTheme } from 'react-native-paper'; const isIOS = Platform.OS === 'ios'; @@ -13,31 +11,38 @@ const DialogWithLoadingIndicator = ({ visible: boolean; close: () => void; }) => { + const theme = useTheme(); + const textColor = { color: theme.colors.onSurfaceVariant }; + return ( - - Progress Dialog - - + - Loading..... + + Loading..... + - - + } + /> ); }; const styles = StyleSheet.create({ - flexing: { + content: { flexDirection: 'row', alignItems: 'center', }, - marginRight: { + indicator: { marginRight: 16, }, }); diff --git a/example/src/Examples/Dialogs/DialogWithLongText.tsx b/example/src/Examples/Dialogs/DialogWithLongText.tsx index eeac1c7d3c..8001ee1dcc 100644 --- a/example/src/Examples/Dialogs/DialogWithLongText.tsx +++ b/example/src/Examples/Dialogs/DialogWithLongText.tsx @@ -1,8 +1,6 @@ -import { Dimensions, ScrollView, StyleSheet } from 'react-native'; +import { Dimensions, StyleSheet } from 'react-native'; -import { Button, Portal, Dialog } from 'react-native-paper'; - -import { TextComponent } from './DialogTextComponent'; +import { Portal, Dialog } from 'react-native-paper'; const DialogWithLongText = ({ visible, @@ -16,63 +14,23 @@ const DialogWithLongText = ({ onDismiss={close} visible={visible} style={{ maxHeight: 0.6 * Dimensions.get('window').height }} - > - Alert - - - - Material is the metaphor - {'\n'} - {'\n'}A material metaphor is the unifying theory of a rationalized - space and a system of motion. The material is grounded in tactile - reality, inspired by the study of paper and ink, yet technologically - advanced and open to imagination and magic. - {'\n'} - {'\n'} - Surfaces and edges of the material provide visual cues that are - grounded in reality. The use of familiar tactile attributes helps - users quickly understand affordances. Yet the flexibility of the - material creates new affordances that supersede those in the - physical world, without breaking the rules of physics. - {'\n'} - {'\n'} - The fundamentals of light, surface, and movement are key to - conveying how objects move, interact, and exist in space and in - relation to each other. Realistic lighting shows seams, divides - space, and indicates moving parts. - {'\n'} - {'\n'}A material metaphor is the unifying theory of a rationalized - space and a system of motion. The material is grounded in tactile - reality, inspired by the study of paper and ink, yet technologically - advanced and open to imagination and magic. - {'\n'} - {'\n'} - Surfaces and edges of the material provide visual cues that are - grounded in reality. The use of familiar tactile attributes helps - users quickly understand affordances. Yet the flexibility of the - material creates new affordances that supersede those in the - physical world, without breaking the rules of physics. - {'\n'} - {'\n'} - The fundamentals of light, surface, and movement are key to - conveying how objects move, interact, and exist in space and in - relation to each other. Realistic lighting shows seams, divides - space, and indicates moving parts. - - - - - - -
+ title="Alert" + scrollable + scrollAreaProps={{ style: styles.scrollArea }} + scrollViewProps={{ contentContainerStyle: styles.scrollViewContent }} + content={ + 'Material is the metaphor\n\nA material metaphor is the unifying theory of a rationalized space and a system of motion. The material is grounded in tactile reality, inspired by the study of paper and ink, yet technologically advanced and open to imagination and magic.\n\nSurfaces and edges of the material provide visual cues that are grounded in reality. The use of familiar tactile attributes helps users quickly understand affordances. Yet the flexibility of the material creates new affordances that supersede those in the physical world, without breaking the rules of physics.\n\nThe fundamentals of light, surface, and movement are key to conveying how objects move, interact, and exist in space and in relation to each other. Realistic lighting shows seams, divides space, and indicates moving parts.\n\nA material metaphor is the unifying theory of a rationalized space and a system of motion. The material is grounded in tactile reality, inspired by the study of paper and ink, yet technologically advanced and open to imagination and magic.\n\nSurfaces and edges of the material provide visual cues that are grounded in reality. The use of familiar tactile attributes helps users quickly understand affordances. Yet the flexibility of the material creates new affordances that supersede those in the physical world, without breaking the rules of physics.\n\nThe fundamentals of light, surface, and movement are key to conveying how objects move, interact, and exist in space and in relation to each other. Realistic lighting shows seams, divides space, and indicates moving parts.' + } + actions={[{ onPress: close, label: 'Ok' }]} + />
); const styles = StyleSheet.create({ - smallPadding: { + scrollArea: { paddingHorizontal: 0, }, - biggerPadding: { + scrollViewContent: { paddingHorizontal: 24, }, }); diff --git a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx index 966422369e..3e9402656c 100644 --- a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx +++ b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx @@ -1,16 +1,15 @@ import * as React from 'react'; -import { ScrollView, View, StyleSheet } from 'react-native'; +import { View, StyleSheet } from 'react-native'; import { - Button, Portal, Dialog, RadioButton, + Text, TouchableRipple, + useTheme, } from 'react-native-paper'; -import { TextComponent } from './DialogTextComponent'; - type Props = { visible: boolean; close: () => void; @@ -20,74 +19,90 @@ type CheckedState = 'normal' | 'first' | 'second' | 'third' | 'fourth'; const DialogWithRadioBtns = ({ visible, close }: Props) => { const [checked, setChecked] = React.useState('normal'); + const theme = useTheme(); + const optionTextColor = { color: theme.colors.onSurfaceVariant }; return ( - - Choose an option - - - - setChecked('normal')}> - - - - - - Option 1 - + + setChecked('normal')}> + + + - - setChecked('second')}> - - - - - - Option 2 - + + Option 1 + + + + setChecked('second')}> + + + - - setChecked('third')}> - - - - - - Option 3 - + + Option 2 + + + + setChecked('third')}> + + + - - setChecked('fourth')}> - - - - - - Option 4 - + + Option 3 + + + + setChecked('fourth')}> + + + - - - - - - - - - + + Option 4 + + + + + } + actions={[ + { label: 'Cancel', onPress: close }, + { label: 'Ok', onPress: close }, + ]} + /> ); }; diff --git a/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx b/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx deleted file mode 100644 index f7b700e43c..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithCustomColors.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { StyleSheet } from 'react-native'; - -import { Dialog, Palette, Portal, Text } from 'react-native-paper'; - -const NewDialogWithCustomColors = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => ( - - - Alert - - } - content={ - - This is a dialog with custom colors - - } - actions={[ - { - label: 'Ok', - onPress: close, - textColor: Palette.primary95, - }, - ]} - /> - -); - -const styles = StyleSheet.create({ - dialog: { - backgroundColor: Palette.primary10, - }, - text: { - color: Palette.primary95, - }, -}); - -export default NewDialogWithCustomColors; diff --git a/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx b/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx deleted file mode 100644 index c53c10c0c2..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithDismissableBackButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; - -const NewDialogWithDismissableBackButton = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => ( - - - -); - -export default NewDialogWithDismissableBackButton; diff --git a/example/src/Examples/Dialogs/NewDialogWithIcon.tsx b/example/src/Examples/Dialogs/NewDialogWithIcon.tsx deleted file mode 100644 index 640fe244e1..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithIcon.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; - -const NewDialogWithIcon = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => ( - - - -); - -export default NewDialogWithIcon; diff --git a/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx b/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx deleted file mode 100644 index ea7aaa9279..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithLoadingIndicator.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native'; - -import { Dialog, Palette, Portal, Text, useTheme } from 'react-native-paper'; - -const isIOS = Platform.OS === 'ios'; - -const NewDialogWithLoadingIndicator = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => { - const theme = useTheme(); - const textColor = { color: theme.colors.onSurfaceVariant }; - - return ( - - - - - Loading..... - - - } - actions={[]} - /> - - ); -}; - -const styles = StyleSheet.create({ - content: { - flexDirection: 'row', - alignItems: 'center', - }, - indicator: { - marginRight: 16, - }, -}); - -export default NewDialogWithLoadingIndicator; diff --git a/example/src/Examples/Dialogs/NewDialogWithLongText.tsx b/example/src/Examples/Dialogs/NewDialogWithLongText.tsx deleted file mode 100644 index 6a5a76f5f8..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithLongText.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Dimensions, StyleSheet } from 'react-native'; - -import { Portal, Dialog } from 'react-native-paper'; - -const NewDialogWithLongText = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => ( - - - -); - -const styles = StyleSheet.create({ - scrollArea: { - paddingHorizontal: 0, - }, - scrollViewContent: { - paddingHorizontal: 24, - }, -}); - -export default NewDialogWithLongText; diff --git a/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx b/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx deleted file mode 100644 index 01e8a35f26..0000000000 --- a/example/src/Examples/Dialogs/NewDialogWithRadioBtns.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import * as React from 'react'; -import { View, StyleSheet } from 'react-native'; - -import { - Portal, - Dialog, - RadioButton, - Text, - TouchableRipple, - useTheme, -} from 'react-native-paper'; - -type Props = { - visible: boolean; - close: () => void; -}; - -type CheckedState = 'normal' | 'first' | 'second' | 'third' | 'fourth'; - -const NewDialogWithRadioBtns = ({ visible, close }: Props) => { - const [checked, setChecked] = React.useState('normal'); - const theme = useTheme(); - const optionTextColor = { color: theme.colors.onSurfaceVariant }; - - return ( - - - setChecked('normal')}> - - - - - - Option 1 - - - - setChecked('second')}> - - - - - - Option 2 - - - - setChecked('third')}> - - - - - - Option 3 - - - - setChecked('fourth')}> - - - - - - Option 4 - - - - - } - actions={[ - { label: 'Cancel', onPress: close }, - { label: 'Ok', onPress: close }, - ]} - /> - - ); -}; - -export default NewDialogWithRadioBtns; - -const styles = StyleSheet.create({ - container: { - maxHeight: 170, - paddingHorizontal: 0, - }, - row: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 8, - }, - text: { - paddingLeft: 8, - }, -}); diff --git a/example/src/Examples/Dialogs/NewUndismissableDialog.tsx b/example/src/Examples/Dialogs/NewUndismissableDialog.tsx deleted file mode 100644 index ff0e984cab..0000000000 --- a/example/src/Examples/Dialogs/NewUndismissableDialog.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; - -const NewUndismissableDialog = ({ - visible, - close, -}: { - visible: boolean; - close: () => void; -}) => ( - - - -); - -export default NewUndismissableDialog; diff --git a/example/src/Examples/Dialogs/UndismissableDialog.tsx b/example/src/Examples/Dialogs/UndismissableDialog.tsx index 208c809967..6d5fbd0d6a 100644 --- a/example/src/Examples/Dialogs/UndismissableDialog.tsx +++ b/example/src/Examples/Dialogs/UndismissableDialog.tsx @@ -1,6 +1,4 @@ -import { Button, Portal, Dialog, Palette } from 'react-native-paper'; - -import { TextComponent } from './DialogTextComponent'; +import { Dialog, Palette, Portal } from 'react-native-paper'; const UndismissableDialog = ({ visible, @@ -10,18 +8,22 @@ const UndismissableDialog = ({ close: () => void; }) => ( - - Alert - - This is an undismissable dialog!! - - - - - - + ); diff --git a/example/src/Examples/Dialogs/index.tsx b/example/src/Examples/Dialogs/index.tsx index 9b320a67fc..7af735d036 100644 --- a/example/src/Examples/Dialogs/index.tsx +++ b/example/src/Examples/Dialogs/index.tsx @@ -5,10 +5,3 @@ 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 NewDialogWithCustomColors } from './NewDialogWithCustomColors'; -export { default as NewDialogWithDismissableBackButton } from './NewDialogWithDismissableBackButton'; -export { default as NewDialogWithIcon } from './NewDialogWithIcon'; -export { default as NewDialogWithLoadingIndicator } from './NewDialogWithLoadingIndicator'; -export { default as NewDialogWithLongText } from './NewDialogWithLongText'; -export { default as NewDialogWithRadioBtns } from './NewDialogWithRadioBtns'; -export { default as NewUndismissableDialog } from './NewUndismissableDialog'; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 561f70f035..df1b0ed5ea 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -20,7 +20,7 @@ import type { IconSource } from '../Icon'; import Modal from '../Modal'; import Text from '../Typography/Text'; -type CommonProps = { +export type Props = { /** * Determines whether clicking outside the dialog dismiss it. */ @@ -37,20 +37,6 @@ type CommonProps = { * Determines Whether the dialog is visible. */ visible: boolean; - style?: Animated.WithAnimatedValue>; - /** - * @optional - */ - theme?: ThemeProp; - /** - * testID to be used on tests. - */ - testID?: string; -}; - -type DialogActionsProps = Omit & { label: string }; - -type SlotProps = { /** * Icon to display above the dialog title. */ @@ -68,7 +54,7 @@ type SlotProps = { * Action buttons displayed at the bottom of the dialog. * Keep their order stable between renders. */ - actions: DialogActionsProps[]; + actions?: DialogActionsProps[]; /** * Whether to render the content in a `ScrollView` within the dialog scroll * area. @@ -86,31 +72,18 @@ type SlotProps = { * Props passed to the `ScrollView` when `scrollable` is enabled. */ scrollViewProps?: Omit; - children?: never; -}; - -type LegacyProps = { + style?: Animated.WithAnimatedValue>; /** - * Content of the `Dialog` composed with `Dialog.Icon`, `Dialog.Title`, - * `Dialog.Content`, `Dialog.ScrollArea`, and `Dialog.Actions`. - * - * @deprecated Use the `icon`, `title`, `content`, `actions`, and `scrollable` - * props instead. Compound children remain available for custom composition. + * @optional */ - children: React.ReactNode; - icon?: never; - title?: never; - content?: never; - actions?: never; - scrollable?: never; - contentProps?: never; - scrollAreaProps?: never; - scrollViewProps?: never; + theme?: ThemeProp; + /** + * testID to be used on tests. + */ + testID?: string; }; -export type Props = CommonProps & (SlotProps | LegacyProps); - -const DIALOG_ELEVATION: number = 24; +type DialogActionsProps = Omit & { label: string }; const renderChildren = (children: React.ReactNode) => { const dialogChildren = React.Children.toArray(children).filter( @@ -144,19 +117,6 @@ const renderChildren = (children: React.ReactNode) => { * Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks. * To render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component. * - * ## Recommended props - * - * | Prop | Type | Description | - * | --- | --- | --- | - * | `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. | - * | `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. | - * | `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | - * | `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. | - * | `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. | - * | `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. | - * | `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. | - * | `scrollViewProps` | `ScrollViewProps` | Props forwarded to the `ScrollView`. | - * * ## Usage * ```js * import * as React from 'react'; @@ -190,35 +150,8 @@ const renderChildren = (children: React.ReactNode) => { * * export default MyComponent; * ``` - * - * ## Compound composition - * - * `Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and - * `Dialog.Actions` remain available for custom composition within `Dialog`. - * Passing them through `children` is deprecated; prefer the props above. - * - * ## Migrating from children - * - * ```js - * // Before - * - * Alert - * Something happened. - * - * - * - * // After - * - * ``` */ const Dialog = ({ - children, dismissable = true, dismissableBackButton = dismissable, onDismiss, @@ -226,7 +159,14 @@ const Dialog = ({ style, theme: themeOverrides, testID, - ...props + actions, + content, + icon, + scrollable, + contentProps, + scrollAreaProps, + scrollViewProps, + title, }: Props) => { const { right, left } = useSafeAreaInsets(); const theme = useInternalTheme(themeOverrides); @@ -235,19 +175,6 @@ const Dialog = ({ const backgroundColor = theme.colors.surfaceContainerHigh; const _children = React.useMemo(() => { - if (children) return children; - - const { - actions, - content, - icon, - scrollable, - contentProps, - scrollAreaProps, - scrollViewProps, - title, - } = props; - const dialogIcon = icon ? ( ) : null; @@ -301,7 +228,17 @@ const Dialog = ({ ) : null; return [dialogIcon, dialogTitle, dialogContent, dialogActions]; - }, [children, props, theme.colors.onSurfaceVariant]); + }, [ + actions, + content, + contentProps, + icon, + scrollAreaProps, + scrollViewProps, + scrollable, + theme.colors.onSurfaceVariant, + title, + ]); return ( setVisible(false); * - * // Before - * return ( - * - * - * - * - * - * - * - * - * ); - * - * // V6 and later * return ( * * setVisible(false); * - * // Before - * return ( - * - * - * - * This is simple dialog - * - * - * - * ); - * - * // V6 and later * return ( * * diff --git a/src/components/Dialog/DialogIcon.tsx b/src/components/Dialog/DialogIcon.tsx index a016f69995..bee3712290 100644 --- a/src/components/Dialog/DialogIcon.tsx +++ b/src/components/Dialog/DialogIcon.tsx @@ -39,20 +39,6 @@ export type Props = { * * const hideDialog = () => setVisible(false); * - * // Before - * return ( - * - * - * - * This is a title - * - * This is simple dialog - * - * - * - * ); - * - * // V6 and later * return ( * * diff --git a/src/components/Dialog/DialogScrollArea.tsx b/src/components/Dialog/DialogScrollArea.tsx index 2c552b6f32..edf050b746 100644 --- a/src/components/Dialog/DialogScrollArea.tsx +++ b/src/components/Dialog/DialogScrollArea.tsx @@ -32,20 +32,6 @@ export type Props = ViewProps & { * * const hideDialog = () => setVisible(false); * - * // Before - * return ( - * - * - * - * - * This is a scrollable area - * - * - * - * - * ); - * - * // V6 and later * return ( * * diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 7cfe7af6f6..0f654800ff 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -11,7 +11,6 @@ import { act, userEvent } from '@testing-library/react-native'; import Dialog from '../../components/Dialog/Dialog'; import { render, screen } from '../../test-utils'; -import Button from '../Button/Button'; interface BackHandlerStatic extends RNBackHandlerStatic { mockPressBack(): void; @@ -21,11 +20,9 @@ interface BackHandlerStatic extends RNBackHandlerStatic { const BackHandler = RNBackHandler as BackHandlerStatic; describe('Dialog', () => { - it('should render passed children', async () => { + it('should render passed content', async () => { await render( - - This is simple dialog - + ); expect(screen.getByTestId('dialog')).toHaveTextContent( @@ -36,9 +33,13 @@ describe('Dialog', () => { it('should call onDismiss when dismissable', async () => { const onDismiss = jest.fn(); await render( - - This is simple dialog - + ); await userEvent.press(screen.getByTestId('dialog-backdrop')); @@ -52,9 +53,13 @@ describe('Dialog', () => { it('should not call onDismiss when dismissable is false', async () => { const onDismiss = jest.fn(); await render( - - This is simple dialog - + ); await userEvent.press(screen.getByTestId('dialog-backdrop')); @@ -75,9 +80,8 @@ describe('Dialog', () => { dismissable={false} dismissableBackButton testID="dialog" - > - This is simple dialog - + content="This is simple dialog" + /> ); await userEvent.press(screen.getByTestId('dialog-backdrop')); @@ -95,32 +99,22 @@ describe('Dialog', () => { }); it('should apply top margin to the first child if the dialog is V3', async () => { - await render( - - - Test Dialog Content - - - ); - - expect(screen.getByTestId('dialog-content')).toHaveStyle({ - marginTop: 24, - }); - }); - - it('should render a content', async () => { await render( This is simple dialog} + content="This is simple dialog" /> ); - expect(screen.getByText('Content')).toBeOnTheScreen(); + const element = screen.getByTestId('dialog-title').parent; + + expect(element).toHaveStyle({ + marginTop: 24, + }); }); - it('should render string content in a scroll area', async () => { + it('should render content in a scroll area', async () => { await render( { await render( { }); describe('DialogActions', () => { - it('should render passed children', async () => { + it('should render passed actions', async () => { await render( - - - - + ); expect(screen.getByTestId('button-cancel')).toBeOnTheScreen(); @@ -171,36 +170,42 @@ describe('DialogActions', () => { it('should apply default styles', async () => { await render( - - - - + ); - const dialogActionsContainer = screen.getByTestId('dialog-actions'); - const dialogActionButtons = dialogActionsContainer.children; + const buttonCancelParent = screen.getByTestId('button-cancel').parent; + const buttonOkParent = screen.getByTestId('button-ok').parent; - expect(dialogActionsContainer).toHaveStyle({ - paddingBottom: 24, - paddingHorizontal: 24, - }); - expect(dialogActionButtons[0]).toHaveStyle({ marginRight: 8 }); - expect(dialogActionButtons[1]).toHaveStyle({ marginRight: 0 }); + expect(buttonCancelParent).toHaveStyle({ marginRight: 8 }); + expect(buttonOkParent).toHaveStyle({ marginRight: 0 }); }); it('should apply custom styles', async () => { await render( - - - - + ); - const dialogActionsContainer = screen.getByTestId('dialog-actions'); - const dialogActionButtons = dialogActionsContainer.children; + const buttonCancel = screen.getByTestId('button-cancel').parent; + const buttonOk = screen.getByTestId('button-ok').parent; - expect(dialogActionButtons[0]).toHaveStyle({ margin: 10 }); - expect(dialogActionButtons[1]).toHaveStyle({ margin: 0 }); + expect(buttonCancel).toHaveStyle({ marginRight: 8 }); + expect(buttonOk).toHaveStyle({ marginRight: 0 }); }); }); From 8ac60f7339e88652f320ff95146e63fd879ebdc5 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 27 Aug 2026 15:52:04 +0200 Subject: [PATCH 07/13] refactor: removed cloneElement from DialogActions, changed actions type --- example/src/DrawerItems.tsx | 7 +- .../Dialogs/DialogWithCustomColors.tsx | 10 ++- .../DialogWithDismissableBackButton.tsx | 19 +++--- .../src/Examples/Dialogs/DialogWithIcon.tsx | 14 ++-- .../Examples/Dialogs/DialogWithLongText.tsx | 8 ++- .../Examples/Dialogs/DialogWithRadioBtns.tsx | 9 ++- .../Examples/Dialogs/UndismissableDialog.tsx | 20 +++--- src/components/Dialog/Dialog.tsx | 23 +------ src/components/Dialog/DialogActions.tsx | 43 +++++++------ src/components/Dialog/DialogScrollArea.tsx | 2 +- src/components/Dialog/utils.ts | 5 -- src/components/__tests__/Dialog.test.tsx | 64 +++++++++---------- 12 files changed, 111 insertions(+), 113 deletions(-) diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx index 733d99cfac..ced8ca108f 100644 --- a/example/src/DrawerItems.tsx +++ b/example/src/DrawerItems.tsx @@ -6,6 +6,7 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import Constants, { ExecutionEnvironment } from 'expo-constants'; import { Badge, + Button, Dialog, Drawer, Palette, @@ -255,7 +256,11 @@ function DrawerItems() { example directory. } - actions={[{ onPress: _handleDismissRTLDialog, label: 'Ok' }]} + actions={[ + , + ]} /> diff --git a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx index b24a0d6dac..3eec815828 100644 --- a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx +++ b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; -import { Dialog, Palette, Portal, Text } from 'react-native-paper'; +import { Button, Dialog, Palette, Portal, Text } from 'react-native-paper'; const DialogWithCustomColors = ({ visible, @@ -25,11 +25,9 @@ const DialogWithCustomColors = ({ } actions={[ - { - label: 'Ok', - onPress: close, - textColor: Palette.primary95, - }, + , ]} /> diff --git a/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx index 825ac73684..fd7663dc79 100644 --- a/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx +++ b/example/src/Examples/Dialogs/DialogWithDismissableBackButton.tsx @@ -1,4 +1,4 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; +import { Button, Dialog, Palette, Portal } from 'react-native-paper'; const DialogWithDismissableBackButton = ({ visible, @@ -16,13 +16,16 @@ const DialogWithDismissableBackButton = ({ title="Alert" content="This is an undismissable dialog, however you can use hardware back button to close it!" actions={[ - { - label: 'Disagree', - onPress: close, - disabled: true, - textColor: Palette.tertiary50, - }, - { label: 'Agree', onPress: close }, + , + , ]} /> diff --git a/example/src/Examples/Dialogs/DialogWithIcon.tsx b/example/src/Examples/Dialogs/DialogWithIcon.tsx index 3ab41285e1..c176946aa4 100644 --- a/example/src/Examples/Dialogs/DialogWithIcon.tsx +++ b/example/src/Examples/Dialogs/DialogWithIcon.tsx @@ -1,4 +1,4 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; +import { Button, Dialog, Palette, Portal } from 'react-native-paper'; const DialogWithIcon = ({ visible, @@ -15,12 +15,12 @@ const DialogWithIcon = ({ title="Dialog with Icon" content="This is a dialog with a component called DialogIcon. When the icon is displayed, the title is centered automatically." actions={[ - { - label: 'Disagree', - onPress: close, - textColor: Palette.error50, - }, - { label: 'Agree', onPress: close }, + , + , ]} /> diff --git a/example/src/Examples/Dialogs/DialogWithLongText.tsx b/example/src/Examples/Dialogs/DialogWithLongText.tsx index 8001ee1dcc..33a6c965d3 100644 --- a/example/src/Examples/Dialogs/DialogWithLongText.tsx +++ b/example/src/Examples/Dialogs/DialogWithLongText.tsx @@ -1,6 +1,6 @@ import { Dimensions, StyleSheet } from 'react-native'; -import { Portal, Dialog } from 'react-native-paper'; +import { Portal, Dialog, Button } from 'react-native-paper'; const DialogWithLongText = ({ visible, @@ -21,7 +21,11 @@ const DialogWithLongText = ({ content={ 'Material is the metaphor\n\nA material metaphor is the unifying theory of a rationalized space and a system of motion. The material is grounded in tactile reality, inspired by the study of paper and ink, yet technologically advanced and open to imagination and magic.\n\nSurfaces and edges of the material provide visual cues that are grounded in reality. The use of familiar tactile attributes helps users quickly understand affordances. Yet the flexibility of the material creates new affordances that supersede those in the physical world, without breaking the rules of physics.\n\nThe fundamentals of light, surface, and movement are key to conveying how objects move, interact, and exist in space and in relation to each other. Realistic lighting shows seams, divides space, and indicates moving parts.\n\nA material metaphor is the unifying theory of a rationalized space and a system of motion. The material is grounded in tactile reality, inspired by the study of paper and ink, yet technologically advanced and open to imagination and magic.\n\nSurfaces and edges of the material provide visual cues that are grounded in reality. The use of familiar tactile attributes helps users quickly understand affordances. Yet the flexibility of the material creates new affordances that supersede those in the physical world, without breaking the rules of physics.\n\nThe fundamentals of light, surface, and movement are key to conveying how objects move, interact, and exist in space and in relation to each other. Realistic lighting shows seams, divides space, and indicates moving parts.' } - actions={[{ onPress: close, label: 'Ok' }]} + actions={[ + , + ]} /> ); diff --git a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx index 3e9402656c..eac4636b5f 100644 --- a/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx +++ b/example/src/Examples/Dialogs/DialogWithRadioBtns.tsx @@ -8,6 +8,7 @@ import { Text, TouchableRipple, useTheme, + Button, } from 'react-native-paper'; type Props = { @@ -99,8 +100,12 @@ const DialogWithRadioBtns = ({ visible, close }: Props) => { } actions={[ - { label: 'Cancel', onPress: close }, - { label: 'Ok', onPress: close }, + , + , ]} /> diff --git a/example/src/Examples/Dialogs/UndismissableDialog.tsx b/example/src/Examples/Dialogs/UndismissableDialog.tsx index 6d5fbd0d6a..1dad790030 100644 --- a/example/src/Examples/Dialogs/UndismissableDialog.tsx +++ b/example/src/Examples/Dialogs/UndismissableDialog.tsx @@ -1,4 +1,4 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; +import { Button, Dialog, Palette, Portal } from 'react-native-paper'; const UndismissableDialog = ({ visible, @@ -15,13 +15,17 @@ const UndismissableDialog = ({ title="Alert" content="This is an undismissable dialog!!" actions={[ - { - label: 'Disagree', - onPress: close, - disabled: true, - textColor: Palette.tertiary50, - }, - { label: 'Agree', onPress: close }, + , + , ]} /> diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index df1b0ed5ea..1812d55f6f 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -14,8 +14,6 @@ import DialogTitle from './DialogTitle'; import type { DialogChildProps } from './utils'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; -import Button from '../Button/Button'; -import type { Props as ButtonProps } from '../Button/Button'; import type { IconSource } from '../Icon'; import Modal from '../Modal'; import Text from '../Typography/Text'; @@ -49,12 +47,12 @@ export type Props = { * Content of the dialog. Non-empty strings are rendered as Material 3 * supporting text. */ - content: React.ReactNode; + content?: React.ReactNode; /** * Action buttons displayed at the bottom of the dialog. * Keep their order stable between renders. */ - actions?: DialogActionsProps[]; + actions?: React.ReactNode[]; /** * Whether to render the content in a `ScrollView` within the dialog scroll * area. @@ -83,8 +81,6 @@ export type Props = { testID?: string; }; -type DialogActionsProps = Omit & { label: string }; - const renderChildren = (children: React.ReactNode) => { const dialogChildren = React.Children.toArray(children).filter( (child) => child != null && typeof child !== 'boolean' @@ -211,20 +207,7 @@ const Dialog = ({ ); const dialogActions = actions?.length ? ( - - {actions.map( - ({ label, onPress: onActionPress, ...buttonProps }, index) => ( - - ) - )} - + {actions} ) : null; return [dialogIcon, dialogTitle, dialogContent, dialogActions]; diff --git a/src/components/Dialog/DialogActions.tsx b/src/components/Dialog/DialogActions.tsx index 4c4c9d00d3..4452f37c92 100644 --- a/src/components/Dialog/DialogActions.tsx +++ b/src/components/Dialog/DialogActions.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { StyleSheet, View } from 'react-native'; import type { StyleProp, ViewProps, ViewStyle } from 'react-native'; -import type { DialogActionChildProps } from './utils'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; @@ -48,26 +47,26 @@ export type Props = ViewProps & { * export default MyComponent; * ``` */ -const DialogActions = (props: Props) => { - useInternalTheme(props.theme); - const actionsLength = React.Children.toArray(props.children).length; +const DialogActions = ({ children, style, theme, ...rest }: Props) => { + useInternalTheme(theme); + + const actions = React.Children.toArray(children).filter((child) => + React.isValidElement<{ style?: StyleProp }>(child) + ); return ( - - {React.Children.map(props.children, (child, i) => - React.isValidElement(child) - ? React.cloneElement(child, { - compact: true, - uppercase: false, - style: [ - { - marginRight: i + 1 === actionsLength ? 0 : 8, - }, - child.props.style, - ], - }) - : child - )} + + {actions.map((child, index) => ( + + {child} + + ))} ); }; @@ -83,6 +82,12 @@ const styles = StyleSheet.create({ paddingBottom: 24, paddingHorizontal: 24, }, + item: { + marginRight: 8, + }, + itemLast: { + marginRight: 0, + }, }); export default DialogActions; diff --git a/src/components/Dialog/DialogScrollArea.tsx b/src/components/Dialog/DialogScrollArea.tsx index edf050b746..340aca4a75 100644 --- a/src/components/Dialog/DialogScrollArea.tsx +++ b/src/components/Dialog/DialogScrollArea.tsx @@ -46,7 +46,7 @@ const DialogScrollArea = (props: Props) => { const theme = useInternalTheme(props.theme); const { colors } = theme; const borderStyles = { - borderColor: colors.outline, + borderColor: colors.outlineVariant, borderTopWidth: 1, borderBottomWidth: 1, }; diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts index 5413d4c612..1602491265 100644 --- a/src/components/Dialog/utils.ts +++ b/src/components/Dialog/utils.ts @@ -3,8 +3,3 @@ import type { StyleProp, TextStyle, ViewStyle } from 'react-native'; export type DialogChildProps = { style?: StyleProp; }; - -export type DialogActionChildProps = DialogChildProps & { - compact?: boolean; - uppercase?: boolean; -}; diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 0f654800ff..4407d9b451 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -11,6 +11,7 @@ import { act, userEvent } from '@testing-library/react-native'; import Dialog from '../../components/Dialog/Dialog'; import { render, screen } from '../../test-utils'; +import Button from '../Button/Button'; interface BackHandlerStatic extends RNBackHandlerStatic { mockPressBack(): void; @@ -122,7 +123,6 @@ describe('Dialog', () => { scrollable scrollAreaProps={{ testID: 'dialog-scroll-area' }} scrollViewProps={{ testID: 'dialog-scroll-view' }} - actions={[{ onPress: jest.fn(), label: 'Ok' }]} /> ); @@ -137,11 +137,9 @@ describe('Dialog', () => { visible content="This is simple dialog" actions={[ - { - onPress: jest.fn(), - label: 'Cancel', - testID: 'cancel-btn', - }, + , ]} /> ); @@ -158,8 +156,12 @@ describe('DialogActions', () => { testID="dialog" content="This is simple dialog" actions={[ - { testID: 'button-cancel', label: 'Cancel' }, - { testID: 'button-ok', label: 'Ok' }, + , + , ]} /> ); @@ -170,42 +172,36 @@ describe('DialogActions', () => { it('should apply default styles', async () => { await render( - + + + + ); - const buttonCancelParent = screen.getByTestId('button-cancel').parent; - const buttonOkParent = screen.getByTestId('button-ok').parent; + const dialogActionsContainer = screen.getByTestId('dialog-actions'); + const dialogActionButtons = dialogActionsContainer.children; - expect(buttonCancelParent).toHaveStyle({ marginRight: 8 }); - expect(buttonOkParent).toHaveStyle({ marginRight: 0 }); + expect(dialogActionsContainer).toHaveStyle({ + paddingBottom: 24, + paddingHorizontal: 24, + }); + expect(dialogActionButtons[0]).toHaveStyle({ marginRight: 8 }); + expect(dialogActionButtons[1]).toHaveStyle({ marginRight: 0 }); }); it('should apply custom styles', async () => { await render( - + + + + ); - const buttonCancel = screen.getByTestId('button-cancel').parent; - const buttonOk = screen.getByTestId('button-ok').parent; + const dialogActionsContainer = screen.getByTestId('dialog-actions'); + const dialogActionButtons = dialogActionsContainer.children; - expect(buttonCancel).toHaveStyle({ marginRight: 8 }); - expect(buttonOk).toHaveStyle({ marginRight: 0 }); + expect(dialogActionButtons[0]).toHaveStyle({ margin: 10 }); + expect(dialogActionButtons[1]).toHaveStyle({ margin: 0 }); }); }); From 1e36df940c0d8267396c978dbe45537137fe5837 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 10:25:43 +0200 Subject: [PATCH 08/13] refactor: dropped cloneElement and updated arialabel for Dialog --- src/components/Dialog/Dialog.tsx | 132 +++++++++-------------- src/components/Dialog/utils.ts | 5 - src/components/Modal.tsx | 6 ++ src/components/__tests__/Dialog.test.tsx | 40 +++++++ 4 files changed, 99 insertions(+), 84 deletions(-) delete mode 100644 src/components/Dialog/utils.ts diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 1812d55f6f..320dbde330 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -11,7 +11,6 @@ import DialogIcon from './DialogIcon'; import DialogScrollArea from './DialogScrollArea'; import type { Props as DialogScrollAreaProps } from './DialogScrollArea'; import DialogTitle from './DialogTitle'; -import type { DialogChildProps } from './utils'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; import type { IconSource } from '../Icon'; @@ -70,6 +69,12 @@ export type Props = { * Props passed to the `ScrollView` when `scrollable` is enabled. */ scrollViewProps?: Omit; + /** + * Accessibility label for the dialog. You can use it to override the defaults. + * By default if a `title` is a string then it's passed as an accessibility label. + */ + accessibilityLabel?: string; + style?: Animated.WithAnimatedValue>; /** * @optional @@ -81,34 +86,6 @@ export type Props = { testID?: string; }; -const renderChildren = (children: React.ReactNode) => { - const dialogChildren = React.Children.toArray(children).filter( - (child) => child != null && typeof child !== 'boolean' - ); - const hasIcon = dialogChildren.some( - (child) => React.isValidElement(child) && child.type === DialogIcon - ); - - return dialogChildren.map((child, i) => { - if (React.isValidElement(child)) { - const topMarginStyle = - i === 0 && child.type !== DialogIcon ? styles.firstChild : undefined; - const titleAlignmentStyle = - hasIcon && child.type === DialogTitle - ? styles.titleWithIcon - : undefined; - - if (topMarginStyle || titleAlignmentStyle) { - return React.cloneElement(child, { - style: [topMarginStyle, child.props.style, titleAlignmentStyle], - }); - } - } - - return child; - }); -}; - /** * Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks. * To render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component. @@ -163,6 +140,7 @@ const Dialog = ({ scrollAreaProps, scrollViewProps, title, + accessibilityLabel, }: Props) => { const { right, left } = useSafeAreaInsets(); const theme = useInternalTheme(themeOverrides); @@ -170,59 +148,18 @@ const Dialog = ({ const backgroundColor = theme.colors.surfaceContainerHigh; - const _children = React.useMemo(() => { - const dialogIcon = icon ? ( - - ) : null; - const dialogTitle = title ? {title} : null; - - const contentNode = - typeof content === 'string' ? ( - - {content} - - ) : ( - content - ); - - const dialogContent = scrollable ? ( - - {contentNode} - + {content} + ) : ( - - {contentNode} - + content ); - const dialogActions = actions?.length ? ( - {actions} - ) : null; - - return [dialogIcon, dialogTitle, dialogContent, dialogActions]; - }, [ - actions, - content, - contentProps, - icon, - scrollAreaProps, - scrollViewProps, - scrollable, - theme.colors.onSurfaceVariant, - title, - ]); - return ( - {renderChildren(_children)} + {icon ? : null} + + {title ? ( + + {title} + + ) : null} + + {scrollable ? ( + + {contentNode} + + ) : ( + + {contentNode} + + )} + + {actions?.length ? {actions} : null} ); }; diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts deleted file mode 100644 index 1602491265..0000000000 --- a/src/components/Dialog/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StyleProp, TextStyle, ViewStyle } from 'react-native'; - -export type DialogChildProps = { - style?: StyleProp; -}; diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 3c66c885b3..c74321e4af 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -32,6 +32,10 @@ export type Props = { * Accessibility label for the overlay. This is read by the screen reader when the user taps outside the modal. */ overlayAccessibilityLabel?: string; + /** + * Accessibility label for the dialog. + */ + dialogAccessibilityLabel?: string; /** * Determines Whether the modal is visible. */ @@ -101,6 +105,7 @@ function Modal({ dismissableBackButton = dismissable, visible = false, overlayAccessibilityLabel = 'Close modal', + dialogAccessibilityLabel, onDismiss = () => {}, children, contentContainerStyle, @@ -220,6 +225,7 @@ function Modal({ theme={theme} style={[{ opacity }, styles.content, contentContainerStyle]} container + aria-label={dialogAccessibilityLabel} > {children} diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 4407d9b451..20e1dc6496 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -146,6 +146,46 @@ describe('Dialog', () => { expect(screen.getByTestId('cancel-btn')).toBeOnTheScreen(); }); + + it('should render passed content if no title or accessibilityLabel were passed', async () => { + await render( + + ); + + expect(screen.getByTestId('dialog-surface')).toHaveAccessibleName( + 'This is simple dialog' + ); + }); + + it('should render passed title as a default accessibility label', async () => { + await render( + + ); + + expect(screen.getByTestId('dialog-surface')).toHaveAccessibleName( + 'dialog-title' + ); + }); + + it('should render passed accessibility label', async () => { + await render( + + ); + + expect(screen.getByTestId('dialog-surface')).toHaveAccessibleName( + 'dialog-label' + ); + }); }); describe('DialogActions', () => { From 91a20a40476617138475ffc1f36c7fe48eb1dd7c Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 10:32:35 +0200 Subject: [PATCH 09/13] docs: updated docs --- docs/6.x/docs/components/Dialog/Dialog.mdx | 107 ++++++++------ .../docs/components/Dialog/DialogActions.mdx | 18 ++- .../docs/components/Dialog/DialogContent.mdx | 6 +- .../6.x/docs/components/Dialog/DialogIcon.mdx | 8 +- .../components/Dialog/DialogScrollArea.mdx | 8 +- .../docs/components/Dialog/DialogTitle.mdx | 7 +- docs/6.x/docs/components/Modal.mdx | 8 ++ docs/src/data/componentDocs6x.json | 131 ++++++++++++++++-- src/components/Dialog/DialogActions.tsx | 8 +- src/components/Dialog/DialogTitle.tsx | 13 -- 10 files changed, 212 insertions(+), 102 deletions(-) diff --git a/docs/6.x/docs/components/Dialog/Dialog.mdx b/docs/6.x/docs/components/Dialog/Dialog.mdx index 8a9f1381c8..6c8b4873d3 100644 --- a/docs/6.x/docs/components/Dialog/Dialog.mdx +++ b/docs/6.x/docs/components/Dialog/Dialog.mdx @@ -11,19 +11,6 @@ import ExtendedExample from '@docs/components/ExtendedExample.tsx'; Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks. To render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal/Portal) component. -## Recommended props - -| Prop | Type | Description | -| --- | --- | --- | -| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. | -| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. | -| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. | -| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and Button props. | -| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. | -| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. | -| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. | -| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. | - @@ -64,32 +51,6 @@ const MyComponent = () => { export default MyComponent; ``` -## Compound composition - -`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and -`Dialog.Actions` remain available for custom composition within `Dialog`. -Passing them through `children` is deprecated; prefer the props above. - -## Migrating from children - -```js -// Before - - Alert - Something happened. - - - -// After - -``` - ## Props @@ -130,11 +91,75 @@ Passing them through `children` is deprecated; prefer the props above.
-### children (depracated, use props instead) +### icon + +
+ + + +
+ +### title + +
+ + + +
+ +### content + +
+ + + +
+ +### actions + +
+ + + +
+ +### scrollable + +
+ + + +
+ +### contentProps + +
+ + + +
+ +### scrollAreaProps + +
+ + + +
+ +### scrollViewProps + +
+ + + +
+ +### accessibilityLabel
- +
diff --git a/docs/6.x/docs/components/Dialog/DialogActions.mdx b/docs/6.x/docs/components/Dialog/DialogActions.mdx index b1d6bda29f..9f06c45722 100644 --- a/docs/6.x/docs/components/Dialog/DialogActions.mdx +++ b/docs/6.x/docs/components/Dialog/DialogActions.mdx @@ -28,12 +28,18 @@ const MyComponent = () => { return ( - - - - - - + + Disagree + , + , + ]} + /> ); }; diff --git a/docs/6.x/docs/components/Dialog/DialogContent.mdx b/docs/6.x/docs/components/Dialog/DialogContent.mdx index 38771647f2..37b1d8dcc9 100644 --- a/docs/6.x/docs/components/Dialog/DialogContent.mdx +++ b/docs/6.x/docs/components/Dialog/DialogContent.mdx @@ -28,11 +28,7 @@ const MyComponent = () => { return ( - - - This is simple dialog - - + ); }; diff --git a/docs/6.x/docs/components/Dialog/DialogIcon.mdx b/docs/6.x/docs/components/Dialog/DialogIcon.mdx index a736d9d384..226dc9aa22 100644 --- a/docs/6.x/docs/components/Dialog/DialogIcon.mdx +++ b/docs/6.x/docs/components/Dialog/DialogIcon.mdx @@ -29,13 +29,7 @@ const MyComponent = () => { return ( - - - This is a title - - This is simple dialog - - + ); }; diff --git a/docs/6.x/docs/components/Dialog/DialogScrollArea.mdx b/docs/6.x/docs/components/Dialog/DialogScrollArea.mdx index f5fc1904d2..15bc9e9afe 100644 --- a/docs/6.x/docs/components/Dialog/DialogScrollArea.mdx +++ b/docs/6.x/docs/components/Dialog/DialogScrollArea.mdx @@ -30,13 +30,7 @@ const MyComponent = () => { return ( - - - - This is a scrollable area - - - + ); }; diff --git a/docs/6.x/docs/components/Dialog/DialogTitle.mdx b/docs/6.x/docs/components/Dialog/DialogTitle.mdx index d548cffb85..df557bf5e6 100644 --- a/docs/6.x/docs/components/Dialog/DialogTitle.mdx +++ b/docs/6.x/docs/components/Dialog/DialogTitle.mdx @@ -28,12 +28,7 @@ const MyComponent = () => { return ( - - This is a title - - This is simple dialog - - + ); }; diff --git a/docs/6.x/docs/components/Modal.mdx b/docs/6.x/docs/components/Modal.mdx index 72691042b1..30aa806989 100644 --- a/docs/6.x/docs/components/Modal.mdx +++ b/docs/6.x/docs/components/Modal.mdx @@ -87,6 +87,14 @@ export default MyComponent;
+### dialogAccessibilityLabel + +
+ + + +
+ ### visible
diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index f6d7ca856c..3a4a748503 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5206,10 +5206,10 @@ "Dialog/Dialog": { "filepath": "Dialog/Dialog.tsx", "title": "Dialog", - "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Recommended props\n\n| Prop | Type | Description |\n| --- | --- | --- |\n| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. |\n| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. |\n| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. |\n| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. |\n| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. |\n| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. |\n| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. |\n| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. |\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```\n\n## Compound composition\n\n`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and\n`Dialog.Actions` remain available for custom composition within `Dialog`.\nPassing them through `children` is deprecated; prefer the props above.\n\n## Migrating from children\n\n```js\n// Before\n\n Alert\n Something happened.\n \n\n\n// After\n\n```", + "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog", "data": { - "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Recommended props\n\n| Prop | Type | Description |\n| --- | --- | --- |\n| `icon` | `IconSource` | Icon rendered through `Dialog.Icon`. |\n| `title` | `ReactNode` | Dialog title rendered through `Dialog.Title`. |\n| `content` | `ReactNode` | Required dialog content. Non-empty strings use Material 3 supporting-text styles. |\n| `actions` | `DialogActionsProps[]` | Action labels, press handlers, and optional Button props. |\n| `scrollable` | `boolean` | Renders content through `Dialog.ScrollArea` and a `ScrollView`. |\n| `contentProps` | `DialogContentProps` | Props forwarded to `Dialog.Content`. |\n| `scrollAreaProps` | `DialogScrollAreaProps` | Props forwarded to `Dialog.ScrollArea`. |\n| `scrollViewProps` | `ScrollViewProps` | Props forwarded to `ScrollView`. |\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```\n\n## Compound composition\n\n`Dialog.Icon`, `Dialog.Title`, `Dialog.Content`, `Dialog.ScrollArea`, and\n`Dialog.Actions` remain available for custom composition within `Dialog`.\nPassing them through `children` is deprecated; prefer the props above.\n\n## Migrating from children\n\n```js\n// Before\n\n Alert\n Something happened.\n \n\n\n// After\n\n```", + "description": "Dialogs inform users about a specific task and may contain critical information, require decisions, or involve multiple tasks.\nTo render the `Dialog` above other components, you'll need to wrap it with the [`Portal`](../Portal) component.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { View } from 'react-native';\nimport { Button, Dialog, Portal, PaperProvider } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const showDialog = () => setVisible(true);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog", "methods": [], "statics": [], @@ -5262,13 +5262,107 @@ "computed": false } }, - "children": { - "required": true, + "icon": { + "required": false, + "tsType": { + "name": "IconSource" + }, + "description": "Icon to display above the dialog title." + }, + "title": { + "required": false, "tsType": { "name": "ReactReactNode", "raw": "React.ReactNode" }, - "description": "Content of the `Dialog`." + "description": "Title of the dialog." + }, + "content": { + "required": false, + "tsType": { + "name": "ReactReactNode", + "raw": "React.ReactNode" + }, + "description": "Content of the dialog. Non-empty strings are rendered as Material 3\nsupporting text." + }, + "actions": { + "required": false, + "tsType": { + "name": "Array", + "elements": [ + { + "name": "ReactReactNode", + "raw": "React.ReactNode" + } + ], + "raw": "React.ReactNode[]" + }, + "description": "Action buttons displayed at the bottom of the dialog.\nKeep their order stable between renders." + }, + "scrollable": { + "required": false, + "tsType": { + "name": "boolean" + }, + "description": "Whether to render the content in a `ScrollView` within the dialog scroll\narea." + }, + "contentProps": { + "required": false, + "tsType": { + "name": "Omit", + "elements": [ + { + "name": "DialogContentProps" + }, + { + "name": "literal", + "value": "'children'" + } + ], + "raw": "Omit" + }, + "description": "Props passed to `Dialog.Content` when `scrollable` is not enabled." + }, + "scrollAreaProps": { + "required": false, + "tsType": { + "name": "Omit", + "elements": [ + { + "name": "DialogScrollAreaProps" + }, + { + "name": "literal", + "value": "'children'" + } + ], + "raw": "Omit" + }, + "description": "Props passed to `Dialog.ScrollArea` when `scrollable` is enabled." + }, + "scrollViewProps": { + "required": false, + "tsType": { + "name": "Omit", + "elements": [ + { + "name": "ScrollViewProps" + }, + { + "name": "literal", + "value": "'children'" + } + ], + "raw": "Omit" + }, + "description": "Props passed to the `ScrollView` when `scrollable` is enabled." + }, + "accessibilityLabel": { + "required": false, + "tsType": { + "name": "string" + }, + "description": "Accessibility label for the dialog. You can use it to override the defaults.\nBy default if a `title` is a string then it's passed as an accessibility label." }, "style": { "required": false, @@ -5313,10 +5407,10 @@ "Dialog/DialogActions": { "filepath": "Dialog/DialogActions.tsx", "title": "Dialog.Actions", - "description": "A component to show a list of actions in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Button, Dialog, Portal } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a list of actions in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Button, Dialog, Portal } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n\t\t\t\t\t\t\tDisagree\n\t\t\t\t\t\t,\n\t\t\t\t\t\t,\n\t\t\t\t\t]}\n\t\t />\n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-actions", "data": { - "description": "A component to show a list of actions in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Button, Dialog, Portal } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a list of actions in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Button, Dialog, Portal } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n\t\t\t\t\t\t\tDisagree\n\t\t\t\t\t\t,\n\t\t\t\t\t\t,\n\t\t\t\t\t]}\n\t\t />\n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.Actions", "methods": [], "statics": [], @@ -5360,10 +5454,10 @@ "Dialog/DialogContent": { "filepath": "Dialog/DialogContent.tsx", "title": "Dialog.Content", - "description": "A component to show content in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show content in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-content", "data": { - "description": "A component to show content in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show content in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.Content", "methods": [], "statics": [], @@ -5400,10 +5494,10 @@ "Dialog/DialogIcon": { "filepath": "Dialog/DialogIcon.tsx", "title": "Dialog.Icon", - "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-icon", "data": { - "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "@supported Available in v5.x with theme version 3\nA component to show an icon in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.Icon", "methods": [], "statics": [], @@ -5451,10 +5545,10 @@ "Dialog/DialogScrollArea": { "filepath": "Dialog/DialogScrollArea.tsx", "title": "Dialog.ScrollArea", - "description": "A component to show a scrollable content in a Dialog. The component only provides appropriate styling.\nFor the scrollable content you can use `ScrollView`, `FlatList` etc. depending on your requirement.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { ScrollView } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n This is a scrollable area\n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a scrollable content in a Dialog. The component only provides appropriate styling.\nFor the scrollable content you can use `ScrollView`, `FlatList` etc. depending on your requirement.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { ScrollView } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-scroll-area", "data": { - "description": "A component to show a scrollable content in a Dialog. The component only provides appropriate styling.\nFor the scrollable content you can use `ScrollView`, `FlatList` etc. depending on your requirement.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { ScrollView } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n \n This is a scrollable area\n \n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a scrollable content in a Dialog. The component only provides appropriate styling.\nFor the scrollable content you can use `ScrollView`, `FlatList` etc. depending on your requirement.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { ScrollView } from 'react-native';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.ScrollArea", "methods": [], "statics": [], @@ -5498,10 +5592,10 @@ "Dialog/DialogTitle": { "filepath": "Dialog/DialogTitle.tsx", "title": "Dialog.Title", - "description": "A component to show a title in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a title in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "link": "dialog-title", "data": { - "description": "A component to show a title in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n This is a title\n \n This is simple dialog\n \n \n \n );\n};\n\nexport default MyComponent;\n```", + "description": "A component to show a title in a Dialog.\n\n## Usage\n```js\nimport * as React from 'react';\nimport { Dialog, Portal, Text } from 'react-native-paper';\n\nconst MyComponent = () => {\n const [visible, setVisible] = React.useState(false);\n\n const hideDialog = () => setVisible(false);\n\n return (\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog.Title", "methods": [], "statics": [], @@ -9435,6 +9529,13 @@ "computed": false } }, + "dialogAccessibilityLabel": { + "required": false, + "tsType": { + "name": "string" + }, + "description": "Accessibility label for the dialog." + }, "visible": { "required": false, "tsType": { diff --git a/src/components/Dialog/DialogActions.tsx b/src/components/Dialog/DialogActions.tsx index 4452f37c92..2aff5e8748 100644 --- a/src/components/Dialog/DialogActions.tsx +++ b/src/components/Dialog/DialogActions.tsx @@ -36,8 +36,12 @@ export type Props = ViewProps & { * visible={visible} * onDismiss={hideDialog} * actions={[ - * { onPress: () => console.log('Cancel'), label: 'Cancel' }, - * { onPress: () => console.log('Ok'), label: 'Ok' }, + * , + * , * ]} * /> * diff --git a/src/components/Dialog/DialogTitle.tsx b/src/components/Dialog/DialogTitle.tsx index 2a2acb5c1a..e76f1e8209 100644 --- a/src/components/Dialog/DialogTitle.tsx +++ b/src/components/Dialog/DialogTitle.tsx @@ -31,19 +31,6 @@ export type Props = React.ComponentPropsWithRef & { * * const hideDialog = () => setVisible(false); * - * // Before - * return ( - * - * - * This is a title - * - * This is simple dialog - * - * - * - * ); - * - * // V6 and later * return ( * * From 06054f058e2ea6339c6849c8e7d6d0ab59e997d9 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:36:44 +0200 Subject: [PATCH 10/13] refactor: changed accessibilityLabel into aria-label --- docs/6.x/docs/components/Dialog/Dialog.mdx | 4 ++-- docs/6.x/docs/components/Modal.mdx | 4 ++-- docs/src/data/componentDocs6x.json | 8 ++++---- src/components/Dialog/Dialog.tsx | 12 ++++-------- src/components/Modal.tsx | 8 ++++---- src/components/__tests__/Dialog.test.tsx | 2 +- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/docs/6.x/docs/components/Dialog/Dialog.mdx b/docs/6.x/docs/components/Dialog/Dialog.mdx index 6c8b4873d3..2a6a33f54d 100644 --- a/docs/6.x/docs/components/Dialog/Dialog.mdx +++ b/docs/6.x/docs/components/Dialog/Dialog.mdx @@ -155,11 +155,11 @@ export default MyComponent;
-### accessibilityLabel +### aria-label
- +
diff --git a/docs/6.x/docs/components/Modal.mdx b/docs/6.x/docs/components/Modal.mdx index 30aa806989..e7cd88fb58 100644 --- a/docs/6.x/docs/components/Modal.mdx +++ b/docs/6.x/docs/components/Modal.mdx @@ -87,11 +87,11 @@ export default MyComponent;
-### dialogAccessibilityLabel +### aria-label
- +
diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index 3a4a748503..fb37d05a76 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5357,12 +5357,12 @@ }, "description": "Props passed to the `ScrollView` when `scrollable` is enabled." }, - "accessibilityLabel": { + "aria-label": { "required": false, "tsType": { "name": "string" }, - "description": "Accessibility label for the dialog. You can use it to override the defaults.\nBy default if a `title` is a string then it's passed as an accessibility label." + "description": "Accessibility label for the dialog. This is read by the screen reader when the user opens a dialog." }, "style": { "required": false, @@ -9529,12 +9529,12 @@ "computed": false } }, - "dialogAccessibilityLabel": { + "aria-label": { "required": false, "tsType": { "name": "string" }, - "description": "Accessibility label for the dialog." + "description": "Accessibility label for the dialog. This is read by the screen reader when the user opens a dialog." }, "visible": { "required": false, diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 320dbde330..011654ae03 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -70,11 +70,9 @@ export type Props = { */ scrollViewProps?: Omit; /** - * Accessibility label for the dialog. You can use it to override the defaults. - * By default if a `title` is a string then it's passed as an accessibility label. + * Accessibility label for the dialog. This is read by the screen reader when the user opens a dialog. */ - accessibilityLabel?: string; - + 'aria-label'?: string; style?: Animated.WithAnimatedValue>; /** * @optional @@ -140,7 +138,7 @@ const Dialog = ({ scrollAreaProps, scrollViewProps, title, - accessibilityLabel, + 'aria-label': ariaLabel, }: Props) => { const { right, left } = useSafeAreaInsets(); const theme = useInternalTheme(themeOverrides); @@ -177,9 +175,7 @@ const Dialog = ({ ]} theme={theme} testID={testID} - dialogAccessibilityLabel={ - typeof title === 'string' ? title : accessibilityLabel - } + aria-label={typeof title === 'string' ? title : ariaLabel} > {icon ? : null} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index c74321e4af..18803b56dc 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -33,9 +33,9 @@ export type Props = { */ overlayAccessibilityLabel?: string; /** - * Accessibility label for the dialog. + * Accessibility label for the dialog. This is read by the screen reader when the user opens a dialog. */ - dialogAccessibilityLabel?: string; + 'aria-label'?: string; /** * Determines Whether the modal is visible. */ @@ -105,7 +105,7 @@ function Modal({ dismissableBackButton = dismissable, visible = false, overlayAccessibilityLabel = 'Close modal', - dialogAccessibilityLabel, + 'aria-label': ariaLabel, onDismiss = () => {}, children, contentContainerStyle, @@ -225,7 +225,7 @@ function Modal({ theme={theme} style={[{ opacity }, styles.content, contentContainerStyle]} container - aria-label={dialogAccessibilityLabel} + aria-label={ariaLabel} > {children} diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 20e1dc6496..6c8bd4e5b9 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -178,7 +178,7 @@ describe('Dialog', () => { testID="dialog" visible content="This is simple dialog" - accessibilityLabel="dialog-label" + aria-label="dialog-label" /> ); From 09cfb96c9b349b2cce0c26cebbddb8c4b2b8c4ad Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:42:11 +0200 Subject: [PATCH 11/13] refactor: updated styles for contents in Dialog --- src/components/Dialog/Dialog.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 011654ae03..358d766e08 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -192,20 +192,14 @@ const Dialog = ({ {scrollable ? ( {contentNode} ) : ( {contentNode} From 60822e77f1c83043ceb9dc6f1044ac1b1346cf8c Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:56:31 +0200 Subject: [PATCH 12/13] docs: updated migration.md --- docs/6.x/docs/guides/migration.md | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index 1fcd32bd25..45c710dc84 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -126,3 +126,53 @@ const theme = { style={{ fontSize: 16, color: '#1C1B1F' }} /> ``` + +### Dialog + +The Paper 6.x `Dialog` now supports a simplified prop-based API for common dialog layouts. If your dialog previously composed `Dialog.Title`, `Dialog.Content`, and `Dialog.Actions` via `children`, migrate to the dedicated `icon`, `title`, `content`, and `actions` props. + +#### Removed props + +- **`children`** was removed from `Dialog` + +Use the dedicated props instead: + +- **`Dialog.Icon`** → **`icon`** +- **`Dialog.Title`** → **`title`** +- **`Dialog.Content`** → **`content`** +- **`Dialog.Actions`** → **`actions`** + +#### New props + +- **`scrollable`** renders the dialog content inside a scrollable area. +- **`contentProps`** passes props to the internal `Dialog.Content` when `scrollable` is not enabled. +- **`scrollAreaProps`** passes props to the internal `Dialog.ScrollArea` when `scrollable` is enabled. +- **`scrollViewProps`** passes props to the internal `ScrollView` when `scrollable` is enabled. +- **`aria-label`** provides an accessibility label when the dialog title is not a string. + +```tsx +// Before (v5) + + + Alert + + This is a simple dialog + + + + + + + +// After (v6) + + Done]} + /> + +``` From 8f4cb22359f9f6c14098c0b86d2c83486beb704b Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 16:05:47 +0200 Subject: [PATCH 13/13] refactor: updated migration.md --- docs/6.x/docs/guides/migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index 45c710dc84..48506a4b21 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -142,6 +142,8 @@ Use the dedicated props instead: - **`Dialog.Content`** → **`content`** - **`Dialog.Actions`** → **`actions`** +The new **`actions`** prop is more customizable because it accepts **`React.ReactNode[]`**. Unlike the previous `Dialog.Actions` children behavior, the dialog no longer injects **`compact`** and **`uppercase`** into action components automatically. When **`actions`** is provided, the passed components are rendered through **`Dialog.Actions`** internally. + #### New props - **`scrollable`** renders the dialog content inside a scrollable area.