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...
1 parent 8e91843 commit 2ae73ffa002221fde8011b012ea5e57e22979232 @ericvicenti ericvicenti committed with Facebook Github Bot Oct 11, 2016
@@ -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>
+ );
+ },
+ },
+];
@@ -33,6 +33,10 @@ const ComponentExamples: Array<UIExplorerExample> = [
module: require('./ActivityIndicatorExample'),
},
{
+ key: 'ButtonExample',
+ module: require('./ButtonExample'),
+ },
+ {
key: 'ImageExample',
module: require('./ImageExample'),
},
@@ -33,6 +33,10 @@ const ComponentExamples: Array<UIExplorerExample> = [
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,
@leeight
leeight Oct 11, 2016 Contributor

onPress could be optional?

@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

@satya164
satya164 Oct 11, 2016 Contributor

@ericvicenti a disabled prop is very useful actually :)

@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];
@mahanhaz
mahanhaz 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

@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];
@chirag04
chirag04 Oct 11, 2016 Contributor

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

@ericvicenti
ericvicenti Oct 11, 2016 edited 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.

@johncblandii
johncblandii Nov 2, 2016

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

@satya164
satya164 Nov 2, 2016 Contributor

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

@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.

@satya164
satya164 Nov 2, 2016 Contributor

@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.

@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.

@satya164
satya164 Nov 2, 2016 Contributor

@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.

@johncblandii
johncblandii Nov 4, 2016 edited

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;
@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.

@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
@mahanhaz
mahanhaz 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',
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

5 comments on commit 2ae73ff

@musicode

icon supported?

@ericvicenti
Contributor

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

@CharlesMangwa
CharlesMangwa commented on 2ae73ff Nov 2, 2016 edited

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

@ericvicenti
Contributor

Yep, because it uses TouchableNativeFeedback!

@ariabuckles
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.

Please sign in to comment.