UNPKG

@dplus/rn-timeline

Version:

Timeline component for React Native

479 lines (456 loc) 12.9 kB
"use strict"; import { FlatList, I18nManager, Image, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import React, { PureComponent } from "react"; const defaultCircleSize = 16; const defaultCircleColor = "#007AFF"; const defaultLineWidth = 2; const defaultLineColor = "#007AFF"; const defaultTimeTextColor = "black"; const defaultDotColor = "white"; const defaultInnerCircle = "none"; const isRtl = I18nManager.isRTL; export default class Timeline extends PureComponent { constructor(props, context) { super(props, context); this._renderItem = this._renderItem.bind(this); this.renderTime = ( this.props.renderTime ? this.props.renderTime : this._renderTime ).bind(this); this.renderDetail = ( this.props.renderDetail ? this.props.renderDetail : this._renderDetail ).bind(this); this.renderCircle = ( this.props.renderCircle ? this.props.renderCircle : this._renderCircle ).bind(this); this.renderEvent = this._renderEvent.bind(this); this.state = { data: this.props.data, x: 0, width: 0, }; } static getDerivedStateFromProps(nextProps, prevState) { if (prevState.data !== nextProps.data) { return { data: nextProps.data, }; } return null; } render() { return ( <View style={[styles.container, this.props.style]}> {this.props.isUsingFlatlist ? ( <FlatList style={[styles.listview, this.props.listViewStyle]} contentContainerStyle={this.props.listViewContainerStyle} data={this.state.data} extraData={this.state} renderItem={this._renderItem} keyExtractor={(item, index) => index + ""} {...this.props.options} /> ) : ( this.state.data.map((item, index) => ( <View key={index + ""}>{this._renderItem({ item, index })}</View> )) )} </View> ); } _renderItem({ item, index }) { let content = null; switch (this.props.columnFormat) { case "single-column-left": content = ( <View style={[styles.rowContainer, this.props.rowContainerStyle]}> {this.renderTime(item, index)} {this.renderEvent(item, index)} {this.renderCircle(item, index)} </View> ); break; case "single-column-right": content = ( <View style={[styles.rowContainer, this.props.rowContainerStyle]}> {this.renderEvent(item, index)} {this.renderTime(item, index)} {this.renderCircle(item, index)} </View> ); break; case "two-column": content = (item.position && item.position == "right") || (!item.position && index % 2 == 0) ? ( <View style={[styles.rowContainer, this.props.rowContainerStyle]}> {this.renderTime(item, index)} {this.renderEvent(item, index)} {this.renderCircle(item, index)} </View> ) : ( <View style={[styles.rowContainer, this.props.rowContainerStyle]}> {this.renderEvent(item, index)} {this.renderTime(item, index)} {this.renderCircle(item, index)} </View> ); break; } return <View key={index}>{content}</View>; } _renderTime(rowData, rowID) { if (!this.props.showTime) { return null; } var timeWrapper = null; switch (this.props.columnFormat) { case "single-column-left": timeWrapper = { alignItems: "flex-end", }; break; case "single-column-right": timeWrapper = { alignItems: "flex-start", }; break; case "two-column": timeWrapper = { flex: 1, alignItems: (rowData.position && rowData.position == "right") || (!rowData.position && rowID % 2 == 0) ? "flex-end" : "flex-start", }; break; } const { isAllowFontScaling } = this.props; return ( <View style={timeWrapper}> <View style={[styles.timeContainer, this.props.timeContainerStyle]}> <Text style={[styles.time, this.props.timeStyle]} allowFontScaling={isAllowFontScaling} > {rowData.time} </Text> </View> </View> ); } _renderEvent(rowData, rowID) { const lineWidth = rowData.lineWidth ? rowData.lineWidth : this.props.lineWidth; const isLast = this.props.renderFullLine ? !this.props.renderFullLine : this.state.data.slice(-1)[0] === rowData; const lineColor = isLast ? "rgba(0,0,0,0)" : rowData.lineColor ? rowData.lineColor : this.props.lineColor; let opStyle = null; switch (this.props.columnFormat) { case "single-column-left": opStyle = { borderColor: lineColor, borderLeftWidth: lineWidth, borderRightWidth: 0, marginLeft: 20, paddingLeft: 20, }; break; case "single-column-right": opStyle = { borderColor: lineColor, borderLeftWidth: 0, borderRightWidth: lineWidth, marginRight: 20, paddingRight: 20, }; break; case "two-column": opStyle = (rowData.position && rowData.position == "right") || (!rowData.position && rowID % 2 == 0) ? { borderColor: lineColor, borderLeftWidth: lineWidth, borderRightWidth: 0, marginLeft: 20, paddingLeft: 20, } : { borderColor: lineColor, borderLeftWidth: 0, borderRightWidth: lineWidth, marginRight: 20, paddingRight: 20, }; break; } return ( <View style={[ styles.details, opStyle, this.props.eventContainerStyle, rowData.eventContainerStyle, ]} onLayout={(evt) => { if (!this.state.x && !this.state.width) { const { x, width } = evt.nativeEvent.layout; this.setState({ x, width }); } }} > <TouchableOpacity disabled={this.props.onEventPress == null} style={[this.props.detailContainerStyle]} onPress={() => this.props.onEventPress ? this.props.onEventPress(rowData) : null } > <View style={[styles.detail, this.props.eventDetailStyle]}> {this.renderDetail(rowData, rowID)} </View> {this._renderSeparator()} </TouchableOpacity> </View> ); } _renderDetail(rowData, rowID) { const { isAllowFontScaling } = this.props; let description; if (typeof rowData.description === "string") { description = ( <Text style={[ styles.description, this.props.descriptionStyle, rowData.descriptionStyle, ]} allowFontScaling={isAllowFontScaling} > {rowData.description} </Text> ); } else if (typeof rowData.description === "object") { description = rowData.description; } return ( <View style={styles.container}> <Text style={[styles.title, this.props.titleStyle, rowData.titleStyle]} allowFontScaling={isAllowFontScaling} > {rowData.title} </Text> {description} </View> ); } _renderCircle(rowData, rowID) { var circleSize = rowData.circleSize ? rowData.circleSize : this.props.circleSize ? this.props.circleSize : defaultCircleSize; var circleColor = rowData.circleColor ? rowData.circleColor : this.props.circleColor ? this.props.circleColor : defaultCircleColor; var lineWidth = rowData.lineWidth ? rowData.lineWidth : this.props.lineWidth ? this.props.lineWidth : defaultLineWidth; var circleStyle = null; switch (this.props.columnFormat) { case "single-column-left": circleStyle = isRtl ? { width: this.state.width ? circleSize : 0, height: this.state.width ? circleSize : 0, borderRadius: circleSize / 2, backgroundColor: circleColor, right: this.state.width - circleSize / 2 - (lineWidth - 1) / 2, } : { width: this.state.x ? circleSize : 0, height: this.state.x ? circleSize : 0, borderRadius: circleSize / 2, backgroundColor: circleColor, left: this.state.x - circleSize / 2 + (lineWidth - 1) / 2, }; break; case "single-column-right": circleStyle = { width: this.state.width ? circleSize : 0, height: this.state.width ? circleSize : 0, borderRadius: circleSize / 2, backgroundColor: circleColor, left: this.state.width - circleSize / 2 - (lineWidth - 1) / 2, }; break; case "two-column": circleStyle = { width: this.state.width ? circleSize : 0, height: this.state.width ? circleSize : 0, borderRadius: circleSize / 2, backgroundColor: circleColor, left: this.state.width - circleSize / 2 - (lineWidth - 1) / 2, }; break; } var innerCircle = null; switch (this.props.innerCircle) { case "icon": let iconDefault = rowData.iconDefault ? rowData.iconDefault : this.props.iconDefault; let iconSource = rowData.icon ? rowData.icon : iconDefault; if (React.isValidElement(iconSource)) { innerCircle = iconSource; break; } if (rowData.icon) iconSource = rowData.icon.constructor === String ? { uri: rowData.icon } : rowData.icon; let iconStyle = { height: circleSize, width: circleSize, }; innerCircle = ( <Image source={iconSource} defaultSource={typeof iconDefault === "number" && iconDefault} style={[iconStyle, this.props.iconStyle]} /> ); break; case "dot": const dotSize = this.props.dotSize ? this.props.dotSize : circleSize / 2; let dotStyle = { height: dotSize, width: dotSize, borderRadius: circleSize / 4, backgroundColor: rowData.dotColor ? rowData.dotColor : this.props.dotColor ? this.props.dotColor : defaultDotColor, }; innerCircle = <View style={[styles.dot, dotStyle]} />; break; case "element": innerCircle = rowData.icon; break; } return ( <View style={[styles.circle, circleStyle, this.props.circleStyle]}> {innerCircle} </View> ); } _renderSeparator() { if (!this.props.separator) { return null; } return <View style={[styles.separator, this.props.separatorStyle]} />; } } Timeline.defaultProps = { circleSize: defaultCircleSize, circleColor: defaultCircleColor, lineWidth: defaultLineWidth, lineColor: defaultLineColor, innerCircle: defaultInnerCircle, columnFormat: "single-column-left", separator: false, showTime: true, isAllowFontScaling: true, isUsingFlatlist: true, }; const styles = StyleSheet.create({ container: { flex: 1, }, listview: { flex: 1, }, sectionHeader: { marginBottom: 15, backgroundColor: "#007AFF", height: 30, justifyContent: "center", }, sectionHeaderText: { color: "#FFF", fontSize: 18, alignSelf: "center", }, rowContainer: { flexDirection: "row", flex: 1, justifyContent: "center", }, timeContainer: { minWidth: 45, }, time: { textAlign: "right", color: defaultTimeTextColor, overflow: "hidden", }, circle: { width: 16, height: 16, borderRadius: 10, zIndex: 1, position: "absolute", alignItems: "center", justifyContent: "center", }, dot: { width: 8, height: 8, borderRadius: 4, backgroundColor: defaultDotColor, }, title: { fontSize: 16, fontWeight: "bold", }, details: { borderLeftWidth: defaultLineWidth, flexDirection: "column", flex: 1, }, detail: { paddingTop: 10, paddingBottom: 10 }, description: { marginTop: 10, }, separator: { height: 1, backgroundColor: "#aaa", marginTop: 10, marginBottom: 10, }, });