zy-react-native-calendars
Version:
React Native Calendar Components
209 lines (191 loc) • 5.78 kB
JavaScript
// @flow
import {
View,
Text,
ScrollView,
TouchableOpacity,
Dimensions
} from 'react-native';
import PropTypes from 'prop-types';
import populateEvents from './Packer';
import React from 'react';
import moment from 'moment';
import _ from 'lodash';
import styleConstructor from './style';
const LEFT_MARGIN = 60 - 1;
const TEXT_LINE_HEIGHT = 17;
function range(from, to) {
return Array.from(Array(to), (_, i) => from + i);
}
let {width: dimensionWidth} = Dimensions.get('window');
export default class Timeline extends React.PureComponent {
static propTypes = {
start: PropTypes.number,
end: PropTypes.number,
eventTapped: PropTypes.func,
format24h: PropTypes.bool,
events: PropTypes.arrayOf(PropTypes.shape({
start: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
color: PropTypes.string
})).isRequired
}
static defaultProps = {
start: 0,
end: 24,
events: [],
format24h: true
}
constructor(props) {
super(props);
const {start, end} = this.props;
this.calendarHeight = (end - start) * 100;
this.styles = styleConstructor(props.styles, this.calendarHeight);
const width = dimensionWidth - LEFT_MARGIN;
const packedEvents = populateEvents(props.events, width, start);
let initPosition =
_.min(_.map(packedEvents, 'top')) - this.calendarHeight / (end - start);
const verifiedInitPosition = initPosition < 0 ? 0 : initPosition;
this.state = {
_scrollY: verifiedInitPosition,
packedEvents
};
}
componentDidUpdate(prevProps) {
const width = dimensionWidth - LEFT_MARGIN;
const {events: prevEvents, start: prevStart = 0} = prevProps;
const {events, start = 0} = this.props;
if(prevEvents !== events || prevStart !== start) {
this.setState({
packedEvents: populateEvents(events, width, start)
});
}
}
componentDidMount() {
this.props.scrollToFirst && this.scrollToFirst();
}
scrollToFirst() {
setTimeout(() => {
if (this.state && this.state._scrollY && this._scrollView) {
this._scrollView.scrollTo({
x: 0,
y: this.state._scrollY,
animated: true
});
}
}, 1);
}
_renderLines() {
const {format24h, start = 0, end = 24} = this.props;
const offset = this.calendarHeight / (end - start);
const EVENT_DIFF = 20;
return range(start, end + 1).map((i, index) => {
let timeText;
if (i === start) {
timeText = '';
} else if (i < 12) {
timeText = !format24h ? `${i} AM` : `${i}:00`;
} else if (i === 12) {
timeText = !format24h ? `${i} PM` : `${i}:00`;
} else if (i === 24) {
timeText = !format24h ? '12 AM' : '23:59';
} else {
timeText = !format24h ? `${i - 12} PM` : `${i}:00`;
}
return [
<Text
key={`timeLabel${i}`}
style={[this.styles.timeLabel, {top: offset * index - 6}]}>
{timeText}
</Text>,
i === start ? null : (
<View
key={`line${i}`}
style={[
this.styles.line,
{top: offset * index, width: dimensionWidth - EVENT_DIFF}
]}
/>
),
<View
key={`lineHalf${i}`}
style={[
this.styles.line,
{top: offset * (index + 0.5), width: dimensionWidth - EVENT_DIFF}
]}
/>
];
});
}
_onEventTapped(event) {
if(this.props.eventTapped) {
this.props.eventTapped(event);
}
}
_renderEvents() {
const {packedEvents} = this.state;
let events = packedEvents.map((event, i) => {
const style = {
left: event.left,
height: event.height,
width: event.width,
top: event.top,
backgroundColor: event.color ? event.color : '#add8e6'
};
// Fixing the number of lines for the event title makes this calculation easier.
// However it would make sense to overflow the title to a new line if needed
const numberOfLines = Math.floor(event.height / TEXT_LINE_HEIGHT);
const formatTime = this.props.format24h ? 'HH:mm' : 'hh:mm A';
return (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => this._onEventTapped(this.props.events[event.index])}
key={i}
style={[this.styles.event, style]}>
{this.props.renderEvent ? (
this.props.renderEvent(event)
) : (
<View>
<Text numberOfLines={1} style={this.styles.eventTitle}>
{event.title || 'Event'}
</Text>
{numberOfLines > 1 ? (
<Text
numberOfLines={numberOfLines - 1}
style={[this.styles.eventSummary]}>
{event.summary || ' '}
</Text>
) : null}
{numberOfLines > 2 ? (
<Text style={this.styles.eventTimes} numberOfLines={1}>
{moment(event.start).format(formatTime)} -{' '}
{moment(event.end).format(formatTime)}
</Text>
) : null}
</View>
)}
</TouchableOpacity>
);
});
return (
<View>
<View style={{marginLeft: LEFT_MARGIN}}>{events}</View>
</View>
);
}
render() {
return (
<ScrollView
ref={ref => (this._scrollView = ref)}
contentContainerStyle={[
this.styles.contentStyle,
{width: dimensionWidth}
]}>
{this._renderLines()}
{this._renderEvents()}
</ScrollView>
);
}
}