react-native-calendars
Version:
React Native Calendar Components
146 lines (145 loc) • 7.05 kB
JavaScript
import includes from 'lodash/includes';
import XDate from 'xdate';
import React, { Fragment, useCallback, useMemo, forwardRef, useImperativeHandle, useRef } from 'react';
import { ActivityIndicator, Platform, View, Text, TouchableOpacity, Image } from 'react-native';
import { formatNumbers, weekDayNames } from '../../dateutils';
import styleConstructor from './style';
const accessibilityActions = [
{ name: 'increment', label: 'increment' },
{ name: 'decrement', label: 'decrement' }
];
const CalendarHeader = forwardRef((props, ref) => {
const { theme, style: propsStyle, addMonth: propsAddMonth, month, monthFormat = 'MMMM yyyy', firstDay, hideDayNames, showWeekNumbers, hideArrows, renderArrow, onPressArrowLeft, onPressArrowRight, arrowsHitSlop = 20, disableArrowLeft, disableArrowRight, disabledDaysIndexes, displayLoadingIndicator, customHeaderTitle, renderHeader, webAriaLevel = 1, testID, accessibilityElementsHidden, importantForAccessibility, numberOfDays, current = '', timelineLeftInset, onHeaderLayout } = props;
const numberOfDaysCondition = useMemo(() => {
return numberOfDays && numberOfDays > 1;
}, [numberOfDays]);
const style = useRef(styleConstructor(theme));
const headerStyle = useMemo(() => {
return [style.current.header, numberOfDaysCondition ? style.current.partialHeader : undefined];
}, [numberOfDaysCondition]);
const partialWeekStyle = useMemo(() => {
return [style.current.partialWeek, { paddingLeft: timelineLeftInset }];
}, [timelineLeftInset]);
const dayNamesStyle = useMemo(() => {
return [style.current.week, numberOfDaysCondition ? partialWeekStyle : undefined];
}, [numberOfDaysCondition, partialWeekStyle]);
const hitSlop = useMemo(() => typeof arrowsHitSlop === 'number'
? { top: arrowsHitSlop, left: arrowsHitSlop, bottom: arrowsHitSlop, right: arrowsHitSlop }
: arrowsHitSlop, [arrowsHitSlop]);
useImperativeHandle(ref, () => ({
onPressLeft,
onPressRight
}));
const addMonth = useCallback(() => {
propsAddMonth?.(1);
}, [propsAddMonth]);
const subtractMonth = useCallback(() => {
propsAddMonth?.(-1);
}, [propsAddMonth]);
const onPressLeft = useCallback(() => {
if (typeof onPressArrowLeft === 'function') {
return onPressArrowLeft(subtractMonth, month);
}
return subtractMonth();
}, [onPressArrowLeft, subtractMonth, month]);
const onPressRight = useCallback(() => {
if (typeof onPressArrowRight === 'function') {
return onPressArrowRight(addMonth, month);
}
return addMonth();
}, [onPressArrowRight, addMonth, month]);
const onAccessibilityAction = useCallback((event) => {
switch (event.nativeEvent.actionName) {
case 'decrement':
onPressLeft();
break;
case 'increment':
onPressRight();
break;
default:
break;
}
}, [onPressLeft, onPressRight]);
const renderWeekDays = useMemo(() => {
const dayOfTheWeek = new XDate(current).getDay();
const weekDaysNames = numberOfDaysCondition ? weekDayNames(dayOfTheWeek) : weekDayNames(firstDay);
const dayNames = numberOfDaysCondition ? weekDaysNames.slice(0, numberOfDays) : weekDaysNames;
return dayNames.map((day, index) => {
const dayStyle = [style.current.dayHeader];
if (includes(disabledDaysIndexes, index)) {
dayStyle.push(style.current.disabledDayHeader);
}
const dayTextAtIndex = `dayTextAtIndex${index}`;
if (style.current[dayTextAtIndex]) {
dayStyle.push(style.current[dayTextAtIndex]);
}
return (<Text allowFontScaling={false} key={index} style={dayStyle} numberOfLines={1} accessibilityLabel={''} testID={`${testID}.dayName_${day}`}>
{day}
</Text>);
});
}, [firstDay, current, numberOfDaysCondition, numberOfDays, disabledDaysIndexes]);
const _renderHeader = () => {
const webProps = Platform.OS === 'web' ? { 'aria-level': webAriaLevel } : {};
if (renderHeader) {
return renderHeader(month, { testID });
}
if (customHeaderTitle) {
return customHeaderTitle;
}
return (<Fragment>
<Text allowFontScaling={false} style={style.current.monthText} testID={`${testID}.title`} {...webProps}>
{formatNumbers(month?.toString(monthFormat))}
</Text>
</Fragment>);
};
const _renderArrow = (direction) => {
if (hideArrows) {
return <View />;
}
const isLeft = direction === 'left';
const arrowDirection = isLeft ? 'left' : 'right';
const arrowId = `${arrowDirection}Arrow`;
const shouldDisable = isLeft ? disableArrowLeft : disableArrowRight;
const onPress = !shouldDisable ? isLeft ? onPressLeft : onPressRight : undefined;
const imageSource = isLeft ? require('../img/previous.png') : require('../img/next.png');
return (<TouchableOpacity onPress={onPress} disabled={shouldDisable} style={style.current.arrow} hitSlop={hitSlop} testID={`${testID}.${arrowId}`} importantForAccessibility={'no-hide-descendants'}>
{renderArrow ? renderArrow(arrowDirection) : <Image source={imageSource} style={shouldDisable ? style.current.disabledArrowImage : style.current.arrowImage}/>}
</TouchableOpacity>);
};
const renderIndicator = () => {
if (displayLoadingIndicator) {
return (<ActivityIndicator color={theme?.indicatorColor} testID={`${testID}.loader`}/>);
}
};
const renderWeekNumbersSpace = () => {
return showWeekNumbers && <View style={style.current.dayHeader}/>;
};
const renderDayNames = () => {
if (!hideDayNames) {
return (<View style={dayNamesStyle} testID={`${testID}.dayNames`} importantForAccessibility={'no-hide-descendants'}>
{renderWeekNumbersSpace()}
{renderWeekDays}
</View>);
}
};
return (<View testID={testID} style={propsStyle} accessible accessibilityRole={'adjustable'} accessibilityActions={accessibilityActions} onAccessibilityAction={onAccessibilityAction} accessibilityElementsHidden={accessibilityElementsHidden} // iOS
importantForAccessibility={importantForAccessibility} // Android
onLayout={onHeaderLayout}>
<View style={headerStyle}>
{_renderArrow('left')}
<View style={style.current.headerContainer} importantForAccessibility={'no-hide-descendants'}>
{_renderHeader()}
{renderIndicator()}
</View>
{_renderArrow('right')}
</View>
{renderDayNames()}
</View>);
});
export default CalendarHeader;
CalendarHeader.displayName = 'CalendarHeader';
CalendarHeader.defaultProps = {
monthFormat: 'MMMM yyyy',
webAriaLevel: 1,
arrowsHitSlop: 20
};