Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Libraries/Alert/Alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
export type Buttons = Array<{
text?: string,
onPress?: ?Function,
isPreferred?: boolean,
style?: AlertButtonStyle,
...
}>;
Expand Down Expand Up @@ -113,6 +114,7 @@ class Alert {
const buttons = [];
let cancelButtonKey;
let destructiveButtonKey;
let preferredButtonKey;
if (typeof callbackOrButtons === 'function') {
callbacks = [callbackOrButtons];
} else if (Array.isArray(callbackOrButtons)) {
Expand All @@ -122,6 +124,8 @@ class Alert {
cancelButtonKey = String(index);
} else if (btn.style === 'destructive') {
destructiveButtonKey = String(index);
} else if (btn.isPreferred) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robbie-c shouldn't this just be an if? It shouldn't depend on the style?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, whilst preferred and destructive seem mutually exclusive visually (it's been a while since I wrote this so I don't remember which one "wins" if you apply both), if we're just passing through flags to the iOS API in an unopionated way it makes sense to match it, so I'll make this change.

preferredButtonKey = String(index);
}
if (btn.text || index < (callbackOrButtons || []).length - 1) {
const btnDef = {};
Expand All @@ -140,6 +144,7 @@ class Alert {
defaultValue,
cancelButtonKey,
destructiveButtonKey,
preferredButtonKey,
keyboardType,
},
(id, value) => {
Expand Down
1 change: 1 addition & 0 deletions Libraries/Alert/NativeAlertManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type Args = {|
defaultValue?: string,
cancelButtonKey?: string,
destructiveButtonKey?: string,
preferredButtonKey?: string,
keyboardType?: string,
|};

Expand Down
56 changes: 31 additions & 25 deletions React/CoreModules/RCTAlertManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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) {
Expand Down Expand Up @@ -152,32 +153,37 @@ - (void)invalidate
buttonStyle = UIAlertActionStyleDestructive;
}
__weak RCTAlertController *weakAlertController = alertController;

UIAlertAction *action = [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<NSString *, NSString *> *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:[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<NSString *, NSString *> *loginCredentials = @{
@"login" : [weakAlertController.textFields.firstObject text],
@"password" : [weakAlertController.textFields.lastObject text]
};
callback(@[ buttonKey, loginCredentials ]);
[weakAlertController hide];
break;
}
case RCTAlertViewStyleDefault:
callback(@[ buttonKey ]);
[weakAlertController hide];
break;
}
}]];
addAction:action];

if ([buttonKey isEqualToString:preferredButtonKey]) {
[alertController setPreferredAction:action];
}
}

if (!_alertControllers) {
Expand Down
42 changes: 42 additions & 0 deletions packages/rn-tester/js/examples/Alert/AlertExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,39 @@ const AlertWithStyles = () => {
);
};

const AlertWithStylesPreferred = () => {
const [message, setMessage] = useState('');

const alertMessage =
"The OK button is styled with 'preferred', so it is emphasized over the cancel button.";

return (
<View>
<TouchableHighlight
style={styles.wrapper}
onPress={() =>
Alert.alert('Foo Title', alertMessage, [
{
text: 'OK',
isPreferred: 'preferred',
onPress: () => setMessage('OK Pressed!'),
},
{
text: 'Cancel',
style: 'cancel',
onPress: () => setMessage('Cancel Pressed!'),
},
])
}>
<View style={styles.button}>
<Text>Tap to view alert</Text>
</View>
</TouchableHighlight>
<Log message={message} />
</View>
);
};

const styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
Expand Down Expand Up @@ -262,4 +295,13 @@ exports.examples = [
return <AlertWithStyles />;
},
},
{
title: 'Alert with styles + preferred',
platform: 'ios',
description:
"Alert buttons with 'isPreferred' will be emphasized, even over cancel buttons",
render(): React.Node {
return <AlertWithStylesPreferred />;
},
},
];