Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ingo horizontal calendar #1

Merged
merged 8 commits into from
Mar 19, 2019
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
A calendar built by customizing react-native-calendars to support grid as well as list view.

Read the whole story here: https://medium.com/@varunon9/how-i-built-horizontal-as-well-as-grid-calendar-in-react-native-using-react-native-calendars-eb7a2edcc5db

# React Native Calendars ✨ 🗓️ 📆

[![Version](https://img.shields.io/npm/v/react-native-calendars.svg)](https://www.npmjs.com/package/react-native-calendars)
Expand Down
191 changes: 185 additions & 6 deletions src/calendar/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, {Component} from 'react';
import {
View,
ViewPropTypes
ViewPropTypes,
ScrollView,
Dimensions,
ActivityIndicator,
Platform
} from 'react-native';
import PropTypes from 'prop-types';

Expand All @@ -22,6 +26,12 @@ const viewPropTypes = ViewPropTypes || View.propTypes;

const EmptyArray = [];

// horizontal calendar will be scrolled to (offset * viewport width) to keep selected date visible
let horizontalScrollViewOffset = 0;

// to throttle back-to-back triggering of onPressArrowRight in horizontal calendar
let onPressArrowRightTriggered = false;

class Calendar extends Component {
static propTypes = {
// Specify theme properties to override specific styles for calendar parts. Default = {}
Expand Down Expand Up @@ -79,7 +89,27 @@ class Calendar extends Component {
// Provide custom calendar header rendering component
calendarHeaderComponent: PropTypes.any,
// data which is passed to calendar header, useful only when implementing custom calendar header
headerData: PropTypes.object
headerData: PropTypes.object,
// Handler which gets executed when press list icon. It will set calendar to horizontal
onPressListView: PropTypes.func,
// Handler which gets executed when press grid icon. It will set calendar to grid
onPressGridView: PropTypes.func,
// to show horizontal calendar with scroll
horizontal: PropTypes.bool,
// to automatically scroll horizontal calendar to keep selected date in view
autoHorizontalScroll: PropTypes.bool,
// to show days in horizontal calendar starting from today, if this is true- autoHorizontalScroll will not work
hidePastDatesInHorizontal: PropTypes.bool,
// offset to decide when to trigger onPressArrowRight in horizontal calendar,
// 0 means when rightmost day is reached, undefined means no auto onPressArrowRight triggering
horizontalEndReachedThreshold: PropTypes.number,
// offset to decide when to trigger onPressArrowLeft in horizontal calendar,
// 0 means when leftmost day is reached, undefined means no auto onPressArrowLeft triggering
horizontalStartReachedThreshold: PropTypes.number,
// to show a loader
loading: PropTypes.bool,
// provide a custom loader component
LoaderComponent: PropTypes.any
};

constructor(props) {
Expand All @@ -92,14 +122,18 @@ class Calendar extends Component {
currentMonth = XDate();
}
this.state = {
currentMonth
currentMonth,
horizontal: props.horizontal
};

this.updateMonth = this.updateMonth.bind(this);
this.addMonth = this.addMonth.bind(this);
this.pressDay = this.pressDay.bind(this);
this.longPressDay = this.longPressDay.bind(this);
this.shouldComponentUpdate = shouldComponentUpdate;
this.onHorizontalCalendarScroll =
this.onHorizontalCalendarScroll.bind(this);
this.horizontalScrollViewRef = React.createRef();
}

componentWillReceiveProps(nextProps) {
Expand All @@ -109,6 +143,23 @@ class Calendar extends Component {
currentMonth: current.clone()
});
}
this.setState({
horizontal: nextProps.horizontal
});
}

// scroll the horizontal calendar so that selected date is visible
componentDidUpdate() {
const horizontalScrollView = this.horizontalScrollViewRef.current;
if (horizontalScrollView
&& this.props.autoHorizontalScroll
&& !this.props.hidePastDatesInHorizontal) {
const windowWidth = Dimensions.get('window').width;
horizontalScrollView.scrollTo({
x: horizontalScrollViewOffset * windowWidth,
animated: true
});
}
}

updateMonth(day, doNotTriggerListeners) {
Expand Down Expand Up @@ -186,13 +237,60 @@ class Calendar extends Component {
onLongPress={this.longPressDay}
date={xdateToData(day)}
marking={this.getDateMarking(day)}
horizontal={this.props.horizontal}
current={this.props.current}
>
{date}
</DayComp>
</View>
);
}

onHorizontalCalendarScroll({ nativeEvent }) {
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
const endReachedThreshold = this.props.horizontalEndReachedThreshold;
const startReachedThreshold = this.props.horizontalStartReachedThreshold;
const contentWidth = contentSize.width;
const travelledWidth = layoutMeasurement.width + contentOffset.x;
const horizontalScrollView = this.horizontalScrollViewRef.current;
let calendarUpdated = false;

// going right
if (endReachedThreshold
&& (travelledWidth + endReachedThreshold) > contentWidth
&& !onPressArrowRightTriggered) {
this.props.onPressArrowRight(this.state.currentMonth, this.addMonth);
calendarUpdated = true;
onPressArrowRightTriggered = true;
setTimeout(() => {
onPressArrowRightTriggered = false;
}, 500);
}

// going left
if (contentOffset.x === startReachedThreshold) {
// don't auto select previous month when past dates are hidden
if (this.props.hidePastDatesInHorizontal) {
const selectedMonthTime = this.state.currentMonth.getTime();
const tomorrowTime = new Date().setHours(0,0,0,0) + (24 * 3600 * 1000);
if (selectedMonthTime > tomorrowTime) {
calendarUpdated = true;
}
} else {
calendarUpdated = true;
}
if (calendarUpdated) {
this.props.onPressArrowLeft(this.state.currentMonth, this.addMonth);
}
}

if (calendarUpdated && horizontalScrollView) {
horizontalScrollView.scrollTo({
x: 50, animated: false
});
}
}

getDayComponent() {
if (this.props.dayComponent) {
return this.props.dayComponent;
Expand Down Expand Up @@ -230,8 +328,24 @@ class Calendar extends Component {

renderWeek(days, id) {
const week = [];
const nowTime = new Date().setHours(0,0,0,0); // ignoring hours, mins, secs, msecs
days.forEach((day, id2) => {
week.push(this.renderDay(day, id2));
const dayTime = day.getTime();

// don't show past days in horizontal calendar
if (this.state.horizontal && this.props.hidePastDatesInHorizontal) {
if (dayTime >= nowTime) {
week.push(this.renderDay(day, id2));
}
} else {
week.push(this.renderDay(day, id2));
}

// if day is selected (aka current) day then corresponding week row id will be offset
if (dayTime === parseDate(this.props.current).getTime()) {
horizontalScrollViewOffset = id;
}

}, this);

if (this.props.showWeekNumbers) {
Expand All @@ -249,6 +363,65 @@ class Calendar extends Component {
return CalendarHeader;
}

showLoader() {
if (this.props.LoaderComponent) {
return <LoaderComponent />;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way, LoaderComponent will be undefined.
It should be return this.props.LoaderComponent()

thoughts?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, seems like i made a mistake. will have to check

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will you plz correct it whenever you get time? plz...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create a PR. I'll merge it

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fork the repo, make changes to your forked repo and then you can create PR

} else {
return (
<View style={{ flex: 1, justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
}
}

showCalendar(weeks) {
const windowWidth = Dimensions.get('window').width;
if (this.state.horizontal) {
return (
<ScrollView
contentContainerStyle={Platform.OS === 'web' ? { flex: 1 } : {}}
style={[this.style.monthView, { marginBottom: 10 }]}
horizontal
scrollEventThrottle={500}
onScroll={this.onHorizontalCalendarScroll}
ref={this.horizontalScrollViewRef}
>
{weeks}
{
this.props.loading ?
<View style={[
this.style.loaderContainer,
{
width: windowWidth
}
]}>
{
this.showLoader()
}
</View>
: null
}
</ScrollView>
);
} else {
return (
<View style={this.style.monthView}>
{weeks}
{
this.props.loading ?
<View style={this.style.loaderContainer}>
{
this.showLoader()
}
</View>
: null
}
</View>
);
}
}

render() {
const days = dateutils.page(this.state.currentMonth, this.props.firstDay);
const weeks = [];
Expand All @@ -260,7 +433,7 @@ class Calendar extends Component {
if (current) {
const lastMonthOfDay = current.clone().addMonths(1, true).setDate(1).addDays(-1).toString('yyyy-MM-dd');
if (this.props.displayLoadingIndicator &&
!(this.props.markedDates && this.props.markedDates[lastMonthOfDay])) {
!(this.props.markedDates && this.props.markedDates[lastMonthOfDay])) {
indicator = true;
}
}
Expand All @@ -282,8 +455,14 @@ class Calendar extends Component {
onPressArrowLeft={this.props.onPressArrowLeft}
onPressArrowRight={this.props.onPressArrowRight}
headerData={this.props.headerData}
horizontal={this.props.horizontal}
onPressListView={this.props.onPressListView}
onPressGridView={this.props.onPressGridView}
/>
<View style={this.style.monthView}>{weeks}</View>
{
this.showCalendar(weeks)
}

</View>);
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/calendar/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export default function getStyle(theme={}) {
flexDirection: 'row',
justifyContent: 'space-around'
},
loaderContainer: {
width: '100%',
height: '100%',
position: 'absolute'
},
...(theme[STYLESHEET_ID] || {})
});
}
Expand Down
7 changes: 7 additions & 0 deletions src/calendar/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export default function shouldComponentUpdate(nextProps, nextState) {
field: 'current'
};
}

if (nextState.horizontal !== this.state.horizontal) {
shouldUpdate = {
update: true,
field: 'horizontal'
};
}
//console.log(shouldUpdate.field, shouldUpdate.update);
return shouldUpdate.update;
}
1 change: 1 addition & 0 deletions src/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function xdateToData(xdate) {
year: xdate.getFullYear(),
month: xdate.getMonth() + 1,
day: xdate.getDate(),
weekDay: xdate.getDay(),
timestamp: XDate(dateString, true).getTime(),
dateString: dateString
};
Expand Down