react-native-common-date-picker
Version:
An awesome and cross-platform React Native date picker and calendar component for iOS and Android
267 lines (243 loc) • 10.4 kB
JavaScript
import React, {Component} from 'react';
import {FlatList, Text, View} from 'react-native';
import PropTypes from 'prop-types';
import styles from './style';
import {BORDER_LINE_POSITION, DATE_KEY_TYPE} from '../contants';
class DatePickerList extends Component {
static propTypes = {
keyType: PropTypes.string,
initialScrollIndex: PropTypes.number,
width: PropTypes.number.isRequired,
onValueChange: PropTypes.func,
data: PropTypes.array,
rows: PropTypes.oneOf([5, 7]),
rowHeight: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selectedRowBackgroundColor: PropTypes.string,
unselectedRowBackgroundColor: PropTypes.string,
selectedBorderLineColor: PropTypes.string,
selectedBorderLineWidth: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selectedBorderLineMarginHorizontal: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selectedTextFontSize: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selectedTextColor: PropTypes.string,
selectedTextStyle: PropTypes.object,
unselectedTextColor: PropTypes.string,
unselectedTextStyle: PropTypes.object,
textMarginHorizontal: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
yearSuffix: PropTypes.string,
monthSuffix: PropTypes.string,
daySuffix: PropTypes.string,
};
constructor(props) {
super(props);
const {rows, rowHeight, selectedBorderLineMarginHorizontal, textMarginHorizontal} = props;
__DEV__ && (rows !== 5 && rows !== 7) && console.error('Oops! Rows is only supported by one of [5, 7]');
const initialRow = (rows - 1) / 2;
this.state = {
data: this._getData(),
initialRow,
selectedIndex: initialRow,
isScrolling: false,
rowHeight: +rowHeight,
selectedBorderLineMarginHorizontal: +selectedBorderLineMarginHorizontal,
textMarginHorizontal: +textMarginHorizontal,
};
}
componentDidUpdate(prevProps, prevState) {
if (JSON.stringify(this.props.data) !== JSON.stringify(prevProps.data)) {
const data = this._getData();
this.setState({data}, () => {
const {selectedIndex, initialRow} = this.state;
const {maxScrollIndex, maxSelectedIndex} = this._maxIndex();
const previousScrollIndex = selectedIndex - initialRow;
const index = selectedIndex > maxSelectedIndex ? maxScrollIndex : previousScrollIndex;
this._scrollToIndex(index);
});
}
}
componentWillUnmount() {
this._removeTimer();
}
_getData = () => {
const {rows, data} = this.props;
const _row = (rows - 1) / 2;
const dataSource = data.slice();
for (let i = 0; i < _row; i++) {
dataSource.unshift({date: ''});
dataSource.push({date: ''});
}
return dataSource;
};
_maxIndex = () => {
const {rows} = this.props;
const {data, initialRow} = this.state;
const length = data.length;
const maxScrollIndex = length - rows;
const maxSelectedIndex = length - 1 - initialRow;
return {maxScrollIndex, maxSelectedIndex};
};
_scrollEnd = ({contentOffset}) => {
this._removeTimer();
const y = contentOffset.y;
const {rowHeight} = this.state;
const {maxScrollIndex} = this._maxIndex();
const maxOffsetY = maxScrollIndex * rowHeight;
const offsetY = y < 0 ? 0 : Math.min(maxOffsetY, y);
const index = Math.round(offsetY / rowHeight);
this._scrollToIndex(index);
};
_scrollToIndex = index => {
this.props.onValueChange(index);
this.flatList.scrollToIndex({index, animated: false});
};
_removeTimer = () => this.timer && clearTimeout(this.timer);
_onMomentumScrollEnd = ({nativeEvent}) => this._scrollEnd(nativeEvent);
_onScrollEndDrag = ({nativeEvent}) => {
this._removeTimer();
/**
* When user is scrolling the scroll view fast, this method will be executed first. After this method is executed,
* "_onMomentumScrollEnd" method will be triggered and go on calling "_scrollEnd". So this.timer here
* to avoid conflict between them.
*/
this.timer = setTimeout(() => {
this._scrollEnd(nativeEvent);
}, 150);
};
_getFlatListStyle = () => {
const {textMarginHorizontal} = this.state;
const {width, dataLength, dataIndex} = this.props;
const lastIndex = dataLength - 1;
const style = {width};
if (dataLength >= 2) {
if (dataIndex === 0) {
style.paddingLeft = textMarginHorizontal;
}
if (dataIndex === lastIndex) {
style.paddingRight = textMarginHorizontal;
}
}
return style;
};
_itemTextStyle = index => {
const {selectedIndex} = this.state;
const {
selectedTextFontSize,
selectedTextColor,
selectedTextStyle,
unselectedTextColor,
unselectedTextStyle,
} = this.props;
const style = {};
const selected = selectedIndex === index;
if (selected) {
style.color = selectedTextColor;
style.fontSize = +selectedTextFontSize;
return {...style, ...selectedTextStyle};
}
style.color = unselectedTextColor;
if (unselectedTextStyle && unselectedTextStyle.fontSize) {
delete unselectedTextStyle.fontSize;
}
if (index === selectedIndex - 1 || index === selectedIndex + 1) {
style.fontSize = +selectedTextFontSize - 3;
} else if (index === selectedIndex - 2 || index === selectedIndex + 2) {
style.fontSize = +selectedTextFontSize - 6;
} else {
style.fontSize = +selectedTextFontSize - 8;
}
return {...style, ...unselectedTextStyle};
};
_lineStyle = position => {
const {
width,
selectedBorderLineColor,
selectedBorderLineWidth,
selectedRowBackgroundColor,
unselectedRowBackgroundColor,
dataLength,
dataIndex,
} = this.props;
const {initialRow, rowHeight, selectedBorderLineMarginHorizontal} = this.state;
let marginHorizontal = selectedBorderLineMarginHorizontal;
let marginLeft = selectedBorderLineMarginHorizontal;
const lastIndex = dataLength - 1;
if (dataIndex !== 0 && dataIndex !== lastIndex) {
marginLeft = 0;
marginHorizontal = 0;
} else {
if (dataIndex === lastIndex) {
marginLeft = 0;
}
}
return {
position: 'absolute',
width: width - marginHorizontal,
marginLeft,
height: position === BORDER_LINE_POSITION.MIDDLE ? rowHeight : initialRow * rowHeight,
borderTopWidth: position === BORDER_LINE_POSITION.MIDDLE ? +selectedBorderLineWidth : 0,
borderBottomWidth: position === BORDER_LINE_POSITION.MIDDLE ? +selectedBorderLineWidth : 0,
borderTopColor: selectedBorderLineColor,
borderBottomColor: selectedBorderLineColor,
marginTop: position === BORDER_LINE_POSITION.MIDDLE ? rowHeight * initialRow : (position === BORDER_LINE_POSITION.TOP ? 0 : (initialRow + 1) * rowHeight),
backgroundColor: position === BORDER_LINE_POSITION.MIDDLE ? selectedRowBackgroundColor : unselectedRowBackgroundColor,
};
};
_onScroll = ({nativeEvent}) => {
this._removeTimer();
const {rowHeight, initialRow} = this.state;
const offsetY = nativeEvent.contentOffset.y;
const {maxSelectedIndex} = this._maxIndex();
const selectedIndex = offsetY <= 0 ? initialRow : (Math.round(offsetY / rowHeight) + initialRow);
this.setState({selectedIndex: Math.min(selectedIndex, maxSelectedIndex)});
};
_renderItem = ({item, index}) => {
const {keyType, yearSuffix, monthSuffix, daySuffix} = this.props;
const suffix = keyType === DATE_KEY_TYPE.YEAR ? yearSuffix : (keyType === DATE_KEY_TYPE.MONTH ? monthSuffix : daySuffix);
return (<View style={[styles.datePickerListItemView, {height: this.state.rowHeight}]}>
<Text style={this._itemTextStyle(index)}>{item.date}{!item.date || suffix}</Text>
</View>);
};
render() {
const {rows, initialScrollIndex} = this.props;
const {data, rowHeight, initialRow} = this.state;
const heightOfContainer = rows * rowHeight;
const {maxScrollIndex} = this._maxIndex();
return (
<View style={{height: heightOfContainer}}>
<View pointerEvents={'box-none'} style={this._lineStyle(BORDER_LINE_POSITION.TOP)}/>
<View pointerEvents={'box-none'} style={this._lineStyle(BORDER_LINE_POSITION.MIDDLE)}/>
<View pointerEvents={'box-none'} style={this._lineStyle(BORDER_LINE_POSITION.BOTTOM)}/>
<FlatList
ref={ref => this.flatList = ref}
style={this._getFlatListStyle()}
data={data}
initialScrollIndex={Math.min(initialScrollIndex, maxScrollIndex)}
getItemLayout={(data, index) => ({length: rowHeight, offset: index * rowHeight, index})}
keyExtractor={(item, index) => index.toString()}
onScrollEndDrag={this._onScrollEndDrag}
onMomentumScrollEnd={this._onMomentumScrollEnd}
renderItem={this._renderItem}
onScroll={this._onScroll}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
scrollsToTop={false}
/>
</View>
);
}
}
export default DatePickerList;