hypertoxin
Version:
A themeable ReactNative component library for developing native apps
1,273 lines (1,224 loc) • 54.6 kB
JavaScript
/**
* Copyright 2016-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module RaisedButton
* @description - Raised button component.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
* @flow
*/
'use strict'; // eslint-disable-line
import React from 'react';
import ReactNative from 'react-native'; // eslint-disable-line
import PropTypes from 'prop-types';
import { View as AnimatedView } from 'react-native-animatable';
import {
DefaultTheme,
DefaultThemeContext
} from '../../themes/default-theme';
const {
Animated,
Easing,
StyleSheet,
Text,
TouchableOpacity,
View
} = ReactNative;
const DEFAULT_TOUCH_PRESS_DEBOUNCE_TIME_MS = 250;
const DEFAULT_ANIMATION_DURATION_MS = 300;
const DEFAULT_DROP_SHADOW_STYLE = {
shadowColor: `#000000`,
shadowRadius: 1,
shadowOpacity: 0.25,
shadowOffset: {
width: 0,
height: 1
}
};
const DEFAULT_RAISED_BUTTON_STYLE = {
small: {
container: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
justifyContent: `center`,
alignItems: `center`,
height: DefaultTheme.button.size.raised.small,
padding: 3
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.small - 6,
backgroundColor: `transparent`
},
contentMiddleRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.small - 6,
maxHeight: DefaultTheme.button.size.raised.small - 6,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.small - 6,
backgroundColor: `transparent`
},
badgeRoom: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
position: `absolute`,
alignSelf: `stretch`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.misc.size.badge.small,
height: DefaultTheme.misc.size.badge.small,
top: -9,
right: -9,
borderRadius: DefaultTheme.misc.size.badge.small / 2
},
activityIndicatorRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.small - 6,
maxHeight: DefaultTheme.button.size.raised.small - 6,
backgroundColor: `transparent`
},
label: {
...DefaultTheme.button.font.raised.label.small,
paddingHorizontal: 6
},
ripple: {
position: `absolute`,
width: DefaultTheme.button.size.raised.small,
height: DefaultTheme.button.size.raised.small,
borderRadius: DefaultTheme.button.size.raised.small / 2,
overflow: `hidden`
}
},
normal: {
container: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
justifyContent: `center`,
alignItems: `center`,
height: DefaultTheme.button.size.raised.normal,
padding: 3
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.normal - 6,
backgroundColor: `transparent`
},
contentMiddleRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.normal - 6,
maxHeight: DefaultTheme.button.size.raised.normal - 6,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.normal - 6,
backgroundColor: `transparent`
},
badgeRoom: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
position: `absolute`,
alignSelf: `stretch`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.misc.size.badge.normal,
height: DefaultTheme.misc.size.badge.normal,
top: -12,
right: -12,
borderRadius: DefaultTheme.misc.size.badge.normal / 2
},
activityIndicatorRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.normal - 6,
maxHeight: DefaultTheme.button.size.raised.normal - 6,
backgroundColor: `transparent`
},
label: {
...DefaultTheme.button.font.raised.label.normal,
paddingHorizontal: 6
},
ripple: {
position: `absolute`,
width: DefaultTheme.button.size.raised.normal,
height: DefaultTheme.button.size.raised.normal,
borderRadius: DefaultTheme.button.size.raised.normal / 2,
overflow: `hidden`
}
},
large: {
container: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
justifyContent: `center`,
alignItems: `center`,
height: DefaultTheme.button.size.raised.large,
padding: 3
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.large - 6,
backgroundColor: `transparent`
},
contentMiddleRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.large - 6,
maxHeight: DefaultTheme.button.size.raised.large - 6,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
maxHeight: DefaultTheme.button.size.raised.large - 6,
backgroundColor: `transparent`
},
badgeRoom: {
...DEFAULT_DROP_SHADOW_STYLE,
flexDirection: `row`,
position: `absolute`,
alignSelf: `stretch`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.misc.size.badge.large,
height: DefaultTheme.misc.size.badge.large,
top: -15,
right: -15,
borderRadius: DefaultTheme.misc.size.badge.large / 2
},
activityIndicatorRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
minWidth: DefaultTheme.button.size.raised.large - 6,
maxHeight: DefaultTheme.button.size.raised.large - 6,
backgroundColor: `transparent`
},
label: {
...DefaultTheme.button.font.raised.label.large,
paddingHorizontal: 6
},
ripple: {
position: `absolute`,
width: DefaultTheme.button.size.raised.large,
height: DefaultTheme.button.size.raised.large,
borderRadius: DefaultTheme.button.size.raised.large / 2,
overflow: `hidden`
}
}
};
const readjustStyle = (newStyle = {
shade: `themed`,
corner: `themed`,
margin: `themed`,
disabled: false,
busy: false,
color: `primary`
}, prevAdjustedStyle = DEFAULT_RAISED_BUTTON_STYLE, Theme = DefaultTheme) => {
const {
shade,
corner,
margin,
disabled,
busy,
color,
style
} = newStyle;
const themedShade = shade === `themed` ? Theme.button.raised.shade : shade;
const themedBadgeShade = shade === `themed` ? Theme.misc.badge.shade : shade;
const nullMargin = {
margin: null,
marginTop: null,
marginBottom: null,
marginLeft: null,
marginRight: null,
marginHorizontal: null,
marginVertical: null
};
const nullBorderRadius = {
borderTopLeftRadius: null,
borderTopRightRadius: null,
borderBottomLeftRadius: null,
borderBottomRightRadius: null
};
let themedColor;
let themedLabelColor;
let themedBadgeColor;
let themedRippleColor;
let themedBorderRadius;
let themedCorner;
let themedMargin;
if (disabled) {
themedColor = Theme.button.color.raised.disabled[themedShade];
} else if (busy) {
themedColor = Theme.button.color.raised.busy[themedShade];
} else {
if (color === `themed`) {
if (Theme.button.color.raised.hasOwnProperty(Theme.button.raised.color)) {
themedColor = Theme.button.color.raised[Theme.button.raised.color][themedShade];
} else {
themedColor = Theme.button.raised.color;
}
} else if (Theme.button.color.raised.hasOwnProperty(color)) {
themedColor = Theme.button.color.raised[color][themedShade];
} else {
themedColor = color;
}
}
if (typeof corner === `string`) {
if (corner === `themed`) {
if (typeof Theme.button.raised.corner === `string` && Theme.button.corner.raised.hasOwnProperty(Theme.button.raised.corner)) {
themedCorner = Theme.button.corner.raised[Theme.button.raised.corner];
} else {
themedCorner = Theme.button.raised.corner;
}
} else if (Theme.button.corner.raised.hasOwnProperty(corner)) {
themedCorner = Theme.button.corner.raised[corner];
} else {
themedCorner = 0;
}
} else {
themedCorner = corner;
}
if (typeof themedCorner === `number`) {
themedBorderRadius = {
small: {
borderRadius: themedCorner
},
normal: {
borderRadius: themedCorner
},
large: {
borderRadius: themedCorner
}
};
} else if (typeof themedCorner === `object`) {
if (themedCorner.hasOwnProperty(`small`) && typeof themedCorner.small === `object` &&
themedCorner.hasOwnProperty(`normal`) && typeof themedCorner.normal === `object` &&
themedCorner.hasOwnProperty(`large`) && typeof themedCorner.large === `object`) {
themedBorderRadius = {
small: Object.entries(themedCorner.small).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius),
normal: Object.entries(themedCorner.normal).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius),
large: Object.entries(themedCorner.large).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius)
};
} else if (themedCorner.hasOwnProperty(`small`) && typeof themedCorner.small === `number` &&
themedCorner.hasOwnProperty(`normal`) && typeof themedCorner.normal === `number` &&
themedCorner.hasOwnProperty(`large`) && typeof themedCorner.large === `number`) {
themedBorderRadius = {
small: {
borderRadius: themedCorner.small
},
normal: {
borderRadius: themedCorner.normal
},
large: {
borderRadius: themedCorner.large
}
};
} else {
themedBorderRadius = {
small: {
...nullBorderRadius,
...themedCorner
},
normal: {
...nullBorderRadius,
...themedCorner
},
large: {
...nullBorderRadius,
...themedCorner
}
};
}
}
if (typeof margin === `string`) {
if (margin === `themed`) {
if (typeof Theme.button.raised.margin === `string` && Theme.button.margin.raised.hasOwnProperty(Theme.button.raised.margin)) {
themedMargin = Theme.button.margin.raised[Theme.button.raised.margin];
} else {
themedMargin = Theme.button.raised.margin;
}
} else if (Theme.button.margin.raised.hasOwnProperty(margin)) {
themedMargin = Theme.button.margin.raised[margin];
} else {
themedMargin = 0;
}
} else {
themedMargin = margin;
}
if (typeof themedMargin === `number`) {
themedMargin = {
small: {
...nullMargin,
margin: themedMargin
},
normal: {
...nullMargin,
margin: themedMargin
},
large: {
...nullMargin,
margin: themedMargin
}
};
} else if (typeof themedMargin === `object`) {
themedMargin = Object.entries(themedMargin).reduce((_themedMargin, [ key, value ]) => {
let _margin = nullMargin;
_margin[`margin${key.charAt(0).toUpperCase()}${key.slice(1)}`] = value;
_themedMargin = {
small: {
..._themedMargin.small,
..._margin
},
normal: {
..._themedMargin.normal,
..._margin
},
large: {
..._themedMargin.large,
..._margin
}
};
return _themedMargin;
}, {
small: nullMargin,
normal: nullMargin,
large: nullMargin
});
}
themedLabelColor = Theme.button.color.raised.label[themedShade];
themedBadgeColor = Theme.misc.color.badge[themedBadgeShade];
themedRippleColor = Theme.button.color.raised.ripple[themedShade];
return {
small: {
container: {
...prevAdjustedStyle.small.container,
height: Theme.button.size.raised.small,
...themedMargin.small,
...themedBorderRadius.small,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
contentLeftRoom: {
...prevAdjustedStyle.small.contentLeftRoom,
maxHeight: Theme.button.size.raised.small - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom === `object` ? style.contentLeftRoom : {})
},
contentMiddleRoom: {
...prevAdjustedStyle.small.contentMiddleRoom,
minWidth: Theme.button.size.raised.small - 6,
maxHeight: Theme.button.size.raised.small - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentMiddleRoom`) && typeof style.contentMiddleRoom === `object` ? style.contentMiddleRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.small.contentRightRoom,
maxHeight: Theme.button.size.raised.small - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom === `object` ? style.contentRightRoom : {})
},
badgeRoom: {
...prevAdjustedStyle.small.badgeRoom,
minWidth: Theme.misc.size.badge.small,
height: Theme.misc.size.badge.small,
borderRadius: Theme.misc.size.badge.small / 2,
backgroundColor: themedBadgeColor,
...(typeof style === `object` && style.hasOwnProperty(`badgeRoom`) && typeof style.badgeRoom === `object` ? style.badgeRoom : {})
},
activityIndicatorRoom: {
...prevAdjustedStyle.small.activityIndicatorRoom,
minWidth: Theme.button.size.raised.small - 6,
maxHeight: Theme.button.size.raised.small - 6,
...(typeof style === `object` && style.hasOwnProperty(`activityIndicatorRoom`) && typeof style.activityIndicatorRoom === `object` ? style.activityIndicatorRoom : {})
},
label: {
...prevAdjustedStyle.small.label,
...Theme.button.font.raised.label.small,
color: themedLabelColor,
...(typeof style === `object` && style.hasOwnProperty(`label`) && typeof style.label === `object` ? style.label : {})
},
ripple: {
...prevAdjustedStyle.small.ripple,
width: Theme.button.size.raised.small,
height: Theme.button.size.raised.small,
borderRadius: Theme.button.size.raised.small / 2,
backgroundColor: themedRippleColor,
...(typeof style === `object` && style.hasOwnProperty(`ripple`) && typeof style.ripple === `object` ? style.ripple : {})
}
},
normal: {
container: {
...prevAdjustedStyle.normal.container,
height: Theme.button.size.raised.normal,
...themedMargin.normal,
...themedBorderRadius.normal,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
contentLeftRoom: {
...prevAdjustedStyle.normal.contentLeftRoom,
maxHeight: Theme.button.size.raised.normal - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom === `object` ? style.contentLeftRoom : {})
},
contentMiddleRoom: {
...prevAdjustedStyle.normal.contentMiddleRoom,
minWidth: Theme.button.size.raised.normal - 6,
maxHeight: Theme.button.size.raised.normal - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentMiddleRoom`) && typeof style.contentMiddleRoom === `object` ? style.contentMiddleRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.normal.contentRightRoom,
maxHeight: Theme.button.size.raised.normal - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom === `object` ? style.contentRightRoom : {})
},
badgeRoom: {
...prevAdjustedStyle.normal.badgeRoom,
minWidth: Theme.misc.size.badge.normal,
height: Theme.misc.size.badge.normal,
borderRadius: Theme.misc.size.badge.normal / 2,
backgroundColor: themedBadgeColor,
...(typeof style === `object` && style.hasOwnProperty(`badgeRoom`) && typeof style.badgeRoom === `object` ? style.badgeRoom : {})
},
activityIndicatorRoom: {
...prevAdjustedStyle.normal.activityIndicatorRoom,
minWidth: Theme.button.size.raised.normal - 6,
maxHeight: Theme.button.size.raised.normal - 6,
...(typeof style === `object` && style.hasOwnProperty(`activityIndicatorRoom`) && typeof style.activityIndicatorRoom === `object` ? style.activityIndicatorRoom : {})
},
label: {
...prevAdjustedStyle.normal.label,
...Theme.button.font.raised.label.normal,
color: themedLabelColor,
...(typeof style === `object` && style.hasOwnProperty(`label`) && typeof style.label === `object` ? style.label : {})
},
ripple: {
...prevAdjustedStyle.normal.ripple,
width: Theme.button.size.raised.normal,
height: Theme.button.size.raised.normal,
borderRadius: Theme.button.size.raised.normal / 2,
backgroundColor: themedRippleColor,
...(typeof style === `object` && style.hasOwnProperty(`ripple`) && typeof style.ripple === `object` ? style.ripple : {})
}
},
large: {
container: {
...prevAdjustedStyle.large.container,
height: Theme.button.size.raised.large,
...themedMargin.large,
...themedBorderRadius.large,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
contentLeftRoom: {
...prevAdjustedStyle.large.contentLeftRoom,
maxHeight: Theme.button.size.raised.large - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom === `object` ? style.contentLeftRoom : {})
},
contentMiddleRoom: {
...prevAdjustedStyle.large.contentMiddleRoom,
minWidth: Theme.button.size.raised.large - 6,
maxHeight: Theme.button.size.raised.large - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentMiddleRoom`) && typeof style.contentMiddleRoom === `object` ? style.contentMiddleRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.large.contentRightRoom,
maxHeight: Theme.button.size.raised.large - 6,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom === `object` ? style.contentRightRoom : {})
},
badgeRoom: {
...prevAdjustedStyle.large.badgeRoom,
minWidth: Theme.misc.size.badge.large,
height: Theme.misc.size.badge.large,
borderRadius: Theme.misc.size.badge.large / 2,
backgroundColor: themedBadgeColor,
...(typeof style === `object` && style.hasOwnProperty(`badgeRoom`) && typeof style.badgeRoom === `object` ? style.badgeRoom : {})
},
activityIndicatorRoom: {
...prevAdjustedStyle.large.activityIndicatorRoom,
minWidth: Theme.button.size.raised.large - 6,
maxHeight: Theme.button.size.raised.large - 6,
...(typeof style === `object` && style.hasOwnProperty(`activityIndicatorRoom`) && typeof style.activityIndicatorRoom === `object` ? style.activityIndicatorRoom : {})
},
label: {
...prevAdjustedStyle.large.label,
...Theme.button.font.raised.label.large,
color: themedLabelColor,
...(typeof style === `object` && style.hasOwnProperty(`label`) && typeof style.label === `object` ? style.label : {})
},
ripple: {
...prevAdjustedStyle.large.ripple,
width: Theme.button.size.raised.large,
height: Theme.button.size.raised.large,
borderRadius: Theme.button.size.raised.large / 2,
backgroundColor: themedRippleColor,
...(typeof style === `object` && style.hasOwnProperty(`ripple`) && typeof style.ripple === `object` ? style.ripple : {})
}
}
};
};
export default class RaisedButton extends React.Component {
static contextType = DefaultThemeContext
static propTypes = {
exclusions: PropTypes.arrayOf(PropTypes.string),
room: PropTypes.oneOf([
`none`,
`content-left`, `content-middle`, `content-right`,
`content-bottom`, `content-top`,
`media`
]),
action: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
name: PropTypes.string,
args: PropTypes.array
})
]),
shade: PropTypes.oneOf([ `themed`, `light`, `dark` ]),
corner: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.shape({
topLeft: PropTypes.number,
topRight: PropTypes.number,
bottomLeft: PropTypes.number,
bottomRight: PropTypes.number
})
]),
size: PropTypes.oneOf([ `themed`, `small`, `normal`, `large` ]),
margin: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.shape({
top: PropTypes.number,
bottom: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
horizontal: PropTypes.number,
vertical: PropTypes.number
})
]),
disabled: PropTypes.bool,
busy: PropTypes.bool,
rippled: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string
]),
label: PropTypes.string,
color: PropTypes.string,
debounced: PropTypes.bool,
initialAnimation: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
refName: PropTypes.string,
transitions: PropTypes.arrayOf(PropTypes.shape({
to: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
from: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
option: PropTypes.shape({
duration: PropTypes.number,
delay: PropTypes.number,
easing: PropTypes.string
})
})),
onTransitionBegin: PropTypes.func,
onTransitionEnd: PropTypes.func,
onAnimationBegin: PropTypes.func,
onAnimationEnd: PropTypes.func
})
]),
onPress: PropTypes.func
}
static defaultProps = {
exclusions: [ `` ],
room: `none`,
action: `none`,
shade: `themed`,
corner: `themed`,
size: `themed`,
margin: `themed`,
disabled: false,
busy: false,
rippled: `themed`,
label: ``,
color: `themed`,
debounced: false,
initialAnimation: `themed`,
onPress: () => null
}
static getDerivedStateFromProps (props, state) {
const {
shade,
corner,
size,
margin,
disabled,
busy,
color,
style
} = props;
const {
Theme
} = state.context;
const themedSize = size === `themed` ? Theme.button.raised.size : size;
return {
adjustedStyle: readjustStyle({
shade,
corner,
margin,
disabled,
busy,
color,
style
}, state.adjustedStyle, Theme),
ripple: {
...state.ripple,
radius: Theme.button.size.raised[themedSize] / 2
}
};
}
constructor (props) {
super(props);
const component = this;
const {
size
} = props;
const themedSize = size === `themed` ? DefaultTheme.button.raised.size : size;
component.refCache = {};
component.debounce = (() => {
let timeoutId = null;
const debounce = (task, ...args) => {
const context = this;
if (timeoutId === null) {
task.apply(context, args);
timeoutId = setTimeout(() => {
clearTimeout(timeoutId);
timeoutId = null;
}, DEFAULT_TOUCH_PRESS_DEBOUNCE_TIME_MS);
}
};
return debounce;
})();
component.state = {
context: {
Theme: DefaultTheme
},
adjustedStyle: DEFAULT_RAISED_BUTTON_STYLE,
width: 0,
height: DefaultTheme.button.size.raised[themedSize],
animation: {
ripple: {
animating: false,
animatedValue: new Animated.Value(0),
easing: {
in: Easing.in(Easing.ease),
out: Easing.out(Easing.ease)
},
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0
}
},
ripple: {
scale: 0,
radius: DefaultTheme.button.size.raised[themedSize] / 2,
locationX: 0,
locationY: 0
}
};
}
onLayout = (event) => {
const component = this;
const {
width,
height
} = event.nativeEvent.layout;
component.setState((prevState) => {
if (prevState.animation.ripple.animating) {
return null;
}
return {
width,
height
};
});
}
onPress = (event) => {
const component = this;
const {
size,
rippled,
onPress
} = component.props;
const {
adjustedStyle,
animation,
width,
height
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.button.raised.size : size;
if ((typeof rippled === `boolean` && rippled) || (rippled === `themed` && Theme.button.raised.rippled)) {
const {
locationX,
locationY
} = event.nativeEvent;
component.setState((prevState) => {
return {
animation: {
ripple: {
...prevState.animation.ripple,
animating: true
}
},
ripple: {
...prevState.ripple,
scale: 2 * Math.sqrt((Math.pow(width, 2) + Math.pow(height, 2)) / ((Math.pow(adjustedStyle[themedSize].ripple.width, 2) + Math.pow(adjustedStyle[themedSize].ripple.height, 2)))),
locationX,
locationY
}
};
}, () => {
Animated.timing(animation.ripple.animatedValue, {
toValue: 1,
easing: animation.ripple.easing.out,
duration: animation.ripple.duration,
useNativeDriver: true
}).start(() => {
animation.ripple.animatedValue.resetAnimation();
component.setState((prevState) => {
return {
animation: {
ripple: {
...prevState.animation.ripple,
animating: false
}
},
ripple: {
animatedValue: new Animated.Value(0),
scale: 0,
radius: Theme.button.size.raised[themedSize] / 2,
locationX: 0,
locationY: 0
}
};
}, () => {
onPress(event);
});
});
});
} else {
onPress(event);
}
}
animate (animation = {
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) {
const component = this;
const {
Theme
} = component.context;
if (typeof animation === `string` && animation !== `none`) {
const animationName = animation.replace(/-([a-z])/g, (match, word) => word.toUpperCase());
if (Theme.button.animation.raised.hasOwnProperty(animationName)) {
animation = Theme.button.animation.raised[animationName];
}
}
if (typeof animation === `object`) {
const {
refName,
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
const componentRef = component.refCache[refName];
if (componentRef !== undefined && Array.isArray(transitions)) {
let transitionDuration = 0;
const transitionPromises = transitions.map((transition, transitionIndex) => {
let transitionBeginPromise;
let transitionEndPromise;
if (typeof transition === `object`) {
let transitionType;
let componentRefTransition = {
from: {},
to: {}
};
let componentRefTransitionOption = {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
};
if (transition.hasOwnProperty(`from`)) {
let from = typeof transition.from === `function` ? transition.from(component.props, component.state, component.context) : transition.from;
componentRefTransition.from = typeof from === `object` ? from : {};
transitionType = `from`;
}
if (transition.hasOwnProperty(`to`)) {
let to = typeof transition.to === `function` ? transition.to(component.props, component.state, component.context) : transition.to;
componentRefTransition.to = typeof to === `object` ? to : {};
transitionType = transitionType === `from` ? `from-to` : `to`;
}
if (transition.hasOwnProperty(`option`) && typeof transition.option === `object`) {
componentRefTransitionOption = {
...componentRefTransitionOption,
...transition.option
};
}
transitionBeginPromise = new Promise((resolve) => {
setTimeout(() => {
if (transitionType === `to`) {
componentRef.transitionTo(
componentRefTransition.to,
componentRefTransitionOption.duration,
componentRefTransitionOption.easing,
componentRefTransitionOption.delay
);
} else if (transitionType === `from-to`) {
setTimeout(() => {
componentRef.transition(
componentRefTransition.from,
componentRefTransition.to,
componentRefTransitionOption.duration,
componentRefTransitionOption.easing
);
}, componentRefTransitionOption.delay);
}
(typeof onTransitionBegin === `function` ? onTransitionBegin : () => null)(transitionIndex);
resolve((_onTransitionBegin) => (typeof _onTransitionBegin === `function` ? _onTransitionBegin : () => null)(_onTransitionBegin));
}, transitionDuration + 5);
});
transitionDuration += componentRefTransitionOption.duration + componentRefTransitionOption.delay;
transitionEndPromise = new Promise((resolve) => {
setTimeout(() => {
(typeof onTransitionEnd === `function` ? onTransitionEnd : () => null)(transitionIndex);
resolve((_onTransitionEnd) => (typeof _onTransitionEnd === `function` ? _onTransitionEnd : () => null)(transitionIndex));
}, transitionDuration);
});
}
return [ transitionBeginPromise, transitionEndPromise ];
});
const animationBeginPromise = new Promise((resolve) => {
(typeof onAnimationBegin === `function` ? onAnimationBegin : () => null)();
resolve((_onAnimationBegin) => (typeof _onAnimationBegin === `function` ? _onAnimationBegin : () => null)());
});
const animationEndPromise = new Promise((resolve) => {
setTimeout(() => {
(typeof onAnimationEnd === `function` ? onAnimationEnd : () => null)();
resolve((_onAnimationEnd) => (typeof _onAnimationEnd === `function` ? _onAnimationEnd : () => null)());
}, transitionDuration + 5);
});
return Promise.all([
animationBeginPromise,
...transitionPromises.flat(),
animationEndPromise
].filter((animationPromise) => animationPromise !== undefined));
}
}
}
componentDidMount () {
const component = this;
const {
shade,
corner,
margin,
disabled,
busy,
color,
initialAnimation,
style
} = component.props;
const {
Theme
} = component.context;
component.setState((prevState) => {
return {
context: {
Theme
},
adjustedStyle: readjustStyle({
shade,
corner,
margin,
disabled,
busy,
color,
style
}, prevState.adjustedStyle, Theme)
};
}, () => {
if ((typeof initialAnimation === `string` && initialAnimation !== `none`) || typeof initialAnimation === `object`) {
component.animate(initialAnimation);
}
});
}
componentWillUnMount () {
const component = this;
const {
animation
} = component.state;
component.refCache = {};
animation.ripple.animatedValue.removeAllListeners();
}
renderRipple () {
const component = this;
const {
size
} = component.props;
const {
adjustedStyle,
animation,
ripple,
width
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.button.raised.size : size;
if (animation.ripple.animating) {
return (
<View
style = {{
...StyleSheet.absoluteFillObject,
borderRadius: adjustedStyle[themedSize].container.borderRadius,
backgroundColor: `transparent`,
overflow: `hidden`
}}
pointerEvents = 'box-only'
>
<Animated.View style = {{
top: 0, // ripple.locationY,
left: width <= ripple.radius * 4 ? width / 2 - ripple.radius : ripple.locationX,
transform: [{
scale: animation.ripple.animatedValue.interpolate({
inputRange: [ 0, 1 ],
outputRange: [ 0, ripple.scale ]
})
}],
opacity: animation.ripple.animatedValue.interpolate({
inputRange: [ 0, 1 ],
outputRange: [ parseInt(Theme.button.color.raised.opacity, 16) / 255, 0 ]
}),
...adjustedStyle[themedSize].ripple
}}/>
</View>
);
}
return null;
}
renderContentChildren (contentLeftChildren = null, activityIndicatorChildren = null, contentMiddleChildren = null, contentRightChildren = null) {
const component = this;
const {
size,
label
} = component.props;
const {
adjustedStyle
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.button.area.size : size;
if (activityIndicatorChildren !== null) {
return ([
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-left-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-left-room-view'
style = { adjustedStyle[themedSize].contentLeftRoom }
pointerEvents = 'box-only'
>
{
contentLeftChildren
}
</AnimatedView>,
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-activity-indicator-room-view`] = componentRef;
}}
key = 'animated-activity-indicator-room-view'
style = { adjustedStyle[themedSize].activityIndicatorRoom } pointerEvents = 'box-only'
useNativeDriver = { true }
>
{
activityIndicatorChildren
}
</AnimatedView>,
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-middle-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-middle-room-view'
style = { adjustedStyle[themedSize].contentMiddleRoom }
pointerEvents = 'box-only'
>
{
contentMiddleChildren !== null ? contentMiddleChildren : <Text style = { adjustedStyle[themedSize].label }>{ label }</Text>
}
</AnimatedView>,
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-right-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-right-room-view'
style = { adjustedStyle[themedSize].contentRightRoom }
pointerEvents = 'box-only'
>
{
contentRightChildren
}
</AnimatedView>
]);
}
return ([
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-left-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-left-room-view'
style = { adjustedStyle[themedSize].contentLeftRoom }
pointerEvents = 'box-only'
>
{
contentLeftChildren
}
</AnimatedView>,
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-middle-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-middle-room-view'
style = { adjustedStyle[themedSize].contentMiddleRoom }
pointerEvents = 'box-only'
>
{
contentMiddleChildren !== null ? contentMiddleChildren : <Text style = { adjustedStyle[themedSize].label }>{ label }</Text>
}
</AnimatedView>,
<AnimatedView
ref = {(componentRef) => {
component.refCache[`animated-content-right-room-view`] = componentRef;
}}
useNativeDriver = { true }
key = 'animated-content-right-room-view'
style = { adjustedStyle[themedSize].contentRightRoom }
pointerEvents = 'box-only'
>
{
contentRightChildren
}
</AnimatedView>
]);
}
render () {
const component = this;
const {
shade,
size,
disabled,
busy,
debounced,
children
} = component.props;
const {
adjustedStyle
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.button.raised.size : size;
let contentLeftChildren = null;
let contentMiddleChildren = null;
let contentRightChildren = null;
let badgeChildren = null;
let activityIndicatorChildren = null;
if (React.Children.count(children) > 0) {
let fragments = React.Children.toArray(React.Children.map(children, (child) => {
if (child !== null) {
const {
exclusions,
room
} = child.props;
if (typeof room === `string` && (room === `content-left` || room === `content-middle` || room === `content-right` ||
room === `badge` || room === `activity-indicator`)) {
const inheritedProps = Array.isArray(exclusions) ? Object.entries({
shade,
size: room === `activity-indicator` ? `small` : themedSize,
margin: 0,
indentation: 0,
color: adjustedStyle[themedSize].label.color
}).filter(([
propName,
pro