UNPKG

@chayns-components/date

Version:

A set of beautiful React components for developing your own applications with chayns.

293 lines (292 loc) • 11.9 kB
import { ComboBox, Icon } from '@chayns-components/core'; import { Language } from 'chayns-api'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CalendarType } from '../../types/calendar'; import { getNewDate, getYearsBetween, isDateInRange } from '../../utils/calendar'; import { addYears, differenceInCalendarMonths, isSameDay, isSameMonth, isWithinInterval, subYears } from '../../utils/date'; import { StyledCalendar, StyledCalendarIconWrapper, StyledCalendarIconWrapperContent, StyledCalendarIconWrapperPseudo, StyledPseudoMonthYearPicker } from './Calendar.styles'; import MonthWrapper from './month-wrapper/MonthWrapper'; const DEFAULT_MAX_DATE = addYears(new Date(), 1); const DEFAULT_MIN_DATE = subYears(new Date(), 1); const Calendar = ({ locale = Language.German, maxDate = DEFAULT_MAX_DATE, minDate = DEFAULT_MIN_DATE, highlightedDates, onChange, customThumbColors, selectedDate, selectedDates, selectedDateInterval, categories, isDisabled, type = CalendarType.Single, shouldShowHighlightsInMonthOverlay = true, disabledDates = [], showMonthYearPickers: showMonthYearPickersProp, onShownDatesChange = () => {}, currentDateBackgroundColor }) => { const [currentDate, setCurrentDate] = useState(); const [shouldRenderTwoMonths, setShouldRenderTwoMonths] = useState(true); const [internalSelectedDate, setInternalSelectedDate] = useState(() => type === CalendarType.Multiple ? [] : undefined); const [direction, setDirection] = useState(); const [width, setWidth] = useState(0); const showMonthYearPickers = useMemo(() => { const hasMultipleMonths = differenceInCalendarMonths(maxDate, minDate) > 0; const hasMultipleYears = getYearsBetween(minDate, maxDate).length > 1; return !!(showMonthYearPickersProp && (hasMultipleMonths || hasMultipleYears)); }, [minDate, maxDate, showMonthYearPickersProp]); const calendarRef = useRef(null); useEffect(() => { if (currentDate) { const start = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); if (shouldRenderTwoMonths) { const end = new Date(currentDate.getFullYear(), currentDate.getMonth() + 2, 0); onShownDatesChange({ start, end }); } else { const end = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); onShownDatesChange({ start, end }); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDate, shouldRenderTwoMonths]); useEffect(() => { const bounds = { start: minDate, end: maxDate }; if (type === CalendarType.Single) { if (selectedDate) { const isDisabledDate = disabledDates.some(disabledDate => isSameDay(selectedDate, disabledDate)); const isDateInBounds = isWithinInterval(selectedDate, bounds); if (!isDisabledDate && isDateInBounds) { setInternalSelectedDate(selectedDate); } else { console.warn('[@chayns-components/date] Warning: Failed to set selectedDate, because it is disabled or out of bounds.', '\nselectedDate:', selectedDate, ...(isDisabledDate ? ['\nselectedDate is disabled'] : []), ...(isDateInBounds ? [] : ['\nselectedDate is outside of bounds:', { minDate, maxDate }])); setInternalSelectedDate(() => undefined); } } else { setInternalSelectedDate(() => undefined); } } else if (type === CalendarType.Multiple) { if (selectedDates) { const disabledSelectedDates = []; const datesOutsideOfBounds = []; const filteredDates = selectedDates.filter(date => { if (disabledDates.some(disabledDate => isSameDay(date, disabledDate))) { disabledSelectedDates.push(date); return false; } if (!isWithinInterval(date, bounds)) { datesOutsideOfBounds.push(date); return false; } return true; }); if (disabledSelectedDates.length > 0 || datesOutsideOfBounds.length > 0) { console.warn('[@chayns-components/date] Warning: Failed to set all selectedDates, because some are disabled or out of bounds.', ...(disabledSelectedDates.length > 0 ? ['\nselectedDates that are disabled:', disabledSelectedDates] : []), ...(datesOutsideOfBounds.length > 0 ? ['\nselectedDates that are outside of bounds:', datesOutsideOfBounds, 'bounds:', { minDate, maxDate }] : [])); } setInternalSelectedDate(filteredDates); } else { setInternalSelectedDate([]); } } else if (type === CalendarType.Interval) { if (selectedDateInterval) { const intervalIncludesDisabledDate = selectedDateInterval.end && disabledDates.some(disabledDate => isWithinInterval(disabledDate, { start: selectedDateInterval.start, end: selectedDateInterval.end })); const intervalIsInBounds = isWithinInterval(selectedDateInterval.start, bounds) && (!selectedDateInterval.end || isWithinInterval(selectedDateInterval.end, bounds)); if (!intervalIncludesDisabledDate && intervalIsInBounds) { setInternalSelectedDate(selectedDateInterval); } else { console.warn('[@chayns-components/date] Warning: Failed to set selectedDateInterval, because it includes disabled dates or dates that are out of bounds.', '\nselectedDateInterval:', selectedDateInterval, ...(intervalIncludesDisabledDate ? ['\ndisabled dates:', disabledDates] : []), ...(intervalIsInBounds ? [] : ['\nbounds:', { minDate, maxDate }])); setInternalSelectedDate(() => undefined); } } } }, [type, selectedDate, selectedDates, selectedDateInterval, disabledDates, minDate, maxDate]); useEffect(() => { if (calendarRef.current) { const resizeObserver = new ResizeObserver(entries => { if (entries && entries[0]) { const observedWidth = entries[0].contentRect.width; setWidth(observedWidth - 30); if (observedWidth < 430) { setShouldRenderTwoMonths(false); } else { setShouldRenderTwoMonths(true); } } }); resizeObserver.observe(calendarRef.current); return () => { resizeObserver.disconnect(); }; } return () => {}; }, []); useEffect(() => { setCurrentDate(prevDate => isDateInRange({ minDate, maxDate, currentDate: prevDate || new Date() })); }, [maxDate, minDate]); const handleLeftArrowClick = useCallback(() => { if (direction) return; setDirection('left'); setCurrentDate(prevDate => { if (!prevDate) { return prevDate; } const newDate = getNewDate(-1, prevDate); return isDateInRange({ minDate, maxDate, currentDate: newDate }); }); }, [maxDate, minDate, direction]); const handleRightArrowClick = useCallback(() => { if (direction) return; setDirection('right'); setCurrentDate(prevDate => { if (!prevDate) { return prevDate; } const newDate = getNewDate(1, prevDate); return isDateInRange({ minDate, maxDate, currentDate: newDate }); }); }, [maxDate, minDate, direction]); const handleSelect = useCallback(date => { setInternalSelectedDate(prevDate => { let onChangePayload = null; let newInternalSelectedDate; if (type === CalendarType.Single) { onChangePayload = date; newInternalSelectedDate = date; } else if (type === CalendarType.Multiple) { const prevSelectedDates = prevDate; // Selects or unselects date , depending on if it is already selected. if (prevSelectedDates.some(d => isSameDay(d, date))) { newInternalSelectedDate = prevSelectedDates.filter(d => !isSameDay(d, date)); } else { newInternalSelectedDate = [...prevSelectedDates, date]; } onChangePayload = newInternalSelectedDate; } else if (type === CalendarType.Interval) { const prevSelectedDateInterval = prevDate; const updateInterval = (start, end) => { const newInterval = { start, end }; onChangePayload = newInterval; newInternalSelectedDate = newInterval; }; // Sets first selection as interval start. if (!prevSelectedDateInterval) { updateInterval(date); } else if (prevSelectedDateInterval.start && !prevSelectedDateInterval.end) { // Sets second selection as interval start, if it is earlier than the previous interval start. // Else sets it as interval end. if (date < prevSelectedDateInterval.start) { updateInterval(date); } else { updateInterval(prevSelectedDateInterval.start, date); } } else { // Resets interval if a third date is selected. updateInterval(date); } } if (typeof onChange === 'function' && onChangePayload) { onChange(onChangePayload); } return newInternalSelectedDate; }); }, [type, onChange]); const handleAnimationFinished = () => { setDirection(() => undefined); }; const ShouldShowLeftArrow = useMemo(() => { if (!currentDate) { return false; } return !isSameMonth(currentDate, minDate); }, [currentDate, minDate]); const ShouldShowRightArrow = useMemo(() => { if (!currentDate) { return false; } return !isSameMonth(currentDate, maxDate); }, [currentDate, maxDate]); return /*#__PURE__*/React.createElement(StyledCalendar, { ref: calendarRef, $isDisabled: isDisabled }, ShouldShowLeftArrow ? /*#__PURE__*/React.createElement(StyledCalendarIconWrapper, { onClick: handleLeftArrowClick }, /*#__PURE__*/React.createElement(StyledCalendarIconWrapperContent, null, showMonthYearPickers && /*#__PURE__*/React.createElement(StyledPseudoMonthYearPicker, null, /*#__PURE__*/React.createElement(ComboBox, { lists: [{ list: [] }], placeholder: "" })), /*#__PURE__*/React.createElement(Icon, { icons: ['fa fa-angle-left'] }))) : /*#__PURE__*/React.createElement(StyledCalendarIconWrapperPseudo, null), currentDate && /*#__PURE__*/React.createElement(MonthWrapper, { shouldRenderTwo: shouldRenderTwoMonths, currentDate: currentDate, width: width, locale: locale, direction: direction, customThumbColors: customThumbColors, onSelect: handleSelect, selectedDate: internalSelectedDate, highlightedDates: highlightedDates, categories: categories, onAnimationFinished: handleAnimationFinished, minDate: minDate, maxDate: maxDate, type: type, disabledDates: disabledDates, setCurrentDate: setCurrentDate, shouldShowHighlightsInMonthOverlay: shouldShowHighlightsInMonthOverlay, showMonthYearPickers: showMonthYearPickers, handleLeftArrowClick: handleLeftArrowClick, handleRightArrowClick: handleRightArrowClick, currentDateBackgroundColor: currentDateBackgroundColor }), ShouldShowRightArrow ? /*#__PURE__*/React.createElement(StyledCalendarIconWrapper, { onClick: handleRightArrowClick }, /*#__PURE__*/React.createElement(StyledCalendarIconWrapperContent, null, showMonthYearPickers && /*#__PURE__*/React.createElement(StyledPseudoMonthYearPicker, null, /*#__PURE__*/React.createElement(ComboBox, { lists: [{ list: [] }], placeholder: "" })), /*#__PURE__*/React.createElement(Icon, { icons: ['fa fa-angle-right'] }))) : /*#__PURE__*/React.createElement(StyledCalendarIconWrapperPseudo, null)); }; Calendar.displayName = 'Calendar'; export default Calendar; //# sourceMappingURL=Calendar.js.map