tuya-panel-kit
Version:
a functional component library for developing tuya device panels!
518 lines (492 loc) • 14.1 kB
JavaScript
/* eslint-disable react/no-array-index-key */
import React from 'react';
import { ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import Modal from '../modal';
import TYModal from '../modal/TYModal';
import Motion from '../motion';
import TYText from '../TYText';
import {
StyledContainer,
StyledTitle,
StyledTitleText,
StyledSwitch,
StyledFooter,
StyledConfirmButton,
StyledCancelButton,
StyledCancelText,
StyledConfirmText,
StyledSubTitleText,
StyledBackIcon,
StyledTouchView,
backIcon,
StyledBackText,
StyleDividerView,
StyleDivider,
StyledMiddleDivider,
} from './styled';
export const MOTION_TYPES = Object.keys(Motion)
.concat('none')
.filter(v => {
return v !== 'Toast' && v !== 'PushDown';
});
/**
*
* @param {ReactElement} WrappedComponent
* @param {Boolean} withModal - 是否包含在Modal组件内
*/
const withSkeleton = (WrappedComponent, withModal = false) => {
// 同步1.x
const name = WrappedComponent.displayName || '';
const ACCESSIBILITY_LABEL_MAP = {
CountdownPopup: 'Popup_CountdownPicker',
DatePickerPopup: 'Popup_DatePicker',
TimerPickerPopup: 'Popup_TimerPicker',
NumberSelectorPopup: 'Popup_NumberSelector',
ListPopup: 'Popup_List',
PickerPopup: 'Popup_Picker',
Custom: 'Popup_Custom',
};
const accessPrefix = ACCESSIBILITY_LABEL_MAP[name] || 'Popup';
return class WrapperComponent extends React.Component {
static propTypes = {
...TYModal.propTypes,
/**
* 容器样式
*/
wrapperStyle: ViewPropTypes.style,
/**
* Popup头部标题
*/
title: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
PropTypes.element,
]),
/**
* Popup头部副标题
*/
subTitle: PropTypes.string,
/**
* Popup头部标题样式
*/
titleTextStyle: TYText.propTypes.style,
/**
* Popup头部样式
*/
titleWrapperStyle: ViewPropTypes.style,
/**
* 头部栏 Switch 值
*/
switchValue: PropTypes.bool,
/**
* 头部栏 Switch Change事件,不用onValueChange的原因是避免props重复
*/
onSwitchValueChange: PropTypes.func,
/**
* 取消点击回调
*/
onCancel: PropTypes.func,
/**
* 确认点击回调
*/
onConfirm: PropTypes.func,
/**
* 取消文案
*/
cancelText: PropTypes.string,
/**
* 确认文案
*/
confirmText: PropTypes.string,
/**
* 取消文字样式
*/
cancelTextStyle: TYText.propTypes.style,
/**
* 确认文字样式
*/
confirmTextStyle: TYText.propTypes.style,
/**
* 自定义footer
*/
footer: PropTypes.element,
/**
* footer容器样式
*/
footerWrapperStyle: ViewPropTypes.style,
/**
* footer容器显示状态
*/
footerType: PropTypes.oneOf(['singleConfirm', 'singleCancel', 'custom', 'both']),
/**
* 动画类型
*/
motionType: PropTypes.oneOf(MOTION_TYPES),
/**
* 动画配置
*/
motionConfig: PropTypes.object,
/**
* 是否竖直居中
*/
isAlign: PropTypes.bool,
/**
* 返回Icon颜色
*/
backIconColor: PropTypes.string,
/**
* 返回回调函数
*/
onBack: PropTypes.func,
/**
* 返回文案
*/
backText: PropTypes.string,
/**
* 是否显示返回按钮
*/
showBack: PropTypes.bool,
/**
* 是否显示头部栏与内容框的分割线
*/
showTitleDivider: PropTypes.bool,
};
static defaultProps = {
title: 'Modal',
titleTextStyle: null,
titleWrapperStyle: null,
wrapperStyle: null,
subTitle: '',
switchValue: undefined,
onSwitchValueChange: null,
onCancel: () => {},
onConfirm: () => {},
cancelText: 'Cancel',
confirmText: 'Confirm',
cancelTextStyle: null,
confirmTextStyle: null,
footer: null,
footerWrapperStyle: null,
footerType: 'both',
motionType: 'PullUp',
motionConfig: {},
isAlign: false,
backIconColor: null,
onBack: null,
backText: 'Back',
showBack: false,
showTitleDivider: true,
};
constructor(props) {
super(props);
this.actionTypeFn = null;
this.extraParams = [];
this.state = {
show: withModal ? props.visible : true,
switchValue: props.switchValue,
visible: withModal ? props.visible : true,
pressConfirmActive: false,
pressCancelActive: false,
};
}
componentWillReceiveProps(nextProps) {
if (this.props.visible !== nextProps.visible) {
this.setState({ show: nextProps.visible });
}
if (nextProps.visible) {
this.setState({ visible: true });
}
}
get hasMotion() {
const { motionType } = this.props;
return motionType !== 'none' && typeof Motion[motionType] === 'function';
}
getData = () => {
return this.data;
};
_handleDataChange = (data, ...extraParams) => {
this.data = data;
this.extraParams = extraParams;
};
// 针对Popup.list
_handleSelect = value => {
const { onSelect } = this.props;
if (this.hasMotion) {
typeof onSelect === 'function' &&
onSelect(value, {
close: this._handleModalClose,
});
} else {
typeof onSelect === 'function' &&
onSelect(value, {
close: this._handleModalClose,
});
}
};
_handleSwitchValueChange = switchValue => {
const { onSwitchValueChange } = this.props;
this.setState({ switchValue });
onSwitchValueChange && onSwitchValueChange(switchValue);
};
_handleMaskPress = () => {
const { onMaskPress } = this.props;
if (this.hasMotion) {
// 将关闭弹框内容函数暴露出去,开发者根据需求是否调用close来决定是否关闭弹框
typeof onMaskPress === 'function' &&
onMaskPress({
close: this._handleModalClose,
});
} else {
typeof onMaskPress === 'function' && onMaskPress({ close: this._handleModalClose });
}
};
_handleCancelPress = () => {
const { onCancel } = this.props;
if (this.hasMotion) {
this.setState({ show: false }, () => {
this.actionTypeFn = () => {
typeof onCancel === 'function' && onCancel();
};
});
} else {
typeof onCancel === 'function' && onCancel();
}
};
_handleBack = () => {
const { onBack } = this.props;
if (this.hasMotion) {
// 将关闭弹框内容函数暴露出去,开发者根据需求是否调用close来决定是否关闭弹框
typeof onBack === 'function' &&
onBack({
close: this._handleModalClose,
});
} else {
typeof onBack === 'function' && onBack({ close: this._handleModalClose });
}
};
_handleConfirmPress = () => {
const { onConfirm } = this.props;
if (this.hasMotion) {
// 将关闭弹框内容函数暴露出去,开发者根据需求是否调用close来决定是否关闭弹框
typeof onConfirm === 'function' &&
onConfirm(this.data, ...this.extraParams, {
close: this._handleModalClose,
});
} else {
typeof onConfirm === 'function' &&
onConfirm(this.data, ...this.extraParams, {
close: this._handleModalClose,
});
}
};
_handleMotionHide = () => {
if (typeof this.actionTypeFn === 'function') {
this.actionTypeFn();
}
this.setState({ visible: false });
};
// 关闭弹框函数抽离
_handleModalClose = () => {
if (this.hasMotion) {
this.setState({ show: false });
this.actionTypeFn = () => {
Modal.close();
};
} else {
Modal.close();
}
};
_handlePressIn = isConfirm => {
if (isConfirm) {
this.setState({
pressConfirmActive: true,
});
} else {
this.setState({
pressCancelActive: true,
});
}
};
_handlePressOut = isConfirm => {
if (isConfirm) {
this.setState({
pressConfirmActive: false,
});
} else {
this.setState({
pressCancelActive: false,
});
}
};
renderTitle = () => {
const {
title,
titleTextStyle,
titleWrapperStyle,
subTitle,
backIconColor,
backText,
showBack,
} = this.props;
if (React.isValidElement(title)) return title;
const titleArray = Array.isArray(title) ? title : [title];
return (
<StyledTitle
style={[
titleWrapperStyle,
subTitle && { flexDirection: 'column', justifyContent: 'center' },
]}
>
{showBack && (
<StyledTouchView onPress={this._handleBack}>
<StyledBackIcon d={backIcon} color={backIconColor} />
<StyledBackText text={backText} />
</StyledTouchView>
)}
{titleArray.map((t, idx) => (
<StyledTitleText key={idx} style={titleTextStyle}>
{t}
</StyledTitleText>
))}
{!!subTitle && <StyledSubTitleText>{subTitle}</StyledSubTitleText>}
{typeof this.state.switchValue === 'boolean' && (
<StyledSwitch
style={{ position: 'absolute', right: 16 }}
accessibilityLabel={`${accessPrefix}_Switch`}
useNativeDriver={false} // 与 Modal 共用暂时有bug
value={this.state.switchValue}
onValueChange={this._handleSwitchValueChange}
/>
)}
</StyledTitle>
);
};
renderFooter = () => {
const {
footer,
footerType,
cancelText,
confirmText,
footerWrapperStyle,
cancelTextStyle,
confirmTextStyle,
} = this.props;
if (footer) return footer;
const { pressCancelActive, pressConfirmActive } = this.state;
const showConfirm = footerType === 'both' || footerType === 'singleConfirm';
const showCancel = footerType === 'both' || footerType === 'singleCancel';
return (
<StyledFooter style={footerWrapperStyle}>
{showCancel ? (
<StyledCancelButton
accessibilityLabel={`${accessPrefix}_Cancel`}
bordered={footerType === 'both'}
onPress={this._handleCancelPress}
onPressIn={() => this._handlePressIn(false)}
onPressOut={() => this._handlePressOut(false)}
pressActive={pressCancelActive}
>
<StyledCancelText style={cancelTextStyle} single={footerType === 'singleCancel'}>
{cancelText}
</StyledCancelText>
</StyledCancelButton>
) : null}
{footerType === 'both' && <StyledMiddleDivider />}
{showConfirm ? (
<StyledConfirmButton
accessibilityLabel={`${accessPrefix}_Confirm`}
onPress={this._handleConfirmPress}
onPressIn={() => this._handlePressIn(true)}
onPressOut={() => this._handlePressOut(true)}
pressActive={pressConfirmActive}
>
<StyledConfirmText style={confirmTextStyle}>{confirmText}</StyledConfirmText>
</StyledConfirmButton>
) : null}
</StyledFooter>
);
};
render() {
const {
// ========= 以下为 Modal 通用 props ========== //
animationType,
alignContainer,
mask,
maskStyle,
onMaskPress,
onShow,
onHide,
onDismiss,
// =========== 以上为 Modal 通用 props ========= //
// ========= 以下为 skeleton 通用 props ========== //
title,
titleTextStyle,
titleWrapperStyle,
footer,
cancelText,
confirmText,
onCancel,
footerWrapperStyle,
cancelTextStyle,
confirmTextStyle,
// ========= 以上为 skeleton 通用 props ========== //
wrapperStyle,
motionType,
motionConfig,
showTitleDivider,
isAlign,
...props
} = this.props;
const { visible, switchValue } = this.state;
let element = (
<StyledContainer style={wrapperStyle}>
{this.renderTitle()}
{showTitleDivider && (
<StyleDividerView>
<StyleDivider />
</StyleDividerView>
)}
<WrappedComponent
{...props}
switchValue={typeof switchValue === 'undefined' ? true : switchValue}
_onDataChange={this._handleDataChange}
onSelect={this._handleSelect}
/>
{this.renderFooter()}
</StyledContainer>
);
if (this.hasMotion) {
const MotionComp = Motion[motionType];
element = (
<MotionComp
{...motionConfig}
show={this.state.show}
onHide={this._handleMotionHide}
isAlign={isAlign}
>
{element}
</MotionComp>
);
}
return withModal ? (
<Modal
visible={visible}
animationType={animationType}
alignContainer={alignContainer}
mask={mask}
maskStyle={maskStyle}
onMaskPress={this._handleMaskPress}
onShow={onShow}
onHide={onHide}
onDismiss={onDismiss}
>
{element}
</Modal>
) : (
element
);
}
};
};
export default withSkeleton;