import includes from 'lodash/includes'; import PropTypes from 'prop-types'; import XDate from 'xdate'; import React, { Fragment, ReactNode, useCallback, useMemo, forwardRef, useImperativeHandle, useRef } from 'react'; import { ActivityIndicator, Platform, View, Text, TouchableOpacity, Image, StyleProp, ViewStyle, AccessibilityActionEvent, ColorValue, ImageStyle, TextStyle } from 'react-native'; import { formatNumbers, weekDayNames } from '../../dateutils'; import { CHANGE_MONTH_LEFT_ARROW, CHANGE_MONTH_RIGHT_ARROW, HEADER_DAY_NAMES, HEADER_LOADING_INDICATOR, HEADER_MONTH_NAME // @ts-expect-error } from '../../testIDs'; import styleConstructor from './style'; import { Theme, Direction } from '../../types'; export interface CalendarHeaderProps { month?: XDate; addMonth?: (num: number) => void; /** Specify theme properties to override specific styles for calendar parts */ theme?: Theme; /** If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday */ firstDay?: number; /** Display loading indicator. Default = false */ displayLoadingIndicator?: boolean; /** Show week numbers. Default = false */ showWeekNumbers?: boolean; /** Month format in the title. Formatting values: http://arshaw.com/xdate/#Formatting */ monthFormat?: string; /** Hide day names */ hideDayNames?: boolean; /** Hide month navigation arrows */ hideArrows?: boolean; /** Replace default arrows with custom ones (direction can be 'left' or 'right') */ renderArrow?: (direction: Direction) => ReactNode; /** Handler which gets executed when press arrow icon left. It receive a callback can go back month */ onPressArrowLeft?: (method: () => void, month?: XDate) => void; //TODO: replace with string /** Handler which gets executed when press arrow icon right. It receive a callback can go next month */ onPressArrowRight?: (method: () => void, month?: XDate) => void; //TODO: replace with string /** Disable left arrow */ disableArrowLeft?: boolean; /** Disable right arrow */ disableArrowRight?: boolean; /** Apply custom disable color to selected day indexes */ disabledDaysIndexes?: number[]; /** Replace default title with custom one. the function receive a date as parameter */ renderHeader?: (date?: XDate) => ReactNode; //TODO: replace with string /** Replace default title with custom element */ customHeaderTitle?: JSX.Element; /** Provide aria-level for calendar heading for proper accessibility when used with web (react-native-web) */ webAriaLevel?: number; testID?: string; style?: StyleProp; accessibilityElementsHidden?: boolean; importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants'; titleHeaderCalendar?: any; onChangeMonth?: any, icStyle?: ImageStyle, txtStyle?: TextStyle } const accessibilityActions = [ { name: 'increment', label: 'increment' }, { name: 'decrement', label: 'decrement' } ]; const CalendarHeader = forwardRef((props: CalendarHeaderProps, ref) => { const { theme, style: propsStyle, addMonth: propsAddMonth, month, monthFormat, firstDay, hideDayNames, showWeekNumbers, hideArrows, renderArrow, onPressArrowLeft, onPressArrowRight, disableArrowLeft, disableArrowRight, disabledDaysIndexes, displayLoadingIndicator, customHeaderTitle, renderHeader, webAriaLevel, testID, accessibilityElementsHidden, importantForAccessibility, titleHeaderCalendar, onChangeMonth, icStyle, txtStyle } = props; const style = useRef(styleConstructor(theme)); useImperativeHandle(ref, () => ({ onPressLeft, onPressRight })); const addMonth = useCallback(() => { propsAddMonth?.(1); }, [propsAddMonth]); const subtractMonth = useCallback(() => { propsAddMonth?.(-1); }, [propsAddMonth]); const onPressLeft = useCallback(() => { onChangeMonth && onChangeMonth("left") if (typeof onPressArrowLeft === 'function') { return onPressArrowLeft(subtractMonth, month); } onChangeMonth && onChangeMonth("left") return subtractMonth(); }, [onPressArrowLeft, subtractMonth, month]); const onPressRight = useCallback(() => { onChangeMonth && onChangeMonth("right") if (typeof onPressArrowRight === 'function') { return onPressArrowRight(addMonth, month); } return addMonth(); }, [onPressArrowRight, addMonth, month]); const onAccessibilityAction = (event: AccessibilityActionEvent) => { switch (event.nativeEvent.actionName) { case 'decrement': onPressLeft(); break; case 'increment': onPressRight(); break; default: break; } }; const renderWeekDays = useMemo(() => { if (titleHeaderCalendar) { return titleHeaderCalendar.map((day: string, index: number) => { const dayStyle = [style.current.dayHeader]; if (includes(disabledDaysIndexes, index)) { dayStyle.push(style.current.disabledDayHeader); } const dayTextAtIndex = `dayTextAtIndex${index}`; // @ts-expect-error if (style[dayTextAtIndex]) { // @ts-expect-error dayStyle.push(style[dayTextAtIndex]); } return ( {day} ); }); } else { const weekDaysNames = weekDayNames(firstDay); return weekDaysNames.map((day: string, index: number) => { const dayStyle = [style.current.dayHeader]; if (includes(disabledDaysIndexes, index)) { dayStyle.push(style.current.disabledDayHeader); } const dayTextAtIndex = `dayTextAtIndex${index}`; // @ts-expect-error if (style[dayTextAtIndex]) { // @ts-expect-error dayStyle.push(style[dayTextAtIndex]); } return ( {day} ); }); } }, [firstDay]); const _renderHeader = () => { const webProps = Platform.OS === 'web' ? { 'aria-level': webAriaLevel } : {}; if (renderHeader) { return renderHeader(month); } if (customHeaderTitle) { return customHeaderTitle; } return ( {formatNumbers(month?.toString(monthFormat))} ); }; const _renderArrow = (direction: Direction) => { if (hideArrows) { return ; } const isLeft = direction === 'left'; const id = isLeft ? CHANGE_MONTH_LEFT_ARROW : CHANGE_MONTH_RIGHT_ARROW; const testId = testID ? `${id}-${testID}` : id; const onPress = isLeft ? onPressLeft : onPressRight; const imageSource = isLeft ? require('../img/previous.png') : require('../img/next.png'); const renderArrowDirection = isLeft ? 'left' : 'right'; const shouldDisable = isLeft ? disableArrowLeft : disableArrowRight; return ( {renderArrow ? ( renderArrow(renderArrowDirection) ) : ( // @ts-expect-error style?: StyleProp )} ); }; const renderIndicator = () => { if (displayLoadingIndicator) { return ( ); } }; const renderDayNames = () => { if (!hideDayNames) { return ( {showWeekNumbers && } {renderWeekDays} ); } }; return ( {_renderArrow('left')} {_renderHeader()} {renderIndicator()} {_renderArrow('right')} {renderDayNames()} ); }); export default CalendarHeader; CalendarHeader.displayName = 'CalendarHeader'; CalendarHeader.propTypes = { month: PropTypes.instanceOf(XDate), addMonth: PropTypes.func, theme: PropTypes.object, firstDay: PropTypes.number, displayLoadingIndicator: PropTypes.bool, showWeekNumbers: PropTypes.bool, monthFormat: PropTypes.string, hideDayNames: PropTypes.bool, hideArrows: PropTypes.bool, renderArrow: PropTypes.func, onPressArrowLeft: PropTypes.func, onPressArrowRight: PropTypes.func, disableArrowLeft: PropTypes.bool, disableArrowRight: PropTypes.bool, disabledDaysIndexes: PropTypes.any, renderHeader: PropTypes.any, customHeaderTitle: PropTypes.any, webAriaLevel: PropTypes.number }; CalendarHeader.defaultProps = { monthFormat: 'MMMM yyyy', webAriaLevel: 1 };