react-native-slideable-calendar
Version:
288 lines (272 loc) • 8.42 kB
JavaScript
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;