UNPKG

react-native-slideable-calendar

Version:
288 lines (272 loc) 8.42 kB
import React, { Component, PureComponent } from 'react'; import PropTypes from 'prop-types'; import { View, Text, FlatList, Dimensions, StyleSheet, PanResponder, TouchableOpacity, } from 'react-native'; import Weeks from './Weeks'; import { format, eachDay, isFuture, isSameDay, endOfWeek, startOfWeek, } from 'date-fns'; import ProgressBarCircular from './ProgressBar/ProgressBarCircular'; import { moderateScale } from './scaling'; const width = Dimensions.get('window').width; const ITEM_LENGTH = width / 7; class DateItem extends PureComponent { renderProgressDate(item, highlight) { const solar = format(item.date, 'D'); return ( <View style={[styles.itemView, { paddingTop: 0 }, { backgroundColor: highlight ? '#6666FF': '#fff' }, { borderWidth: highlight ? 0: moderateScale(4), borderRadius: 25, borderColor: '#EEF2FA' }]}> <ProgressBarCircular style={{ display: highlight ? 'none' : 'flex' }} strokeWidth={highlight ? 0 : moderateScale(4)} sqSize={moderateScale(40)} percentage={item.percent}> <Text style={[styles.itemDateText, { color: highlight ? '#fff': '#414B5A' }]}>{solar}</Text> <View style={[{ backgroundColor: 'white' }]} /> </ProgressBarCircular> </View> ); } renderCurrentDate(item) { const highlightBgColor = '#6666FF'; const hightlightTextColor = '#fff'; const solar = format(item.date, 'D'); return ( <View style={[styles.itemView, { backgroundColor: highlightBgColor }, { borderRadius: 25 }]}> <Text style={[styles.itemDateText,{ color: hightlightTextColor, fontWeight: 'bold' }]}>{solar}</Text> <View style={[{ backgroundColor: 'white' }]} /> </View> ); } renderDate(item) { const solar = format(item.date, 'D'); const normalTextColor = '#414B5A'; return <View style={[ styles.itemView, { paddingTop: 0 }, ]}> <ProgressBarCircular strokeWidth={moderateScale(4)} sqSize={moderateScale(40)} percentage={item.percent} > <Text style={{ color: normalTextColor, fontWeight: '600', fontSize: 14, lineHeight: 17 }}>{solar}</Text> </ProgressBarCircular> </View> } renderViewLessThanOrEqualNewDate(onItemPress, highlight, item) { return ( <TouchableOpacity key={0} underlayColor='#008b8b' style={styles.itemWrapButton} onPress={onItemPress}> {this.renderProgressDate(item, highlight)} </TouchableOpacity> ); } renderViewMoreThanNewDate(item) { return <View underlayColor='#008b8b' style={styles.itemWrapButton}> {this.renderDate(item)} </View>; } render() { const { item, highlight, onItemPress, } = this.props; const newDate = new Date().setHours(0, 0, 0, 0); const isMoreThanToday = new Date(item.date) > newDate; return ( <View style={styles.itemContainer}> {isMoreThanToday ? this.renderViewMoreThanNewDate(item) : this.renderViewLessThanOrEqualNewDate(onItemPress, highlight, item)} </View> ); } } class CalendarStrip extends Component { constructor(props) { super(props); this.state = { datas: this.props.dates, isTodayVisible: true, pageOfToday: 2, // page of today in calendar, start from 0 currentPage: 2, // current page in calendar, start from 0 }; } componentWillMount() { const touchThreshold = 0; const speedThreshold = 0; this._panResponder = PanResponder.create({ onStartShouldSetPanResponder: () => false, onMoveShouldSetPanResponder: (evt, gestureState) => { const { dy, vy } = gestureState; if (dy > touchThreshold && vy > speedThreshold) { const { onSwipeDown } = this.props; onSwipeDown && onSwipeDown(); } return false; }, onPanResponderRelease: () => { }, }); } componentWillReceiveProps(nextProps) { if (isSameDay(nextProps.selectedDate, this.props.selectedDate)) return; const nextSelectedDate = nextProps.selectedDate; if (!this.currentPageDatesIncludes(nextSelectedDate)) { const sameDay = (d) => isSameDay(d, nextSelectedDate); if (this.props.datas.find(sameDay)) { let selectedIndex = this.props.datas.findIndex(sameDay); if (selectedIndex === -1) selectedIndex = this.state.pageOfToday; // in case not find const selectedPage = ~~(selectedIndex / 7); this.scrollToPage(selectedPage); } else { if (isFuture(nextSelectedDate)) { const head = this.props.datas[0]; const tail = endOfWeek(nextSelectedDate, { weekStartsOn: nextProps.weekStartsOn }); const days = eachDay(head, tail); this.setState({ datas: days, isTodayVisible: false, }, () => { const page = ~~(days.length / 7 - 1); // to last page this.scrollToPage(page); }); } else { const head = startOfWeek(nextSelectedDate, { weekStartsOn: nextProps.weekStartsOn }); const tail = this.props.datas[this.props.datas.length - 1]; const days = eachDay(head, tail); this.setState({ datas: days, isTodayVisible: false, }, () => { // to first page this.scrollToPage(0); }); } } } } scrollToPage = (page, animated = true) => { this._calendar.scrollToIndex({ animated, index: 7 * page }); } currentPageDatesIncludes = (date) => { const { currentPage } = this.state; const currentPageDates = this.props.datas.slice(7 * currentPage, 7 * (currentPage + 1)); // dont use currentPageDates.includes(date); because can't compare Date in it return !!currentPageDates.find(d => isSameDay(d, date)); } render() { const { onPressDate, selectedDate, weekStartsOn, datas } = this.props; return ( <View style={styles.container} {...this._panResponder.panHandlers}> <FlatList ref={ref => this._calendar = ref} bounces={false} horizontal={true} scrollEnabled={false} pagingEnabled={false} initialScrollIndex={6} showsHorizontalScrollIndicator={false} getItemLayout={(data, index) => ( { length: ITEM_LENGTH, offset: ITEM_LENGTH * index, index } )} data={datas} extraData={this.state} keyExtractor={(item, index) => index.toString()} renderItem={({ item}) => <DateItem item={item} onItemPress={() => onPressDate && onPressDate(new Date(item.date))} highlight={isSameDay(selectedDate, item.date)} />} /> <Weeks weekStartsOn={weekStartsOn} selectedDate={selectedDate} /> </View> ); } } CalendarStrip.propTypes = { selectedDate: PropTypes.object.isRequired, onPressDate: PropTypes.func, onPressGoToday: PropTypes.func, onSwipeDown: PropTypes.func, showWeekNumber: PropTypes.bool, weekStartsOn: PropTypes.number, datas: PropTypes.array }; CalendarStrip.defaultProps = { showWeekNumber: false, weekStartsOn: 0, }; const styles = StyleSheet.create({ container: { width, height: 70, justifyContent: 'center', alignItems: 'center', }, header: { height: 30, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: 'white', }, headerDate: { color: 'gray', fontSize: 13, }, headerDateWeek: { color: '#3D6DCF', fontSize: 14, }, headerGoTodayButton: { borderRadius: 10, width: 20, height: 20, backgroundColor: '#3D6DCF', position: 'absolute', top: 5, right: 50, justifyContent: 'center', alignItems: 'center', }, todayText: { fontSize: 12, color: 'white', }, itemContainer: { width: width / 7.7, height: 50, }, itemWrapButton: { flex: 1, justifyContent: 'center', alignItems: 'center', }, itemView: { justifyContent: 'center', alignItems: 'center', paddingTop: 2, width: moderateScale(40), height: moderateScale(40), borderRadius: 25, }, itemDateText: { alignItems: 'baseline', fontSize: 14, lineHeight: 17, fontWeight: 'bold', } }); export default CalendarStrip;