Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add props to Button to get basic styles seen across iOS/Android platforms #17065

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
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
212 changes: 170 additions & 42 deletions Libraries/Components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableNativeFeedback = require('TouchableNativeFeedback');
const TouchableOpacity = require('TouchableOpacity');
const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
const View = require('View');

const invariant = require('fbjs/lib/invariant');
Expand Down Expand Up @@ -52,14 +53,20 @@ const invariant = require('fbjs/lib/invariant');
*/

class Button extends React.Component<{
title: string,
onPress: () => any,
color?: ?string,
accessibilityLabel?: ?string,
bold?: ?boolean,
bordered?: ?boolean,
color?: ?string,
disabled?: ?boolean,
testID?: ?string,
hasTVPreferredFocus?: ?boolean,
}> {
noShadow?: ?boolean,
onPress: () => any,
testID?: ?string,
title: string,
transparent?: ?boolean,
}, {
activeAnim: typeof Animated.Value
}> {
static propTypes = {
/**
* Text to display inside the button
Expand Down Expand Up @@ -91,96 +98,217 @@ class Button extends React.Component<{
* @platform ios
*/
hasTVPreferredFocus: PropTypes.bool,
/**
* *(iOS only)* If true, title will be be bold (on Android the title is always bold regardless of this prop).
*
* @platform ios
*/
bold: PropTypes.bool,
/**
* *(Android only)* If true color will affect the title, and background will be transparent. On iOS the background is always transparent regardless of this. If set transparent to true, you cannot set bordered to true.
*
* @platform android
*/
transparent: PropTypes.bool,
/**
* *(Android only)* If true, no shadow is drawn.
*
* @platform android
*/
noShadow: PropTypes.bool,
/**
* The border and title will be same as "color". On Android, the background is white, and on iOS the background remains transparent. iOS also has a different onPressIn and onPressOut animation where the text goes to white and the background goes to "color".
*/
bordered: PropTypes.bool,
};

state = {
activeAnim: new Animated.Value(0)
}

render() {
const {
accessibilityLabel,
bold,
bordered,
color,
onPress,
title,
hasTVPreferredFocus,
disabled,
hasTVPreferredFocus,
noShadow,
onPress,
testID,
title,
transparent
} = this.props;
const { activeAnim } = this.state;

const isIOS = Platform.OS === 'ios';
const isAndroid = !isIOS;

const buttonStyles = [styles.button];
const textStyles = [styles.text];
if (color) {
if (Platform.OS === 'ios') {
textStyles.push({color: color});
} else {
buttonStyles.push({backgroundColor: color});
}
}
const accessibilityTraits = ['button'];

if (disabled) {
accessibilityTraits.push('disabled');
buttonStyles.push(styles.buttonDisabled);
textStyles.push(styles.textDisabled);

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

accessibilityTraits.push('disabled');
if (bordered) buttonStyles.push(styles.buttonBordered, styles.buttonBorderedDisabled);

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

if (isAndroid) {
if (transparent) buttonStyles.push(styles.buttonTransparentDisabledAndroid);
}
} else {
if (transparent && isAndroid) {
buttonStyles.push(styles.buttonTransparentAndroid);
}
if (bordered && !(isAndroid && transparent)) {
buttonStyles.push(styles.buttonBordered);
}

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

if (color) {

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

if (bordered) buttonStyles.push({ borderColor: color });

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

if (isIOS || (isAndroid && (transparent || bordered))) textStyles.push({ color });
else if (!transparent) buttonStyles.push({ backgroundColor: color }); // isAndroid on this line

Choose a reason for hiding this comment

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

curly: Expected { after 'if' condition.

} else {
if (isAndroid && (transparent || bordered)) textStyles.push(styles.textBorderedAndroid);
}
if (noShadow && isAndroid && !bordered && !transparent) {
buttonStyles.push(styles.buttonUnelevatedAndroid);
}
}

if (bold && isIOS) {
textStyles.push(styles.textBold);
}
invariant(
typeof title === 'string',
'The title prop of a Button must be a string',
);
const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title;
const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
return (
<Touchable
accessibilityComponentType="button"
accessibilityLabel={accessibilityLabel}
accessibilityTraits={accessibilityTraits}
hasTVPreferredFocus={hasTVPreferredFocus}
testID={testID}
disabled={disabled}
onPress={onPress}>
const formattedTitle = isAndroid ? title.toUpperCase() : title;
const Touchable = isAndroid ? TouchableNativeFeedback : TouchableOpacity;
if (isIOS && bordered) {
return (
<View style={buttonStyles}>
<Text style={textStyles} disabled={disabled}>{formattedTitle}</Text>

Choose a reason for hiding this comment

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

react/jsx-no-undef: 'Animated' is not defined.

<TouchableWithoutFeedback accessibilityComponentType="button" accessibilityLabel={accessibilityLabel} accessibilityTraits={accessibilityTraits} disabled={disabled} hasTVPreferredFocus={hasTVPreferredFocus} onPress={onPress} onPressIn={this.activateIOS} onPressOut={this.deactivateIOS} testID={testID}>
<Animated.View style={[{ opacity: activeAnim }, styles.buttonBorderedActiveIOS, color && { backgroundColor: color }]}>
<Text style={[styles.text, styles.textBorderedActiveIOS, bold && styles.textBold]}>{formattedTitle}</Text>
</Animated.View>
</TouchableWithoutFeedback>
</View>
</Touchable>

Choose a reason for hiding this comment

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

semi: Missing semicolon.

);
)
} else {
return (
<Touchable accessibilityComponentType="button" accessibilityLabel={accessibilityLabel} accessibilityTraits={accessibilityTraits} disabled={disabled} hasTVPreferredFocus={hasTVPreferredFocus} onPress={onPress} testID={testID}>
<View style={buttonStyles}>
<Text style={textStyles} disabled={disabled}>{formattedTitle}</Text>
</View>

Choose a reason for hiding this comment

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

semi: Missing semicolon.

</Touchable>
)
}
}

activateIOS = () => {
const { activeAnim } = this.state;
activeAnim.stopAnimation();
Animated.timing(activeAnim, { toValue: 1, duration: 200, useNativeDriver: true }).start();
}
deactivateIOS = () => {
const { activeAnim } = this.state;
activeAnim.stopAnimation();
Animated.timing(activeAnim, { toValue: 0, duration: 200, useNativeDriver: true }).start();
}
}

const styles = StyleSheet.create({
button: Platform.select({
ios: {},
ios: undefined,
android: {
elevation: 4,
// Material design blue from https://material.google.com/style/color.html#color-color-palette
backgroundColor: '#2196F3',
borderRadius: 2,
backgroundColor: '#2196F3', // Material design blue from https://material.google.com/style/color.html#color-color-palette
borderRadius: 2
}
}),
buttonTransparentAndroid: {
backgroundColor: 'transparent',
elevation: 0
},
buttonBordered: Platform.select({
ios: {
borderWidth: 1,
borderColor: '#007AFF',
borderRadius: 6,
overflow: 'hidden' // otherwse backgroundColor onPress messes up borderRadius
},
android: {
elevation: 0,
borderColor: '#2196F3',
borderWidth: 1,
backgroundColor: '#FFFFFF'
}
}),
buttonUnelevatedAndroid: {
elevation: 0
},
text: Platform.select({
ios: {
// iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
color: '#007AFF',
color: '#007AFF', // iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
textAlign: 'center',
padding: 8,
fontSize: 18,
paddingVertical: 12,
fontSize: 18
},
android: {
color: 'white',
color: '#FFFFFF',
textAlign: 'center',
padding: 8,
fontWeight: '500',
fontWeight: '500'
}
}),
textBold: Platform.select({
ios: {
fontWeight: '500'
},
android: undefined
}),
textBorderedAndroid: {
color: '#2196F3'
},
buttonDisabled: Platform.select({
ios: {},
ios: undefined,
android: {
elevation: 0,
backgroundColor: '#dfdfdf',
backgroundColor: '#DFDFDF'
}
}),
buttonBorderedDisabled: Platform.select({
ios: {
borderColor: '#CDCDCD'
},
android: {
borderColor: '#DFDFDF',
backgroundColor: '#FFFFFF'
}
}),
buttonTransparentDisabledAndroid: {
backgroundColor: 'transparent'
},
textDisabled: Platform.select({
ios: {
color: '#cdcdcd',
color: '#CDCDCD'
},
android: {
color: '#a1a1a1',
color: '#A1A1A1'
}
}),
textBorderedActiveIOS: {
color: '#FFFFFF'
},
buttonBorderedActiveIOS: {
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#007AFF'
}
});

module.exports = Button;