baryshok-react-native-material-kit
Version:
Bringing Material Design to React Native
313 lines (270 loc) • 7.43 kB
JavaScript
//
// MDL-style Button component.
//
// - @see [MDL Button](http://www.getmdl.io/components/index.html#buttons-section)
// - [Props](#props)
// - [Defaults](#defaults)
// - [Built-in builders](#builders)
//
// Created by ywu on 15/7/2.
//
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import {
TouchableWithoutFeedback,
Text,
} from 'react-native';
import MKColor from '../MKColor';
import Ripple from './Ripple';
import * as utils from '../utils';
import { getTheme } from '../theme';
//
// ## <section id='Button'>Button</section>
// The `Button` component. With configurable shadow, ripple effect, and FAB style.
//
class Button extends Component {
constructor(props) {
super(props);
this.theme = getTheme();
this.state = {
width: 0,
height: 0,
};
}
_onLayout({ nativeEvent: { layout: { width, height } } }) {
if (width !== this.state.width || height !== this.state.height) {
this.setState({
width,
height,
});
}
}
_renderChildren() {
return this.props.children;
}
_onPressIn = (evt) => {
let ripple = this.refs.container;
ripple && ripple.showRipple();
this.props.onPressIn && this.props.onPressIn(evt);
}
_onPressOut = (evt) => {
let ripple = this.refs.container;
ripple && ripple.hideRipple();
this.props.onPressOut && this.props.onPressOut(evt);
}
render() {
const touchableProps = {};
if (this.props.enabled) {
Object.assign(touchableProps, utils.extractTouchableProps(this));
}
// fix #57 applying `onLayout` to <Ripple>, <TouchableXXX> clones `onLayout` to it's child
touchableProps.onLayout = this._onLayout.bind(this);
const fabStyle = {};
const maskProps = {};
if (this.props.fab) {
maskProps.maskBorderRadiusInPercent = 50;
if (this.state.width > 0 || this.state.height > 0) {
let size = Math.min(this.state.width, this.state.height);
if (!size || size <= 0) {
size = Math.max(this.state.width, this.state.height);
}
fabStyle.width = size;
fabStyle.height = size;
fabStyle.borderRadius = size / 2;
}
}
return (
<TouchableWithoutFeedback
{...touchableProps}
delayPressIn={100}
onPressIn={this._onPressIn}
onPressOut={this._onPressOut}
>
<Ripple ref="container"
{...this.props}
{...maskProps}
style={[
this.props.style,
fabStyle,
]}
>
{this._renderChildren()}
</Ripple>
</TouchableWithoutFeedback>
);
}
}
// ## <section id='props'>Props</section>
Button.propTypes = {
// [Ripple Props](Ripple.html#props)...
...Ripple.propTypes,
// [RN.TouchableWithoutFeedback Props](https://facebook.github.io/react-native/docs/touchablewithoutfeedback.html#props)...
...TouchableWithoutFeedback.propTypes,
// Whether this's a FAB
fab: PropTypes.bool,
// Whether the button is enabled
enabled: PropTypes.bool,
};
// ## <section id='defaults'>Defaults</section>
Button.defaultProps = {
// [Ripple defaults](Ripple.html#defaults)...
...Ripple.defaultProps,
pointerEvents: 'box-only',
enabled: true,
};
// --------------------------
// Builder
//
const { TextViewBuilder } = require('../builder');
//
// ## Button builder
// - @see [TextViewBuilder](../builder.html#TextViewBuilder)
//
class ButtonBuilder extends TextViewBuilder {
mergeStyle() {
this.choseBackgroundColor();
if (this.fab) {
this.styleFab();
}
super.mergeStyle();
}
// merge default FAB style with custom setting
styleFab() {
this.mergeStyleWith({
width: 48,
height: 48,
borderRadius: 24,
});
}
build() {
const theBuilder = this;
const props = this.toProps();
const BuiltButton = class extends Button {
_renderChildren() {
// use a text or a custom content
return theBuilder.text ? (
<Text style={theBuilder.textStyle || {}}>
{theBuilder.text}
</Text>
) : this.props.children;
}
};
BuiltButton.defaultProps = Object.assign({}, Button.defaultProps, props);
return BuiltButton;
}
}
// define builder method for each prop
ButtonBuilder.defineProps(Button.propTypes);
// ----------
// ## <section id="builders">Built-in builders</section>
//
// Colored raised button
// FIXME shadow not work on Android
// @see https://facebook.github.io/react-native/docs/known-issues.html#no-support-for-shadows-on-android
function coloredRaisedButton() {
return new ButtonBuilder()
.withStyle({
borderRadius: 2,
shadowRadius: 1,
shadowOffset: { width: 0, height: 0.5 },
shadowOpacity: 0.7,
shadowColor: 'black',
elevation: 4,
})
.withTextStyle({
color: 'white',
fontWeight: 'bold',
});
}
// Accent colored raised button
function accentColoredRaisedButton() {
return coloredRaisedButton().withAccent(true);
}
// Plain raised button
function plainRaisedButton() {
// FIXME doesn't support translucent bg, has shadow problems
return coloredRaisedButton()
// .withBackgroundColor(getTheme().bgPlain)
.withBackgroundColor(MKColor.Silver)
.withMaskColor(getTheme().bgPlain)
.withRippleColor(getTheme().bgPlain)
// .withStyle({
// shadowOffset: {width: 0.3, height: 0},
// })
.withTextStyle({
color: 'black',
fontWeight: 'bold',
});
}
// Plain flat button
function flatButton() {
return new ButtonBuilder()
.withBackgroundColor(MKColor.Transparent)
.withMaskColor(getTheme().bgPlain)
.withRippleColor(getTheme().bgPlain)
.withShadowAniEnabled(false)
.withStyle({
borderRadius: 2,
})
.withTextStyle({
color: 'black',
fontWeight: 'bold',
});
}
// Colored flat button
function coloredFlatButton() {
return flatButton()
.withRippleColor('rgba(255,255,255,0.2)')
.withTextStyle({
color: getTheme().primaryColor,
fontWeight: 'bold',
});
}
// Accent colored flat button
function accentColoredFlatButton() {
return flatButton()
.withRippleColor('rgba(255,255,255,0.2)')
.withTextStyle({
color: getTheme().accentColor,
fontWeight: 'bold',
});
}
// Colored FAB
function coloredFab() {
return new ButtonBuilder()
.withStyle({
shadowRadius: 1,
shadowOffset: { width: 0, height: 0.5 },
shadowOpacity: 0.4,
shadowColor: 'black',
elevation: 4,
})
.withFab(true)
.withRippleLocation('center');
}
// Accent colored FAB
function accentColoredFab() {
return coloredFab().withAccent(true);
}
// Plain FAB
function plainFab() {
// FIXME doesn't support translucent bg, has shadow problems
return coloredFab()
.withMaskColor(getTheme().bgPlain)
.withRippleColor(getTheme().bgPlain)
.withBackgroundColor(MKColor.Silver);
}
// ## Public interface
module.exports = Button;
Button.Builder = ButtonBuilder;
Button.button = plainRaisedButton;
Button.coloredButton = coloredRaisedButton;
Button.accentColoredButton = accentColoredRaisedButton;
Button.flatButton = flatButton;
Button.coloredFlatButton = coloredFlatButton;
Button.accentColoredFlatButton = accentColoredFlatButton;
Button.plainFab = plainFab;
Button.coloredFab = coloredFab;
Button.accentColoredFab = accentColoredFab;