Skip to content

Commit bbe6c44

Browse files
authored
ExpandableCalendar - Added imperative Calendar Toggling (#2634)
* Added imperative toggling of the calendar * Rename toggleCalendar to toggleCalendarPosition in ExpandableCalendarRef * Remove ExpandableCalendarRef type export from index * Define ExpandableCalendarRef type for toggling calendar position * Removed forwaredRef typings * Implement toggleCalendarPosition and add example to screen * Fixed lint * Changed chevron asset to use next and minor rename to interpolation variable
1 parent bb2004d commit bbe6c44

File tree

2 files changed

+80
-23
lines changed

2 files changed

+80
-23
lines changed

example/src/screens/expandableCalendarScreen.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, {useRef, useCallback} from 'react';
2-
import {StyleSheet} from 'react-native';
2+
import {Animated, Easing, StyleSheet, Text, TouchableOpacity} from 'react-native';
33
import {ExpandableCalendar, AgendaList, CalendarProvider, WeekCalendar} from 'react-native-calendars';
44
import testIDs from '../testIDs';
55
import {agendaItems, getMarkedDates} from '../mocks/agendaItems';
66
import AgendaItem from '../mocks/AgendaItem';
77
import {getTheme, themeColor, lightThemeColor} from '../mocks/theme';
8+
import type XDate from 'xdate';
89

910
const leftArrowIcon = require('../img/previous.png');
1011
const rightArrowIcon = require('../img/next.png');
@@ -13,7 +14,7 @@ const ITEMS: any[] = agendaItems;
1314
interface Props {
1415
weekView?: boolean;
1516
}
16-
17+
const CHEVRON = require('../img/next.png');
1718
const ExpandableCalendarScreen = (props: Props) => {
1819
const {weekView} = props;
1920
const marked = useRef(getMarkedDates());
@@ -34,6 +35,35 @@ const ExpandableCalendarScreen = (props: Props) => {
3435
return <AgendaItem item={item}/>;
3536
}, []);
3637

38+
const calendarRef = useRef<{toggleCalendarPosition: () => boolean}>(null);
39+
const rotation = useRef(new Animated.Value(0));
40+
41+
const toggleCalendarExpansion = useCallback(() => {
42+
const isOpen = calendarRef.current?.toggleCalendarPosition();
43+
Animated.timing(rotation.current, {
44+
toValue: isOpen ? 1 : 0,
45+
duration: 200,
46+
useNativeDriver: true,
47+
easing: Easing.out(Easing.ease)
48+
}).start();
49+
}, []);
50+
51+
const renderHeader = useCallback(
52+
(date?: XDate) => {
53+
const rotationInDegrees = rotation.current.interpolate({
54+
inputRange: [0, 1],
55+
outputRange: ['0deg', '180deg']
56+
});
57+
return (
58+
<TouchableOpacity style={styles.header} onPress={toggleCalendarExpansion}>
59+
<Text style={styles.headerTitle}>{date?.toString('MMMM yyyy')}</Text>
60+
<Animated.Image source={CHEVRON} style={{transform: [{rotate: '-90deg'}, {rotate: rotationInDegrees}]}}/>
61+
</TouchableOpacity>
62+
);
63+
},
64+
[toggleCalendarExpansion]
65+
);
66+
3767
return (
3868
<CalendarProvider
3969
date={ITEMS[1]?.title}
@@ -49,6 +79,8 @@ const ExpandableCalendarScreen = (props: Props) => {
4979
) : (
5080
<ExpandableCalendar
5181
testID={testIDs.expandableCalendar.CONTAINER}
82+
renderHeader={renderHeader}
83+
ref={calendarRef}
5284
// horizontal={false}
5385
// hideArrows
5486
// disablePan
@@ -86,8 +118,12 @@ const styles = StyleSheet.create({
86118
paddingRight: 20
87119
},
88120
header: {
89-
backgroundColor: 'lightgrey'
121+
flexDirection: 'row',
122+
justifyContent: 'center',
123+
alignItems: 'center',
124+
marginVertical: 10
90125
},
126+
headerTitle: {fontSize: 16, fontWeight: 'bold', marginRight: 6},
91127
section: {
92128
backgroundColor: lightThemeColor,
93129
color: 'grey',

src/expandableCalendar/index.tsx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import throttle from 'lodash/throttle';
55

66
import XDate from 'xdate';
77

8-
import React, {useContext, useRef, useState, useEffect, useCallback, useMemo} from 'react';
8+
import React, {
9+
useContext,
10+
useRef,
11+
useState,
12+
useEffect,
13+
useCallback,
14+
useMemo,
15+
useImperativeHandle,
16+
forwardRef
17+
} from 'react';
918
import {
1019
AccessibilityInfo,
1120
PanResponder,
@@ -71,6 +80,10 @@ export interface ExpandableCalendarProps extends CalendarListProps {
7180
closeOnDayPress?: boolean;
7281
}
7382

83+
type ExpandableCalendarRef = {
84+
toggleCalendarPosition: () => boolean;
85+
};
86+
7487
const headerStyleOverride = {
7588
stylesheet: {
7689
calendar: {
@@ -94,7 +107,7 @@ const headerStyleOverride = {
94107
* @example: https://github.com/wix/react-native-calendars/blob/master/example/src/screens/expandableCalendar.js
95108
*/
96109

97-
const ExpandableCalendar = (props: ExpandableCalendarProps) => {
110+
const ExpandableCalendar = forwardRef<ExpandableCalendarRef, ExpandableCalendarProps>((props, ref) => {
98111
const _context = useContext(Context);
99112
const {date, setDate, numberOfDays, timelineLeftInset} = _context;
100113

@@ -370,7 +383,6 @@ const ExpandableCalendar = (props: ExpandableCalendarProps) => {
370383
_height.current = Number(_wrapperStyles.current.style.height);
371384
bounceToPosition();
372385
};
373-
374386
const numberOfDaysCondition = useMemo(() => {
375387
return !numberOfDays || numberOfDays && numberOfDays <= 1;
376388
}, [numberOfDays]);
@@ -437,8 +449,17 @@ const ExpandableCalendar = (props: ExpandableCalendarProps) => {
437449

438450
const toggleCalendarPosition = useCallback(() => {
439451
bounceToPosition(isOpen ? closedHeight : openHeight.current);
452+
return !isOpen;
440453
}, [isOpen, bounceToPosition, closedHeight]);
441454

455+
useImperativeHandle(
456+
ref,
457+
() => ({
458+
toggleCalendarPosition
459+
}),
460+
[toggleCalendarPosition]
461+
);
462+
442463
/** Events */
443464

444465
const _onPressArrowLeft = useCallback((method: () => void, month?: XDate) => {
@@ -626,20 +647,20 @@ const ExpandableCalendar = (props: ExpandableCalendarProps) => {
626647
)}
627648
</View>
628649
);
629-
};
630-
631-
export default ExpandableCalendar;
632-
633-
ExpandableCalendar.displayName = 'ExpandableCalendar';
634-
ExpandableCalendar.defaultProps = {
635-
horizontal: true,
636-
initialPosition: Positions.CLOSED,
637-
firstDay: 0,
638-
leftArrowImageSource: LEFT_ARROW,
639-
rightArrowImageSource: RIGHT_ARROW,
640-
allowShadow: true,
641-
openThreshold: PAN_GESTURE_THRESHOLD,
642-
closeThreshold: PAN_GESTURE_THRESHOLD,
643-
closeOnDayPress: true
644-
};
645-
ExpandableCalendar.positions = Positions;
650+
});
651+
652+
export default Object.assign(ExpandableCalendar, {
653+
displayName: 'ExpandableCalendar',
654+
positions: Positions,
655+
defaultProps: {
656+
horizontal: true,
657+
initialPosition: Positions.CLOSED,
658+
firstDay: 0,
659+
leftArrowImageSource: LEFT_ARROW,
660+
rightArrowImageSource: RIGHT_ARROW,
661+
allowShadow: true,
662+
openThreshold: PAN_GESTURE_THRESHOLD,
663+
closeThreshold: PAN_GESTURE_THRESHOLD,
664+
closeOnDayPress: true
665+
}
666+
});

0 commit comments

Comments
 (0)