Skip to content

Commit

Permalink
added calendar list
Browse files Browse the repository at this point in the history
  • Loading branch information
rghorbani committed Mar 26, 2018
1 parent e69aa60 commit e86cd32
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 4 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,30 @@ If you implement an awesome day component please make a PR so that other people

### CalendarList

<kbd>
<img src="https://github.com/rghorbani/react-native-general-calendars/blob/master/demo/calendar-list.gif?raw=true">
</kbd>

`<CalendarList />` is scrollable semi-infinite calendar composed of `<Calendar />` components. Currently it is possible to scroll 4 years back and 4 years to the future. All paramters that are available for `<Calendar />` are also available for this component. There are also some additional params that can be used:

```javascript
<CalendarList
// Callback which gets executed when visible months change in scroll view. Default = undefined
onVisibleMonthsChange={(months) => {console.log('now these months are visible', months);}}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={50}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={50}
// Enable or disable scrolling of calendar list
scrollEnabled={true}
// Enable or disable vertical scroll indicator. Default = false
showScrollIndicator={true}
...calendarParams
/>
```

### Agenda

Coming soon

## Authors
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-general-calendars",
"version": "1.0.6",
"version": "1.1.0",
"description": "React Native Calendar Components with support of Gregorian & Jalaali Calendars",
"main": "src/index.js",
"scripts": {
Expand Down
248 changes: 248 additions & 0 deletions src/calendar-list/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/**
* Copyright 2016 Reza (github.com/rghorbani).
*
* @flow
*/

'use strict';

const React = require('react');
const PropTypes = require('prop-types');
const Moment = require('moment');
const jMoment = require('moment-jalaali');
const { FlatList, Platform, StyleSheet } = require('react-native');

const CalendarListItem = require('./item');
const Calendar = require('../calendar');
const defaultStyle = require('../style');
const dateutils = require('../dateutils');
const { xdateToData, parseDate } = require('../interface');

class CalendarList extends React.Component {
static propTypes = {
...Calendar.propTypes,

// hight of each calendar. Default = 360
calendarHeight: PropTypes.number,

// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange: PropTypes.number,

// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange: PropTypes.number,

// Enable or disable scrolling of calendar list
scrollEnabled: PropTypes.bool,

// Enable or disable vertical scroll indicator. Default = false
showScrollIndicator: PropTypes.bool,

// When true, the calendar list scrolls to top when the status bar is tapped. Default = false
scrollsToTop: PropTypes.bool,
};

static defaultProps = {
type: 'gregorian',
calendarHeight: 360,
pastScrollRange: 50,
futureScrollRange: 50,
showScrollIndicator: false,
scrollEnabled: true,
scrollsToTop: false,
};

constructor(props) {
super(props);
this.style = styleConstructor(props.theme);
const rows = [];
const texts = [];
let date;
if (props.type === 'jalaali') {
date = parseDate(this.props.type, props.current) || jMoment.utc();
} else {
date = parseDate(this.props.type, props.current) || Moment.utc();
}
for (let i = 0; i <= this.props.pastScrollRange + this.props.futureScrollRange; i++) {
let rangeDate;
let rangeDateStr;
if (props.type === 'jalaali') {
rangeDate = date.clone().add(i - this.props.pastScrollRange, 'jMonths');
rangeDateStr = rangeDate.format('jMMMM jYYYY');
} else {
rangeDate = date.clone().add(i - this.props.pastScrollRange, 'months');
rangeDateStr = rangeDate.format('MMM YYYY');
}
texts.push(rangeDateStr);
/*
* This selects range around current shown month [-0, +2] or [-1, +1] month for detail calendar rendering.
* If `this.props.pastScrollRange` is `undefined` it's equal to `false` or 0 in next condition.
*/
if (this.props.pastScrollRange - 1 <= i && i <= this.props.pastScrollRange + 1 || !this.props.pastScrollRange && i <= this.props.pastScrollRange + 2) {
rows.push(rangeDate);
} else {
rows.push(rangeDateStr);
}
}

this.state = {
rows,
texts,
openDate: date,
initialized: false,
};
this.lastScrollPosition = -1000;

this.onViewableItemsChangedBound = this.onViewableItemsChanged.bind(this);
this.renderCalendar = this.renderCalendar.bind(this);
this.getItemLayout = this.getItemLayout.bind(this);
}

scrollToDay(d, offset, animated) {
const day = parseDate(this.props.type, d);
let diffMonths;
if (this.props.type === 'jalaali') {
diffMonths = Math.round(this.state.openDate.clone().jDate(1).diff(day.clone().jDate(1), 'jMonths'));
} else {
diffMonths = Math.round(this.state.openDate.clone().date(1).diff(day.clone().date(1), 'months'));
}
let scrollAmount = (this.props.calendarHeight * this.props.pastScrollRange) + (diffMonths * this.props.calendarHeight) + (offset || 0);
let week = 0;
const days = dateutils.page(this.props.type, day, this.props.firstDay);
for (let i = 0; i < days.length; i++) {
week = Math.floor(i / 7);
if (dateutils.sameDate(this.props.type, days[i], day)) {
scrollAmount += 46 * week;
break;
}
}
this.listView.scrollToOffset({offset: scrollAmount, animated});
}

scrollToMonth(m) {
const month = parseDate(this.props.type, m);
const scrollTo = month || this.state.openDate;
let diffMonths;
if (this.props.type === 'jalaali') {
diffMonths = Math.round(this.state.openDate.clone().jDate(1).diff(scrollTo.clone().jDate(1), 'jMonths'));
} else {
diffMonths = Math.round(this.state.openDate.clone().date(1).diff(scrollTo.clone().date(1), 'months'));
}
const scrollAmount = (this.props.calendarHeight * this.props.pastScrollRange) + (diffMonths * this.props.calendarHeight);
//console.log(month, this.state.openDate);
//console.log(scrollAmount, diffMonths);
this.listView.scrollToOffset({offset: scrollAmount, animated: false});
}

componentWillReceiveProps(props) {
const current = parseDate(this.props.type, this.props.current);
const nextCurrent = parseDate(this.props.type, props.current);
if (nextCurrent && current && nextCurrent.valueOf() !== current.valueOf()) {
this.scrollToMonth(nextCurrent);
}

const rowclone = this.state.rows;
const newrows = [];
for (let i = 0; i < rowclone.length; i++) {
let val = this.state.texts[i];
if (rowclone[i].getTime) {
val = rowclone[i].clone();
val.propbump = rowclone[i].propbump ? rowclone[i].propbump + 1 : 1;
}
newrows.push(val);
}
this.setState({
rows: newrows
});
}

onViewableItemsChanged({viewableItems}) {
function rowIsCloseToViewable(index, distance) {
for (let i = 0; i < viewableItems.length; i++) {
if (Math.abs(index - parseInt(viewableItems[i].index)) <= distance) {
return true;
}
}
return false;
}

const rowclone = this.state.rows;
const newrows = [];
const visibleMonths = [];
for (let i = 0; i < rowclone.length; i++) {
let val = rowclone[i];
const rowShouldBeRendered = rowIsCloseToViewable(i, 1);
if (rowShouldBeRendered && !rowclone[i].getTime) {
if (this.props.type === 'jalaali') {
val = this.state.openDate.clone().add(i - this.props.pastScrollRange, 'jMonths');
} else {
val = this.state.openDate.clone().add(i - this.props.pastScrollRange, 'months');
}
} else if (!rowShouldBeRendered) {
val = this.state.texts[i];
}
newrows.push(val);
if (rowIsCloseToViewable(i, 0)) {
visibleMonths.push(xdateToData(this.props.type, val));
}
}
if (this.props.onVisibleMonthsChange) {
this.props.onVisibleMonthsChange(visibleMonths);
}
this.setState({
rows: newrows
});
}

renderCalendar({item}) {
return (<CalendarListItem item={item} calendarHeight={this.props.calendarHeight} {...this.props} />);
}

getItemLayout(data, index) {
return {length: this.props.calendarHeight, offset: this.props.calendarHeight * index, index};
}

getMonthIndex(month) {
let diffMonths;
if (this.props.type === 'jalaali') {
diffMonths = this.state.openDate.diff(month, 'jMonths') + this.props.pastScrollRange;
} else {
diffMonths = this.state.openDate.diff(month, 'months') + this.props.pastScrollRange;
}
return diffMonths;
}

render() {
return (
<FlatList
ref={(c) => this.listView = c}
style={[this.style.container, this.props.style]}
initialListSize={this.props.pastScrollRange * this.props.futureScrollRange + 1}
data={this.state.rows}
removeClippedSubviews={Platform.OS === 'android' ? false : true}
pageSize={1}
onViewableItemsChanged={this.onViewableItemsChangedBound}
renderItem={this.renderCalendar}
showsVerticalScrollIndicator={this.props.showScrollIndicator}
scrollEnabled={this.props.scrollEnabled}
keyExtractor={(item, index) => String(index)}
initialScrollIndex={this.state.openDate ? this.getMonthIndex(this.state.openDate) : false}
getItemLayout={this.getItemLayout}
scrollsToTop={this.props.scrollsToTop}
/>
);
}
}

const STYLESHEET_ID = 'stylesheet.calendar-list.main';

function styleConstructor(theme = {}) {
const appStyle = {...defaultStyle, ...theme};
return StyleSheet.create({
container: {
backgroundColor: appStyle.calendarBackground,
},
...(theme[STYLESHEET_ID] || {})
});
}

module.exports = CalendarList;
94 changes: 94 additions & 0 deletions src/calendar-list/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright 2016 Reza (github.com/rghorbani).
*
* @flow
*/

'use strict';

const React = require('react');
const {StyleSheet, Text, View} = require('react-native');

const Calendar = require('../calendar');
const defaultStyle = require('../style');

class CalendarListItem extends React.Component {
static defaultProps = {
hideExtraDays: true,
};

constructor(props) {
super(props);
this.style = styleConstructor(props.theme);
}

shouldComponentUpdate(nextProps) {
const r1 = this.props.item;
const r2 = nextProps.item;
return (typeof r1 !== typeof r2 && typeof r1 === 'string' && r1.toString() !== r2.format('YYYY MM'))
|| (typeof r1 !== typeof r2 && typeof r2 === 'string' && r2.toString() !== r1.format('YYYY MM'))
|| !!(r2.propbump && r2.propbump !== r1.propbump);
// return r1.format('YYYY MM') !== r2.format('YYYY MM') || !!(r2.propbump && r2.propbump !== r1.propbump);
}

render() {
const row = this.props.item;
if (row.format) {
return (
<Calendar
type={this.props.type}
rtl={this.props.rtl}
theme={this.props.theme}
style={[{height: this.props.calendarHeight}, this.style.calendar]}
current={row}
hideArrows
hideExtraDays={this.props.hideExtraDays}
disableMonthChange
markedDates={this.props.markedDates}
markingType={this.props.markingType}
hideDayNames={this.props.hideDayNames}
onDayPress={this.props.onDayPress}
displayLoadingIndicator={this.props.displayLoadingIndicator}
minDate={this.props.minDate}
maxDate={this.props.maxDate}
firstDay={this.props.firstDay}
monthFormat={this.props.monthFormat}
dayComponent={this.props.dayComponent}
disabledByDefault={this.props.disabledByDefault}
showWeekNumbers={this.props.showWeekNumbers}
/>);
} else {
const text = row.toString();
return (
<View style={[{height: this.props.calendarHeight}, this.style.placeholder]}>
<Text allowFontScaling={false} style={this.style.placeholderText}>{text}</Text>
</View>
);
}
}
}

const STYLESHEET_ID = 'stylesheet.calendar-list.main';

function styleConstructor(theme = {}) {
const appStyle = {...defaultStyle, ...theme};
return StyleSheet.create({
placeholder: {
backgroundColor: appStyle.calendarBackground,
alignItems: 'center',
justifyContent: 'center',
},
placeholderText: {
fontSize: 30,
fontWeight: '200',
color: appStyle.dayTextColor,
},
calendar: {
paddingLeft: 15,
paddingRight: 15,
},
...(theme[STYLESHEET_ID] || {})
});
}

module.exports = CalendarListItem;
2 changes: 1 addition & 1 deletion src/calendar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class Calendar extends React.Component {
this.style = styleConstructor(props.theme, props);
let currentMonth;
if (props.current) {
currentMonth = parseDate(this.props.type, props.current);
currentMonth = parseDate(props.type, props.current);
} else {
if (props.type === 'jalaali') {
currentMonth = jMoment.utc();
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@

module.exports = {
get Calendar() { return require('./calendar'); },
get CalendarList() { return require('./calendar-list'); },
get DatePicker() { return require('./datepicker'); },
};
Loading

0 comments on commit e86cd32

Please sign in to comment.