Permalink
Browse files

Introduce Button Component

Summary:
Button is an important component to help the community get onboarded with RN quickly, so the first few minutes of a developer's experience is not spent formatting a simple button component.

In my opinion, `<Button />` should be seen as a "lowest common demoniator" component, rather than "the one button to rule them all". In other words, we should only support features in Button that will work on any platform. We should encourage people to fork Button if they need to add specific features to it, rather than trying to twist and bloat this component until it supports everything.

These platform imitations may not have the perfect constants just yet, but they are good enough to make a user feel at home in the app, without any modification. The community can help tweak the final formatting to make them look just right- PRs are welcome!

Reviewed By: frantic

Differential Revision: D3929041

fbshipit-source-id: 3785fb67472a7614eeee0a9aef504c0bdf62ede7
  • Loading branch information...
ericvicenti authored and Facebook Github Bot committed Oct 11, 2016
1 parent 8e91843 commit 2ae73ffa002221fde8011b012ea5e57e22979232
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {
Alert,
Button,
View,
} = ReactNative;
const onButtonPress = () => {
Alert.alert('Button has been pressed!');
};
exports.displayName = 'ButtonExample';
exports.framework = 'React';
exports.title = '<Button>';
exports.description = 'Simple React Native button component.';
exports.examples = [
{
title: 'Simple Button',
description: 'The title and onPress handler are required. It is ' +
'recommended to set accessibilityLabel to help make your app usable by ' +
'everyone.',
render: function() {
return (
<Button
onPress={onButtonPress}
title="Press Me"
accessibilityLabel="See an informative alert"
/>
);
},
},
{
title: 'Adjusted color',
description: 'Adjusts the color in a way that looks standard on each ' +
'platform. On iOS, the color prop controls the color of the text. On ' +
'Android, the color adjusts the background color of the button.',
render: function() {
return (
<Button
onPress={onButtonPress}
title="Press Purple"
color="#841584"
accessibilityLabel="Learn more about purple"
/>
);
},
},
{
title: 'Fit to text layout',
description: 'This layout strategy lets the title define the width of ' +
'the button',
render: function() {
return (
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Button
onPress={onButtonPress}
title="This looks great!"
accessibilityLabel="This sounds great!"
/>
<Button
onPress={onButtonPress}
title="Ok!"
color="#841584"
accessibilityLabel="Ok, Great!"
/>
</View>
);
},
},
];
@@ -32,6 +32,10 @@ const ComponentExamples: Array<UIExplorerExample> = [
key: 'ActivityIndicatorExample',
module: require('./ActivityIndicatorExample'),
},
{
key: 'ButtonExample',
module: require('./ButtonExample'),
},
{
key: 'ImageExample',
module: require('./ImageExample'),
@@ -32,6 +32,10 @@ const ComponentExamples: Array<UIExplorerExample> = [
key: 'ActivityIndicatorExample',
module: require('./ActivityIndicatorExample'),
},
{
key: 'ButtonExample',
module: require('./ButtonExample'),
},
{
key: 'DatePickerIOSExample',
module: require('./DatePickerIOSExample'),
@@ -0,0 +1,144 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Button
* @flow
*/
'use strict';
const ColorPropType = require('ColorPropType');
const Platform = require('Platform');
const React = require('React');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableNativeFeedback = require('TouchableNativeFeedback');
const TouchableOpacity = require('TouchableOpacity');
const View = require('View');
const invariant = require('invariant');
/**
* A basic button component that should render nicely on any platform. Supports
* a minimal level of customization.
*
* <center><img src="img/buttonExample.png"></img></center>
*
* If this button doesn't look right for your app, you can build your own
* button using [TouchableOpacity](https://facebook.github.io/react-native/docs/touchableopacity.html)
* or [TouchableNativeFeedback](https://facebook.github.io/react-native/docs/touchablenativefeedback.html).
* For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js).
* Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button).
*
* Example usage:
*
* ```
* <Button
* onPress={onPressLearnMore}
* title="Learn More"
* color="#841584"
* accessibilityLabel="Learn more about this purple button"
* />
* ```
*
*/
class Button extends React.Component {
props: {
title: string,
onPress: () => any,
color?: ?string,
accessibilityLabel?: ?string,
};
static propTypes = {
/**
* Text to display inside the button
*/
title: React.PropTypes.string.isRequired,
/**
* Text to display for blindness accessibility features
*/
accessibilityLabel: React.PropTypes.string,
/**
* Color of the text (iOS), or background color of the button (Android)
*/
color: ColorPropType,
/**
* Handler to be called when the user taps the button
*/
onPress: React.PropTypes.func.isRequired,

This comment has been minimized.

Show comment
Hide comment
@leeight

leeight Oct 11, 2016

Contributor

onPress could be optional?

@leeight

leeight Oct 11, 2016

Contributor

onPress could be optional?

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Oct 11, 2016

Contributor

In what case would we want that? I could see it being optional if we ever introduced a 'disabled' prop

@ericvicenti

ericvicenti Oct 11, 2016

Contributor

In what case would we want that? I could see it being optional if we ever introduced a 'disabled' prop

This comment has been minimized.

Show comment
Hide comment
@satya164

satya164 Oct 11, 2016

Collaborator

@ericvicenti a disabled prop is very useful actually :)

@satya164

satya164 Oct 11, 2016

Collaborator

@ericvicenti a disabled prop is very useful actually :)

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Oct 12, 2016

Contributor

Agreed! Just posted an issue for it: #10354

@ericvicenti

ericvicenti Oct 12, 2016

Contributor

Agreed! Just posted an issue for it: #10354

};
render() {
const {
accessibilityLabel,
color,
onPress,
title,
} = this.props;
const buttonStyles = [styles.button];

This comment has been minimized.

Show comment
Hide comment
@blackavec

blackavec Nov 2, 2016

i personally prefer to have freedom on the view style of a component, what i mean is that it's better to refactor the code to become like this to accept the style from outside of the component too.

const buttonStyles = [styles.button, this.props.style];

and then make this.props.style optional in validation

@blackavec

blackavec Nov 2, 2016

i personally prefer to have freedom on the view style of a component, what i mean is that it's better to refactor the code to become like this to accept the style from outside of the component too.

const buttonStyles = [styles.button, this.props.style];

and then make this.props.style optional in validation

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Nov 2, 2016

Contributor

Yes, but then it is very easy to break the component by putting some random styles in props.style. I think it is more elegant to handle the style in leaf components, to avoid passing style props everywhere in your application.

If you want to customize the look of the button, why not use a different component? This component is optimized for beginners who don't need a custom appearance.

@ericvicenti

ericvicenti Nov 2, 2016

Contributor

Yes, but then it is very easy to break the component by putting some random styles in props.style. I think it is more elegant to handle the style in leaf components, to avoid passing style props everywhere in your application.

If you want to customize the look of the button, why not use a different component? This component is optimized for beginners who don't need a custom appearance.

const textStyles = [styles.text];

This comment has been minimized.

Show comment
Hide comment
@chirag04

chirag04 Oct 11, 2016

Collaborator

we can support textStyles and buttonStyles as props while still keeping this as a small component.

@chirag04

chirag04 Oct 11, 2016

Collaborator

we can support textStyles and buttonStyles as props while still keeping this as a small component.

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Oct 11, 2016

Contributor

Yeah, but then this component would be very easy to break, and won't update over time to reflect latest platform standards. If people want to customize the styles, they can use something else or fork this component.

@ericvicenti

ericvicenti Oct 11, 2016

Contributor

Yeah, but then this component would be very easy to break, and won't update over time to reflect latest platform standards. If people want to customize the styles, they can use something else or fork this component.

This comment has been minimized.

Show comment
Hide comment
@johncblandii

johncblandii Nov 2, 2016

How will a button with rounded corners exist without allowing at least some styles?

@johncblandii

johncblandii Nov 2, 2016

How will a button with rounded corners exist without allowing at least some styles?

This comment has been minimized.

Show comment
Hide comment
@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii it's very simple to write your own button component with custom styles.

@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii it's very simple to write your own button component with custom styles.

This comment has been minimized.

Show comment
Hide comment
@johncblandii

johncblandii Nov 2, 2016

Of course it is, but that's not really the point, right?

This is intended to be a core component available for a basic Button component. I think a basic component should at a minimum have some basic styles and one of those basic styles is borderRadius to make a rounded button.

@johncblandii

johncblandii Nov 2, 2016

Of course it is, but that's not really the point, right?

This is intended to be a core component available for a basic Button component. I think a basic component should at a minimum have some basic styles and one of those basic styles is borderRadius to make a rounded button.

This comment has been minimized.

Show comment
Hide comment
@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii this is a very basic component so that you could get started with building the app quickly. I don't think it's meant to be a core component for you to use in production apps.

@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii this is a very basic component so that you could get started with building the app quickly. I don't think it's meant to be a core component for you to use in production apps.

This comment has been minimized.

Show comment
Hide comment
@johncblandii

johncblandii Nov 2, 2016

Why would you use a component in dev then rip it out in prod, @satya164?

This component, from the looks of it, is 100% intended for prod use. If that is the case, it seems some basic styling would be welcomed.

@johncblandii

johncblandii Nov 2, 2016

Why would you use a component in dev then rip it out in prod, @satya164?

This component, from the looks of it, is 100% intended for prod use. If that is the case, it seems some basic styling would be welcomed.

This comment has been minimized.

Show comment
Hide comment
@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii read other comments. this component is only intended for getting started with the app. you'll probably need a custom one.

Why would you use a component in dev then rip it out in prod

Sometimes I want to test something out of prototype something quickly without having to spend time styling. This button is a good fit for that. But I'll probably fix up styling later and replace this one with my custom component.

@satya164

satya164 Nov 2, 2016

Collaborator

@johncblandii read other comments. this component is only intended for getting started with the app. you'll probably need a custom one.

Why would you use a component in dev then rip it out in prod

Sometimes I want to test something out of prototype something quickly without having to spend time styling. This button is a good fit for that. But I'll probably fix up styling later and replace this one with my custom component.

This comment has been minimized.

Show comment
Hide comment
@johncblandii

johncblandii Nov 4, 2016

I've read them. We never proto with base components we're not using.

But...your world. Just providing insight into a small setting that would make this prod worthy.

@johncblandii

johncblandii Nov 4, 2016

I've read them. We never proto with base components we're not using.

But...your world. Just providing insight into a small setting that would make this prod worthy.

const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
if (color && Platform.OS === 'ios') {
textStyles.push({color: color});
} else if (color) {
buttonStyles.push({backgroundColor: color});
}
invariant(
typeof title === 'string',
'The title prop of a Button must be a string',
);
const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title;

This comment has been minimized.

Show comment
Hide comment
@rturk

rturk Oct 12, 2016

This looks opinionated in favor of Material-UI. I think it goes against original ideia of a "lowest common demoniator" component, rather than "the one button to rule them all". Also there is no way to override this behavior.

@rturk

rturk Oct 12, 2016

This looks opinionated in favor of Material-UI. I think it goes against original ideia of a "lowest common demoniator" component, rather than "the one button to rule them all". Also there is no way to override this behavior.

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Oct 12, 2016

Contributor

Material-UI seems to encourage buttons to be all-caps. Because Material-UI has only really caught on in the Android world, we only capitalize the button title on Android. Unspecified platforms like Windows will have a button with the standard title. So we aren't really opinionated in favor of Material, except for Android where it is standard.

If you want a different behavior, you can always use a different component or build your own button.

@ericvicenti

ericvicenti Oct 12, 2016

Contributor

Material-UI seems to encourage buttons to be all-caps. Because Material-UI has only really caught on in the Android world, we only capitalize the button title on Android. Unspecified platforms like Windows will have a button with the standard title. So we aren't really opinionated in favor of Material, except for Android where it is standard.

If you want a different behavior, you can always use a different component or build your own button.

return (
<Touchable

This comment has been minimized.

Show comment
Hide comment
@blackavec

blackavec Nov 2, 2016

as you know the default UI in android and IOS are deferent and in android most of the components are not really interactive with user behaviors like touch but in IOS is deferent and some components should use touchableOpacity in order to make it more look like the native component.

image

and when u press it:
screen shot 2016-11-02 at 10 12 15 am 2

i suggest you to make separate UI for android and IOS then you can handle it easier

@blackavec

blackavec Nov 2, 2016

as you know the default UI in android and IOS are deferent and in android most of the components are not really interactive with user behaviors like touch but in IOS is deferent and some components should use touchableOpacity in order to make it more look like the native component.

image

and when u press it:
screen shot 2016-11-02 at 10 12 15 am 2

i suggest you to make separate UI for android and IOS then you can handle it easier

accessibilityComponentType="button"
accessibilityLabel={accessibilityLabel}
accessibilityTraits={['button']}
onPress={onPress}>
<View style={buttonStyles}>
<Text style={textStyles}>{formattedTitle}</Text>
</View>
</Touchable>
);
}
}
// Material design blue from https://material.google.com/style/color.html#color-color-palette
let defaultBlue = '#2196F3';
if (Platform.OS === 'ios') {
// Measured default tintColor from iOS 10
defaultBlue = '#0C42FD';
}
const styles = StyleSheet.create({
button: Platform.select({
ios: {},
android: {
elevation: 4,
backgroundColor: defaultBlue,
borderRadius: 2,
},
}),
text: Platform.select({
ios: {
color: defaultBlue,
textAlign: 'center',
padding: 8,
fontSize: 18,
},
android: {
textAlign: 'center',
color: 'white',
padding: 8,
fontWeight: '500',
},
}),
});
module.exports = Button;
@@ -30,6 +30,7 @@ const ReactNative = {
get ActivityIndicator() { return require('ActivityIndicator'); },
get ActivityIndicatorIOS() { return require('ActivityIndicatorIOS'); },
get ART() { return require('ReactNativeART'); },
get Button() { return require('Button'); },
get DatePickerIOS() { return require('DatePickerIOS'); },
get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); },
get Image() { return require('Image'); },
@@ -10,16 +10,15 @@
'use strict';
const assert = require('assert');
const babel = require('babel-core');
const deepAssign = require('deep-assign');
const docgen = require('react-docgen');
const docgenHelpers = require('./docgenHelpers');
const fs = require('fs');
const jsDocs = require('../jsdocs/jsdocs.js');
const jsdocApi = require('jsdoc-api');
const path = require('path');
const slugify = require('../core/slugify');
const babel = require('babel-core');
const jsdocApi = require('jsdoc-api');
const deepAssign = require('deep-assign');
const ANDROID_SUFFIX = 'android';
const CROSS_SUFFIX = 'cross';
@@ -488,6 +487,7 @@ function renderStyle(filepath) {
const components = [
'../Libraries/Components/ActivityIndicator/ActivityIndicator.js',
'../Libraries/Components/ActivityIndicator/ActivityIndicatorIOS.ios.js',
'../Libraries/Components/Button.js',
'../Libraries/Components/DatePicker/DatePickerIOS.ios.js',
'../Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js',
'../Libraries/Image/Image.ios.js',
Binary file not shown.

5 comments on commit 2ae73ff

@musicode

This comment has been minimized.

Show comment
Hide comment
@musicode

musicode Nov 1, 2016

icon supported?

icon supported?

@ericvicenti

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Nov 1, 2016

Contributor

Nope, icons are not supported because react-native does not have a built-in icon toolkit

Contributor

ericvicenti replied Nov 1, 2016

Nope, icons are not supported because react-native does not have a built-in icon toolkit

@CharlesMangwa

This comment has been minimized.

Show comment
Hide comment
@CharlesMangwa

CharlesMangwa Nov 2, 2016

Quick question: does it also support the ripple effect on Android?

CharlesMangwa replied Nov 2, 2016

Quick question: does it also support the ripple effect on Android?

@ericvicenti

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Nov 2, 2016

Contributor

Yep, because it uses TouchableNativeFeedback!

Contributor

ericvicenti replied Nov 2, 2016

Yep, because it uses TouchableNativeFeedback!

@ariabuckles

This comment has been minimized.

Show comment
Hide comment
@ariabuckles

ariabuckles Nov 10, 2016

Contributor

Just saw this in the release notes (and hope it's okay to post here). This is lovely, and something that felt really absent in RN before when trying to build a new app/pages quickly (as opposed to wanting to build a fully custom button that works a specific way). Thank you for making it <3.

Contributor

ariabuckles replied Nov 10, 2016

Just saw this in the release notes (and hope it's okay to post here). This is lovely, and something that felt really absent in RN before when trying to build a new app/pages quickly (as opposed to wanting to build a fully custom button that works a specific way). Thank you for making it <3.

Please sign in to comment.