diff --git a/Libraries/Alert/Alert.js b/Libraries/Alert/Alert.js index 1a943e1bbd7b0f..3e011e4cd7274e 100644 --- a/Libraries/Alert/Alert.js +++ b/Libraries/Alert/Alert.js @@ -22,6 +22,7 @@ export type AlertButtonStyle = 'default' | 'cancel' | 'destructive'; export type Buttons = Array<{ text?: string, onPress?: ?Function, + isPreferred?: boolean, style?: AlertButtonStyle, ... }>; @@ -126,6 +127,7 @@ class Alert { const buttons = []; let cancelButtonKey; let destructiveButtonKey; + let preferredButtonKey; if (typeof callbackOrButtons === 'function') { callbacks = [callbackOrButtons]; } else if (Array.isArray(callbackOrButtons)) { @@ -135,6 +137,8 @@ class Alert { cancelButtonKey = String(index); } else if (btn.style === 'destructive') { destructiveButtonKey = String(index); + } else if (btn.isPreferred) { + preferredButtonKey = String(index); } if (btn.text || index < (callbackOrButtons || []).length - 1) { const btnDef: {[number]: string} = {}; @@ -153,6 +157,7 @@ class Alert { defaultValue, cancelButtonKey, destructiveButtonKey, + preferredButtonKey, keyboardType, userInterfaceStyle: options?.userInterfaceStyle || undefined, }, diff --git a/Libraries/Alert/NativeAlertManager.js b/Libraries/Alert/NativeAlertManager.js index 9597ef155a522d..65d815ec7962af 100644 --- a/Libraries/Alert/NativeAlertManager.js +++ b/Libraries/Alert/NativeAlertManager.js @@ -19,6 +19,7 @@ export type Args = {| defaultValue?: string, cancelButtonKey?: string, destructiveButtonKey?: string, + preferredButtonKey?: string, keyboardType?: string, userInterfaceStyle?: string, |}; diff --git a/React/CoreModules/RCTAlertManager.mm b/React/CoreModules/RCTAlertManager.mm index 7480580d9865d5..532e898faf05a0 100644 --- a/React/CoreModules/RCTAlertManager.mm +++ b/React/CoreModules/RCTAlertManager.mm @@ -94,6 +94,7 @@ - (void)invalidate NSString *defaultValue = [RCTConvert NSString:args.defaultValue()]; NSString *cancelButtonKey = [RCTConvert NSString:args.cancelButtonKey()]; NSString *destructiveButtonKey = [RCTConvert NSString:args.destructiveButtonKey()]; + NSString *preferredButtonKey = [RCTConvert NSString:args.preferredButtonKey()]; UIKeyboardType keyboardType = [RCTConvert UIKeyboardType:args.keyboardType()]; if (!title && !message) { @@ -175,32 +176,37 @@ - (void)invalidate buttonStyle = UIAlertActionStyleDestructive; } __weak RCTAlertController *weakAlertController = alertController; - [alertController - addAction:[UIAlertAction - actionWithTitle:buttonTitle - style:buttonStyle - handler:^(__unused UIAlertAction *action) { - switch (type) { - case RCTAlertViewStylePlainTextInput: - case RCTAlertViewStyleSecureTextInput: - callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]); - [weakAlertController hide]; - break; - case RCTAlertViewStyleLoginAndPasswordInput: { - NSDictionary *loginCredentials = @{ - @"login" : [weakAlertController.textFields.firstObject text], - @"password" : [weakAlertController.textFields.lastObject text] - }; - callback(@[ buttonKey, loginCredentials ]); - [weakAlertController hide]; - break; - } - case RCTAlertViewStyleDefault: - callback(@[ buttonKey ]); - [weakAlertController hide]; - break; - } - }]]; + + UIAlertAction *alertAction = + [UIAlertAction actionWithTitle:buttonTitle + style:buttonStyle + handler:^(__unused UIAlertAction *action) { + switch (type) { + case RCTAlertViewStylePlainTextInput: + case RCTAlertViewStyleSecureTextInput: + callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]); + [weakAlertController hide]; + break; + case RCTAlertViewStyleLoginAndPasswordInput: { + NSDictionary *loginCredentials = @{ + @"login" : [weakAlertController.textFields.firstObject text], + @"password" : [weakAlertController.textFields.lastObject text] + }; + callback(@[ buttonKey, loginCredentials ]); + [weakAlertController hide]; + break; + } + case RCTAlertViewStyleDefault: + callback(@[ buttonKey ]); + [weakAlertController hide]; + break; + } + }]; + [alertController addAction:alertAction]; + + if ([buttonKey isEqualToString:preferredButtonKey]) { + [alertController setPreferredAction:alertAction]; + } } if (!_alertControllers) { diff --git a/packages/rn-tester/js/examples/Alert/AlertExample.js b/packages/rn-tester/js/examples/Alert/AlertExample.js index 4e22ba4b6c62da..94f16e95111560 100644 --- a/packages/rn-tester/js/examples/Alert/AlertExample.js +++ b/packages/rn-tester/js/examples/Alert/AlertExample.js @@ -5,13 +5,15 @@ * LICENSE file in the root directory of this source tree. * * @format + * @flow */ -import React, {useState} from 'react'; +import * as React from 'react'; +import type {RNTesterModule} from '../../types/RNTesterTypes'; import {Alert, StyleSheet, Text, TouchableHighlight, View} from 'react-native'; // Shows log on the screen -const Log = ({message}) => +const Log = ({message}: {message: string}) => message ? ( @@ -42,7 +44,7 @@ const AlertWithDefaultButton = () => { }; const AlertWithTwoButtons = () => { - const [message, setMessage] = useState(''); + const [message, setMessage] = React.useState(''); const alertMessage = 'Your subscription has expired!'; @@ -66,7 +68,7 @@ const AlertWithTwoButtons = () => { }; const AlertWithThreeButtons = () => { - const [message, setMessage] = useState(''); + const [message, setMessage] = React.useState(''); const alertMessage = 'Do you want to save your changes?'; @@ -92,7 +94,7 @@ const AlertWithThreeButtons = () => { }; const AlertWithManyButtons = () => { - const [message, setMessage] = useState(''); + const [message, setMessage] = React.useState(''); const alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' + @@ -122,7 +124,7 @@ const AlertWithManyButtons = () => { }; const AlertWithCancelableTrue = () => { - const [message, setMessage] = useState(''); + const [message, setMessage] = React.useState(''); const alertMessage = 'Tapping outside this dialog will dismiss this alert.'; @@ -154,7 +156,7 @@ const AlertWithCancelableTrue = () => { }; const AlertWithStyles = () => { - const [message, setMessage] = useState(''); + const [message, setMessage] = React.useState(''); const alertMessage = 'Look at the button styles!'; @@ -190,6 +192,39 @@ const AlertWithStyles = () => { ); }; +const AlertWithStylesPreferred = () => { + const [message, setMessage] = React.useState(''); + + const alertMessage = + "The Preferred button is styled with 'preferred', so it is emphasized over the cancel button."; + + return ( + + + Alert.alert('Foo Title', alertMessage, [ + { + text: 'Preferred', + isPreferred: true, + onPress: () => setMessage('Preferred Pressed!'), + }, + { + text: 'Cancel', + style: 'cancel', + onPress: () => setMessage('Cancel Pressed!'), + }, + ]) + }> + + Tap to view alert + + + + + ); +}; + const styles = StyleSheet.create({ wrapper: { borderRadius: 5, @@ -208,12 +243,7 @@ const styles = StyleSheet.create({ }, }); -exports.title = 'Alerts'; -exports.description = - 'Alerts display a concise and informative message ' + - 'and prompt the user to make a decision.'; -exports.documentationURL = 'https://reactnative.dev/docs/alert'; -exports.examples = [ +export const examples = [ { title: 'Alert with default Button', description: @@ -262,4 +292,24 @@ exports.examples = [ return ; }, }, + { + title: 'Alert with styles + preferred', + platform: 'ios', + description: + "Alert buttons with 'isPreferred' will be emphasized, even over cancel buttons", + render(): React.Node { + return ; + }, + }, ]; + +export default ({ + framework: 'React', + title: 'Alerts', + category: 'UI', + documentationURL: 'https://reactnative.dev/docs/alert', + description: + 'Alerts display a concise and informative messageand prompt the user to make a decision.', + showIndividualExamples: true, + examples, +}: RNTesterModule); diff --git a/packages/rn-tester/js/examples/Alert/AlertIOSExample.js b/packages/rn-tester/js/examples/Alert/AlertIOSExample.js index 984ff8b6324cc7..47d7963935616a 100644 --- a/packages/rn-tester/js/examples/Alert/AlertIOSExample.js +++ b/packages/rn-tester/js/examples/Alert/AlertIOSExample.js @@ -19,7 +19,7 @@ const { Alert, } = require('react-native'); -const {examples: SharedAlertExamples} = require('./AlertExample'); +import {examples as SharedAlertExamples} from './AlertExample'; import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; diff --git a/packages/rn-tester/js/utils/RNTesterList.android.js b/packages/rn-tester/js/utils/RNTesterList.android.js index f150ea666b1109..97ffe9759a1b7c 100644 --- a/packages/rn-tester/js/utils/RNTesterList.android.js +++ b/packages/rn-tester/js/utils/RNTesterList.android.js @@ -142,7 +142,7 @@ const APIs: Array = [ { key: 'AlertExample', category: 'UI', - module: require('../examples/Alert/AlertExample'), + module: require('../examples/Alert/AlertExample').default, }, { key: 'AnimatedIndex',