import PropTypes from 'prop-types'; import XDate from 'xdate'; import React, {Component} from 'react'; import {StyleSheet, Animated, TouchableOpacity, View, StyleProp, ViewStyle} from 'react-native'; import {toMarkingFormat} from '../../interface'; import {Theme, DateData} from '../../types'; import styleConstructor from '../style'; import CalendarContext from './index'; import Presenter from './Presenter'; import {UpdateSources} from '../commons'; const TOP_POSITION = 65; interface Props { /** Specify theme properties to override specific styles for calendar parts */ theme?: Theme; /** Specify style for calendar container element */ style?: StyleProp; /** Initial date in 'yyyy-MM-dd' format. Default = now */ date: string; /** Callback for date change event */ onDateChanged?: (date: string, updateSource: UpdateSources) => void; /** Callback for month change event */ onMonthChange?: (date: DateData, updateSource: UpdateSources) => void; /** Whether to show the today button */ showTodayButton?: boolean; /** Today button's top position */ todayBottomMargin?: number; /** Today button's style */ todayButtonStyle?: ViewStyle; /** The opacity for the disabled today button (0-1) */ disabledOpacity?: number; } export type CalendarContextProviderProps = Props; /** * @description: Calendar context provider component * @example: https://github.com/wix/react-native-calendars/blob/master/example/src/screens/expandableCalendar.js */ class CalendarProvider extends Component { static displayName = 'CalendarProvider'; static propTypes = { date: PropTypes.any.isRequired, onDateChanged: PropTypes.func, onMonthChange: PropTypes.func, showTodayButton: PropTypes.bool, todayBottomMargin: PropTypes.number, todayButtonStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), disabledOpacity: PropTypes.number }; style = styleConstructor(this.props.theme); presenter = new Presenter(); state = { prevDate: this.getDate(this.props.date), date: this.getDate(this.props.date), updateSource: UpdateSources.CALENDAR_INIT, buttonY: new Animated.Value(this.props.todayBottomMargin ? -this.props.todayBottomMargin : -TOP_POSITION), buttonIcon: this.presenter.getButtonIcon(this.getDate(this.props.date), this.props.showTodayButton), disabled: false, opacity: new Animated.Value(1) }; componentDidUpdate(prevProps: Props) { if (this.props.date && prevProps.date !== this.props.date) { this.setDate(this.props.date, UpdateSources.PROP_UPDATE); } } getDate(date: string) { return date || toMarkingFormat(new XDate()); } getProviderContextValue = () => { return { setDate: this.setDate, date: this.state.date, prevDate: this.state.prevDate, updateSource: this.state.updateSource, setDisabled: this.setDisabled }; }; setDate = (date: string, updateSource: UpdateSources) => { const {setDate} = this.presenter; const updateState = (buttonIcon: number) => { this.setState({date, prevDate: this.state.date, updateSource, buttonIcon}, () => { this.animateTodayButton(date); }); }; setDate(this.props, date, this.state.date, updateState, updateSource); }; setDisabled = (disabled: boolean) => { const {setDisabled} = this.presenter; const {showTodayButton = false} = this.props; const updateState = (disabled: boolean) => { this.setState({disabled}); this.animateOpacity(disabled); }; setDisabled(showTodayButton, disabled, this.state.disabled, updateState); }; animateTodayButton(date: string) { const {shouldAnimateTodayButton, getPositionAnimation} = this.presenter; if (shouldAnimateTodayButton(this.props)) { const animationData = getPositionAnimation(date, this.props.todayBottomMargin); Animated.spring(this.state.buttonY, { ...animationData }).start(); } } animateOpacity(disabled: boolean) { const {shouldAnimateOpacity, getOpacityAnimation} = this.presenter; if (shouldAnimateOpacity(this.props)) { const animationData = getOpacityAnimation(this.props, disabled); Animated.timing(this.state.opacity, { ...animationData }).start(); } } onTodayPress = () => { const today = this.presenter.getTodayDate(); this.setDate(today, UpdateSources.TODAY_PRESS); }; renderTodayButton() { const {disabled, opacity, buttonY, buttonIcon} = this.state; const {getTodayFormatted} = this.presenter; const today = getTodayFormatted(); return ( {today} ); } render() { return ( {this.props.children} {this.props.showTodayButton && this.renderTodayButton()} ); } } export default CalendarProvider; const styles = StyleSheet.create({ container: { flex: 1 } });