From 79dd3dce59b1e5a7fc4988c3b3995bb8444359af Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 20 Aug 2026 09:26:48 +0200 Subject: [PATCH 01/18] 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 b223c9c0a8..92c459b85e 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 89832fe658..2beb77285a 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -105,6 +105,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; + })} ); }; @@ -162,6 +179,11 @@ const styles = StyleSheet.create({ */ marginVertical: Platform.OS === 'android' ? 44 : 0, 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 cc3a902eb63747d93a106bd0876b2ab19966d005 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:54:22 +0200 Subject: [PATCH 02/18] 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 | 189 ++++++++++++++++----- 3 files changed, 200 insertions(+), 52 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 92c459b85e..b6ce0ee29d 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5212,10 +5212,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 2beb77285a..9f1a37cd17 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -15,7 +15,7 @@ import Modal from '../Modal'; import type { SurfaceStyle } from '../Surface'; import type { DialogChildProps } from './utils'; -export type Props = { +type CommonProps = { /** * Determines whether clicking outside the dialog dismiss it. */ @@ -49,15 +49,56 @@ export type Props = { const DIALOG_ELEVATION: Elevation = 3; +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); @@ -71,15 +112,13 @@ const DIALOG_ELEVATION: Elevation = 3; * * * - * - * Alert - * - * This is simple dialog - * - * - * - * - * + * * * * @@ -88,6 +127,32 @@ const DIALOG_ELEVATION: Elevation = 3; * * 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, @@ -98,6 +163,7 @@ const Dialog = ({ style, theme: themeOverrides, testID, + ...props }: Props) => { const { right, left } = useSafeAreaInsets(); @@ -105,12 +171,75 @@ 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 - ); + + 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)} ); }; @@ -185,6 +293,9 @@ const styles = StyleSheet.create({ titleWithIcon: { textAlign: 'center', }, + firstChild: { + marginTop: 24, + }, }); export default Dialog; From 959726dc35e5df7452b6a6ab4e1701071e2d13fe Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:55:03 +0200 Subject: [PATCH 03/18] 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 f0983cfc669f75acabc7c65ef0e55b9242294f57 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 09:56:44 +0200 Subject: [PATCH 04/18] 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 eeb2bea65a168b2e5723f94c15b4e2e74b744ca3 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 25 Aug 2026 12:01:09 +0200 Subject: [PATCH 05/18] 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 9f1a37cd17..670dc81194 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -87,12 +87,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 5b17c29095dbab6f8f71c1dbb0a8925024a4a6ce Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 27 Aug 2026 13:11:05 +0200 Subject: [PATCH 06/18] 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 | 4 +- .../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 | 76 +++------ 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, 253 insertions(+), 835 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 75b5ee6b6e..c66309a565 100644 --- a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx +++ b/example/src/Examples/Dialogs/DialogWithCustomColors.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 DialogWithCustomColors = ({ visible, 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 670dc81194..11b02214c5 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -15,7 +15,7 @@ import Modal from '../Modal'; import type { SurfaceStyle } from '../Surface'; import type { DialogChildProps } from './utils'; -type CommonProps = { +export type Props = { /** * Determines whether clicking outside the dialog dismiss it. */ @@ -81,19 +81,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'; @@ -127,35 +114,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, @@ -163,7 +123,14 @@ const Dialog = ({ style, theme: themeOverrides, testID, - ...props + actions, + content, + icon, + scrollable, + contentProps, + scrollAreaProps, + scrollViewProps, + title, }: Props) => { const { right, left } = useSafeAreaInsets(); @@ -173,19 +140,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; @@ -239,7 +193,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 c907350399b1550d421983d4cb2041ab7ba63077 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Thu, 27 Aug 2026 15:52:04 +0200 Subject: [PATCH 07/18] refactor: removed cloneElement from DialogActions, changed actions type --- example/src/DrawerItems.tsx | 7 +- .../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 | 15 +---- 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 +++++++++---------- 11 files changed, 105 insertions(+), 101 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/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 11b02214c5..f221f1a79d 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -176,20 +176,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 0395096dac81b01629bb5f4d4bf32485c821bdab Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 10:25:43 +0200 Subject: [PATCH 08/18] refactor: dropped cloneElement and updated arialabel for Dialog --- src/components/Dialog/Dialog.tsx | 169 ++++++++++++----------- src/components/Dialog/utils.ts | 5 - src/components/Modal.tsx | 6 + src/components/__tests__/Dialog.test.tsx | 40 ++++++ 4 files changed, 133 insertions(+), 87 deletions(-) delete mode 100644 src/components/Dialog/utils.ts diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index f221f1a79d..4331f6eb68 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -11,9 +11,8 @@ import DialogScrollArea from './DialogScrollArea'; import DialogTitle from './DialogTitle'; import { useInternalTheme } from '../../core/theming'; import type { Elevation, ThemeProp } from '../../types'; +import type { IconSource } from '../Icon'; import Modal from '../Modal'; -import type { SurfaceStyle } from '../Surface'; -import type { DialogChildProps } from './utils'; export type Props = { /** @@ -35,8 +34,45 @@ export type Props = { /** * Content of the `Dialog`. */ - children: React.ReactNode; - style?: StyleProp; + 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?: React.ReactNode[]; + /** + * 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; + /** + * 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 */ @@ -49,34 +85,6 @@ export type Props = { const DIALOG_ELEVATION: Elevation = 3; -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. @@ -131,6 +139,7 @@ const Dialog = ({ scrollAreaProps, scrollViewProps, title, + accessibilityLabel, }: Props) => { const { right, left } = useSafeAreaInsets(); @@ -139,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 22a3f7ba8e..b24b98feea 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -36,6 +36,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. */ @@ -127,6 +131,7 @@ function Modal({ dismissableBackButton = dismissable, visible = false, overlayAccessibilityLabel = 'Close modal', + dialogAccessibilityLabel, onDismiss = () => {}, children, contentContainerStyle, @@ -259,6 +264,7 @@ function Modal({ ]} elevation={contentElevation} transitionDuration={scale * DEFAULT_DURATION} + 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 1cb4b2212bf3f835121dd3637a82c081a51f8d4f Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 10:32:35 +0200 Subject: [PATCH 09/18] 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 f94e758cad..0babcca4c7 100644 --- a/docs/6.x/docs/components/Modal.mdx +++ b/docs/6.x/docs/components/Modal.mdx @@ -93,6 +93,14 @@ export default MyComponent;
+### dialogAccessibilityLabel + +
+ + + +
+ ### visible
diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index b6ce0ee29d..646335640a 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5212,10 +5212,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": [], @@ -5268,13 +5268,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": [], @@ -9441,6 +9535,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 fca679ef248803b21086b15ae02a4962164859a6 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:36:44 +0200 Subject: [PATCH 10/18] 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 0babcca4c7..860bc2f5b2 100644 --- a/docs/6.x/docs/components/Modal.mdx +++ b/docs/6.x/docs/components/Modal.mdx @@ -93,11 +93,11 @@ export default MyComponent;
-### dialogAccessibilityLabel +### aria-label
- +
diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index 646335640a..486ff2a76b 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5363,12 +5363,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, @@ -9535,12 +9535,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 4331f6eb68..805706349f 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -67,11 +67,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 @@ -139,7 +137,7 @@ const Dialog = ({ scrollAreaProps, scrollViewProps, title, - accessibilityLabel, + 'aria-label': ariaLabel, }: Props) => { const { right, left } = useSafeAreaInsets(); @@ -178,9 +176,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 b24b98feea..9842818d41 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -37,9 +37,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. */ @@ -131,7 +131,7 @@ function Modal({ dismissableBackButton = dismissable, visible = false, overlayAccessibilityLabel = 'Close modal', - dialogAccessibilityLabel, + 'aria-label': ariaLabel, onDismiss = () => {}, children, contentContainerStyle, @@ -264,7 +264,7 @@ function Modal({ ]} elevation={contentElevation} transitionDuration={scale * DEFAULT_DURATION} - 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 a43978d469251d4d3a07d306017c2e22bf20bb4a Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:42:11 +0200 Subject: [PATCH 11/18] 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 805706349f..28a639d0fb 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -193,20 +193,14 @@ const Dialog = ({ {scrollable ? ( {contentNode} ) : ( {contentNode} From 2fdfdb7f4a38bd134abf1efd5daff35a07d9def0 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 15:56:31 +0200 Subject: [PATCH 12/18] 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 e488b101ef..b75e36151b 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -280,3 +280,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 e028d64722336402a78b494f96a8da5e8eea9afe Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Fri, 28 Aug 2026 16:05:47 +0200 Subject: [PATCH 13/18] 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 b75e36151b..fcd0cbf412 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -296,6 +296,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. From 35d8e443107b4c55fff316c798c285352e0aef67 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Mon, 31 Aug 2026 10:53:31 +0200 Subject: [PATCH 14/18] refactor: updated DialogActions.tsx and tests --- src/components/Dialog/DialogActions.tsx | 20 +++++++----------- src/components/__tests__/Dialog.test.tsx | 27 +++++++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/components/Dialog/DialogActions.tsx b/src/components/Dialog/DialogActions.tsx index 2aff5e8748..1f912ef183 100644 --- a/src/components/Dialog/DialogActions.tsx +++ b/src/components/Dialog/DialogActions.tsx @@ -61,15 +61,14 @@ const DialogActions = ({ children, style, theme, ...rest }: Props) => { return ( {actions.map((child, index) => ( - + {index > 0 && } {child} - + ))} ); @@ -86,11 +85,8 @@ const styles = StyleSheet.create({ paddingBottom: 24, paddingHorizontal: 24, }, - item: { - marginRight: 8, - }, - itemLast: { - marginRight: 0, + spacer: { + width: 8, }, }); diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 6c8bd4e5b9..d1f3e8d793 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -219,29 +219,36 @@ describe('DialogActions', () => { ); const dialogActionsContainer = screen.getByTestId('dialog-actions'); - const dialogActionButtons = dialogActionsContainer.children; + const dialogActionChildren = dialogActionsContainer.children; expect(dialogActionsContainer).toHaveStyle({ paddingBottom: 24, paddingHorizontal: 24, }); - expect(dialogActionButtons[0]).toHaveStyle({ marginRight: 8 }); - expect(dialogActionButtons[1]).toHaveStyle({ marginRight: 0 }); + + // We expect 3 children because Dialog.Actions puts in between actions to add a proper styling + expect(dialogActionChildren).toHaveLength(3); + expect(dialogActionChildren[1]).toHaveStyle({ width: 8 }); }); it('should apply custom styles', async () => { await render( - - + + ); - const dialogActionsContainer = screen.getByTestId('dialog-actions'); - const dialogActionButtons = dialogActionsContainer.children; - - expect(dialogActionButtons[0]).toHaveStyle({ margin: 10 }); - expect(dialogActionButtons[1]).toHaveStyle({ margin: 0 }); + expect(screen.getByTestId('button-cancel-container')).toHaveStyle({ + margin: 10, + }); + expect(screen.getByTestId('button-ok-container')).toHaveStyle({ + margin: 0, + }); }); }); From 42864d14c664bae3b42ac34ff053f6cad25b416d Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Tue, 1 Sep 2026 10:37:55 +0200 Subject: [PATCH 15/18] refactor: addressed comments, updated docs --- docs/6.x/docs/components/Dialog/Dialog.mdx | 4 +++- docs/src/data/componentDocs6x.json | 4 ++-- src/components/Dialog/Dialog.tsx | 6 ++++-- src/components/Modal.tsx | 1 + 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/6.x/docs/components/Dialog/Dialog.mdx b/docs/6.x/docs/components/Dialog/Dialog.mdx index 2a6a33f54d..9ced6eb35d 100644 --- a/docs/6.x/docs/components/Dialog/Dialog.mdx +++ b/docs/6.x/docs/components/Dialog/Dialog.mdx @@ -40,7 +40,9 @@ const MyComponent = () => { onDismiss={hideDialog} title="Alert" content="This is simple dialog" - actions={[{ label: 'Done', onPress: hideDialog }]} + actions={[ + + ]} /> diff --git a/docs/src/data/componentDocs6x.json b/docs/src/data/componentDocs6x.json index 486ff2a76b..522465f6ad 100644 --- a/docs/src/data/componentDocs6x.json +++ b/docs/src/data/componentDocs6x.json @@ -5212,10 +5212,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 } 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```", + "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 Done\n\t\t\t\t\t\t ]}\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## 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```", + "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 Done\n\t\t\t\t\t\t ]}\n />\n \n \n \n );\n};\n\nexport default MyComponent;\n```", "displayName": "Dialog", "methods": [], "statics": [], diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 28a639d0fb..60b367f74d 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -110,7 +110,9 @@ const DIALOG_ELEVATION: Elevation = 3; * onDismiss={hideDialog} * title="Alert" * content="This is simple dialog" - * actions={[{ label: 'Done', onPress: hideDialog }]} + * actions={[ + * + * ]} * /> * * @@ -176,7 +178,7 @@ const Dialog = ({ ]} theme={theme} testID={testID} - aria-label={typeof title === 'string' ? title : ariaLabel} + aria-label={ariaLabel ?? (typeof title === 'string' ? title : undefined)} > {icon ? : null} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 9842818d41..316074d1a2 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -265,6 +265,7 @@ function Modal({ elevation={contentElevation} transitionDuration={scale * DEFAULT_DURATION} aria-label={ariaLabel} + role="dialog" > {children} From 7be180f2ce4a9021e002f7e166293c9b22c5332b Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Wed, 2 Sep 2026 10:36:03 +0200 Subject: [PATCH 16/18] refactor: conflicts fix --- .../Dialogs/DialogWithCustomColors.tsx | 37 ++++++++++++------- src/components/Dialog/Dialog.tsx | 14 +++++-- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx index c66309a565..c47f54b4d3 100644 --- a/example/src/Examples/Dialogs/DialogWithCustomColors.tsx +++ b/example/src/Examples/Dialogs/DialogWithCustomColors.tsx @@ -1,4 +1,6 @@ -import { Dialog, Palette, Portal } from 'react-native-paper'; +import { StyleSheet } from 'react-native'; + +import { Button, Dialog, Palette, Portal, Text } from 'react-native-paper'; const DialogWithCustomColors = ({ visible, @@ -11,27 +13,36 @@ const DialogWithCustomColors = ({ - Alert - - + title={ + + Alert + + } + content={ + This is a dialog with custom colors - - - - - - + , + ]} + /> ); }; +const styles = StyleSheet.create({ + text: { + color: Palette.primary95, + }, +}); + export default DialogWithCustomColors; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 60b367f74d..70427b19f9 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,9 +1,14 @@ import * as React from 'react'; -import { Platform, StyleSheet } from 'react-native'; -import type { StyleProp } from 'react-native'; +import { Platform, ScrollView, StyleSheet } from 'react-native'; +import type { ScrollViewProps, StyleProp } from 'react-native'; +import type { + DialogContentProps, + DialogScrollAreaProps, +} from 'react-native-paper'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Modal from '../Modal'; import DialogActions from './DialogActions'; import DialogContent from './DialogContent'; import DialogIcon from './DialogIcon'; @@ -12,7 +17,8 @@ import DialogTitle from './DialogTitle'; import { useInternalTheme } from '../../core/theming'; import type { Elevation, ThemeProp } from '../../types'; import type { IconSource } from '../Icon'; -import Modal from '../Modal'; +import type { SurfaceStyle } from '../Surface'; +import Text from '../Typography/Text'; export type Props = { /** @@ -70,7 +76,7 @@ export type Props = { * Accessibility label for the dialog. This is read by the screen reader when the user opens a dialog. */ 'aria-label'?: string; - style?: Animated.WithAnimatedValue>; + style?: StyleProp; /** * @optional */ From a91e1588318ca990982255a23ab4773caf25f761 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Wed, 2 Sep 2026 10:38:53 +0200 Subject: [PATCH 17/18] docs: fixed description for icon prop in Dialog --- src/components/Dialog/Dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 70427b19f9..e9cbf41c47 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -38,7 +38,7 @@ export type Props = { */ visible: boolean; /** - * Content of the `Dialog`. + * Icon to display above the dialog title. */ icon?: IconSource; /** From e2db9f19b7a44d5b6277dba04c656fb2516cb594 Mon Sep 17 00:00:00 2001 From: Oskar Kaczmarzyk Date: Wed, 2 Sep 2026 16:34:51 +0200 Subject: [PATCH 18/18] refactor: addressed comments --- docs/6.x/docs/guides/migration.md | 10 ++++------ src/components/Dialog/Dialog.tsx | 6 ++---- src/components/Modal.tsx | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index fcd0cbf412..69a8372ac8 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -155,11 +155,6 @@ e.g.: ``` -### Dialog - -- The default elevation changed from level `1` to level `3`. -- The `style` prop no longer configures the background color or border radius. You can override `theme.colors.surfaceContainerHigh` and `theme.shapes.corner.extraLarge` using the `theme` prop instead. - ### TextInput The Paper 6.x `TextInput` is a complete rewrite with a new API. Import the component the same way, but note that the props and behavior have changed significantly. @@ -283,6 +278,9 @@ const theme = { ### Dialog +- The default elevation changed from level `1` to level `3`. +- The `style` prop no longer configures the background color or border radius. You can override `theme.colors.surfaceContainerHigh` and `theme.shapes.corner.extraLarge` using the `theme` prop instead. + 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 @@ -304,7 +302,7 @@ The new **`actions`** prop is more customizable because it accepts **`React.Reac - **`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. +- **`aria-label`** overrides the title as the dialog's accessible name. ```tsx // Before (v5) diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index e9cbf41c47..5c229d0900 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -2,10 +2,6 @@ import * as React from 'react'; import { Platform, ScrollView, StyleSheet } from 'react-native'; import type { ScrollViewProps, StyleProp } from 'react-native'; -import type { - DialogContentProps, - DialogScrollAreaProps, -} from 'react-native-paper'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Modal from '../Modal'; @@ -18,6 +14,8 @@ import { useInternalTheme } from '../../core/theming'; import type { Elevation, ThemeProp } from '../../types'; import type { IconSource } from '../Icon'; import type { SurfaceStyle } from '../Surface'; +import type { Props as DialogContentProps } from './DialogContent'; +import type { Props as DialogScrollAreaProps } from './DialogScrollArea'; import Text from '../Typography/Text'; export type Props = { diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 316074d1a2..5d9d9b1c11 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -227,7 +227,6 @@ function Modal({ return ( {children}