UNPKG

tuya-panel-kit

Version:

a functional component library for developing tuya device panels!

350 lines (333 loc) 8.84 kB
/* eslint-disable prettier/prettier */ import React from 'react'; import { StyleSheet, ViewPropTypes, ColorPropType, TouchableHighlight, View, Text, } from 'react-native'; import color from 'color'; import PropTypes from 'prop-types'; import Svg, { Rect } from 'react-native-svg'; import { ACTIVEOPACITY, STYLES, mergeActions, LOADINGSIZE, ACTIONS, BASERADIUS } from './config'; import TYText from '../TYText'; import Loading from './loading'; import LinearGradient from '../gradient/linear-gradient'; import { RatioUtils } from '../../utils'; const { convertX: cx } = RatioUtils; const styles = StyleSheet.create({ wrapper: { justifyContent: 'center', alignItems: 'center', flexDirection: 'row', }, loading: { marginRight: cx(8), }, }); export default class BrickButton extends React.PureComponent { static propTypes = { /** * 容器样式 */ style: ViewPropTypes.style, /** * 点击事件 */ onPress: PropTypes.func, /** * 事件监听 */ onChange: PropTypes.func, /** * loading状态 */ loading: PropTypes.bool, /** * 按钮文字 */ text: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), /** * 按钮文字样式 */ textStyle: Text.propTypes.style, /** * 按钮类型 */ type: PropTypes.oneOf(['primary', 'primaryGradient', 'primaryBorder', 'normal', 'small']), /** * 按钮内部包裹内容样式 */ wrapperStyle: ViewPropTypes.style, /** * 按下背景色 */ backgroundColorTouched: ColorPropType, /** * 是否可以点击 */ disabled: PropTypes.bool, /** * 按钮点按时的颜色 */ underlayColor: ColorPropType, /** * 按钮按下透明度 */ activeOpacity: PropTypes.number, /** * 是否显示按钮点击时的颜色 */ showUnderlay: PropTypes.bool, /** * loading组件主颜色 */ loadingColor: ColorPropType, /** * loading背景颜色 */ loadingBackground: ColorPropType, /** * loading大小 */ loadingSize: PropTypes.oneOfType([PropTypes.oneOf(['small', 'large']), PropTypes.number]), /** * loading样式 */ loadingStyle: ViewPropTypes.style, /** * loading外边框粗细 */ loadingStrokeWidth: PropTypes.number, /** * 渐变背景 */ background: PropTypes.object, }; static defaultProps = { style: {}, onPress: () => {}, onChange: () => {}, loading: false, text: '', type: 'primary', wrapperStyle: {}, textStyle: {}, disabled: false, underlayColor: 'transparent', activeOpacity: 1, showUnderlay: false, backgroundColorTouched: null, loadingColor: '#fff', loadingBackground: 'rgba(0,0,0,.1)', loadingStyle: {}, loadingSize: 'small', loadingStrokeWidth: cx(2), background: { x1: '0%', y1: '0%', x2: '0%', y2: '100%', stops: { '0%': 'red', '30%': 'blue', '100%': 'yellow', }, }, }; constructor(props) { super(props); this.state = { init: false, showUnderlay: props.showUnderlay, layout: { width: 0, height: 0, textWidth: 0, }, // isTextOverFlow: false }; } onPress = (...args) => { const { onPress } = this.props; onPress && onPress(...args); }; /** * @description Unified external exposure of the event * @param {string} [eventName=''] * @param {*} args */ onChange = (eventName = '', ...args) => { const { onChange } = this.props; switch (eventName) { case 'onShowUnderlay': this.setState({ showUnderlay: true }); break; case 'onHideUnderlay': this.setState({ showUnderlay: false }); break; default: break; } onChange && onChange(eventName, ...args); }; onLayout = ({ nativeEvent: { layout: { width, height }, }, }) => { // this.measureText(); this.setState({ layout: { ...this.state.layout, width, height }, init: true, }); }; getLayoutStyle = () => { const { type, wrapperStyle, disabled, textStyle } = this.props; const wrapper = StyleSheet.flatten([ styles.wrapper, STYLES[`${type}`].style, wrapperStyle, disabled && { backgroundColor: STYLES[`${type}`].backgroundColorDisabled }, ]); const contentTextStyle = StyleSheet.flatten([ STYLES[`${type}`].textStyle, textStyle, disabled && { color: STYLES[`${type}`].textDisabledColor }, ]); const { borderColor } = StyleSheet.flatten([ wrapper, type === 'primaryBorder' && disabled && { borderColor: STYLES[`${type}`].borderColorDisabled, }, ]); const textColor = contentTextStyle.color; const backgroundColor = type === 'primaryGradient' && !disabled ? 'transparent' : wrapper.backgroundColor; return { wrapper, contentTextStyle, borderColor, textColor, backgroundColor }; }; measureIsTextSizeToSmall = () => { const { width: w } = this.state.layout; const minWidth = STYLES.small.style.width; return w <= minWidth; }; renderLinearGradient = radius => { const { init, layout } = this.state; const { background } = this.props; const { width, height } = layout; if (!init) return; return ( <View style={{ width, height, position: 'absolute', }} > <LinearGradient {...background}> <Rect x="0" y="0" rx={radius} ry={radius} width={width} height={height} /> </LinearGradient> </View> ); }; renderMaskView = (width, height, radius, fill) => { if (!width && !height) return; return ( <View style={{ width, height, position: 'absolute', }} > <Svg width={width} height={height}> <Rect x="0" y="0" rx={radius} ry={radius} width={width} height={height} fill={fill} /> </Svg> </View> ); }; renderLoadingView = () => { const { loadingColor, loadingStyle, text, type, loadingSize, loading, loadingBackground, loadingStrokeWidth, } = this.props; if (!loading) return; const isWrapperSmall = this.measureIsTextSizeToSmall(); const size = typeof loadingSize === 'string' ? LOADINGSIZE[loadingSize] : loadingSize; return ( <Loading size={size} style={[text && type !== 'small' && !isWrapperSmall && styles.loading, loadingStyle]} color={loadingColor} loading={loading} backgroundColor={loadingBackground} strokeWidth={loadingStrokeWidth} /> ); }; renderContentTextView = (textStyle = {}) => { const { text, loading, type } = this.props; const str = Array.isArray(text) ? text.join('') : text; const isWrapperSmall = this.measureIsTextSizeToSmall(); if (loading && (type === 'small' || isWrapperSmall)) return; return ( <TYText ref={ref => (this._text = ref)} text={str} style={textStyle} numberOfLines={1} /> ); }; render() { const { type, style, loading, disabled, underlayColor, activeOpacity } = this.props; const { layout, showUnderlay } = this.state; const { wrapper, contentTextStyle, borderColor, textColor, backgroundColor, } = this.getLayoutStyle(); const { width, height } = layout; const radius = typeof wrapper.borderRadius !== 'undefined' ? wrapper.borderRadius : BASERADIUS; const events = mergeActions(ACTIONS, this.onChange); const fill = type === 'primaryBorder' ? color(wrapper.borderColor) .alpha(0.1) .rgbString() : 'rgba(0,0,0,.1)'; return ( <TouchableHighlight onPress={this.onPress} activeOpacity={activeOpacity || ACTIVEOPACITY} disabled={disabled} underlayColor={underlayColor} {...events} style={[style, { width: layout.width, height: layout.height }]} > <View style={[wrapper, { backgroundColor, borderColor }]} onLayout={this.onLayout}> {type === 'primaryGradient' && !disabled && this.renderLinearGradient(radius)} {this.renderLoadingView()} {this.renderContentTextView( [ contentTextStyle, loading && { color: color(textColor) .alpha(0.7) .rgbString(), }, ], wrapper )} {showUnderlay && this.renderMaskView(width, height, radius, fill)} </View> </TouchableHighlight> ); } }