UNPKG

@blueprintjs/datetime

Version:

Components for interacting with dates and times

118 lines 7.62 kB
/* * Copyright 2023 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import classNames from "classnames"; import * as React from "react"; import { CaptionLabel, useDayPicker, useNavigation } from "react-day-picker"; import innerText from "react-innertext"; import { Button, DISPLAYNAME_PREFIX, HTMLSelect } from "@blueprintjs/core"; import { ChevronLeft, ChevronRight } from "@blueprintjs/icons"; import { DateUtils, Months } from "../../common"; import { DatePickerCaptionClasses as CaptionClasses, ReactDayPickerClasses } from "../../common/classes"; import { useMonthSelectRightOffset } from "../../common/useMonthSelectRightOffset"; import { DatePickerContext } from "../date-picker/datePickerContext"; /** * Custom react-day-picker caption component used in non-contiguous two-month date range pickers. * * We need to override the whole caption instead of its lower-level components because react-day-picker * does not have built-in support for non-contiguous range pickers. * * @see https://daypicker.dev/guides/custom-components */ export const DatePickerCaption = (props) => { const { classNames: rdpClassNames, formatters, fromDate, toDate, labels } = useDayPicker(); const { locale, reverseMonthAndYearMenus } = React.useContext(DatePickerContext); // non-null assertion because we define these values in defaultProps const minYear = fromDate.getFullYear(); const maxYear = toDate.getFullYear(); const displayMonth = props.displayMonth.getMonth(); const displayYear = props.displayMonth.getFullYear(); const containerElement = React.useRef(null); const monthSelectElement = React.useRef(null); const { currentMonth, goToMonth, nextMonth, previousMonth } = useNavigation(); const handlePreviousClick = React.useCallback(() => previousMonth && goToMonth(previousMonth), [previousMonth, goToMonth]); const handleNextClick = React.useCallback(() => nextMonth && goToMonth(nextMonth), [nextMonth, goToMonth]); const prevButton = (React.createElement(Button, { "aria-label": labels.labelPrevious(previousMonth, { locale }), className: classNames(CaptionClasses.DATEPICKER3_NAV_BUTTON, CaptionClasses.DATEPICKER3_NAV_BUTTON_PREVIOUS), disabled: !previousMonth, icon: React.createElement(ChevronLeft, null), onClick: handlePreviousClick, variant: "minimal" })); const nextButton = (React.createElement(Button, { "aria-label": labels.labelNext(nextMonth, { locale }), className: classNames(CaptionClasses.DATEPICKER3_NAV_BUTTON, CaptionClasses.DATEPICKER3_NAV_BUTTON_NEXT), disabled: !nextMonth, icon: React.createElement(ChevronRight, null), onClick: handleNextClick, variant: "minimal" })); // build the list of available years, relying on react-day-picker's default date-fns formatter or a // user-provided formatter to localize the year "names" const { formatYearCaption } = formatters; const allYearOptions = React.useMemo(() => { const years = []; for (let year = minYear; year <= maxYear; year++) { const yearDate = new Date(year, 0); const yearCaption = formatYearCaption(yearDate, { locale }); years.push({ label: innerText(yearCaption), value: year }); } return years; }, [formatYearCaption, maxYear, minYear, locale]); // allow out-of-bounds years but disable the option. // this handles the Dec 2016 case in https://github.com/palantir/blueprint/issues/391 if (displayYear > maxYear) { const displayYearDate = new Date(displayYear, 0); const displayYearCaption = formatYearCaption(displayYearDate, { locale }); allYearOptions.push({ disabled: true, label: innerText(displayYearCaption), value: displayYear }); } const handleMonthSelectChange = React.useCallback((e) => { const newMonth = parseInt(e.target.value, 10); // ignore change events with invalid values to prevent crash on iOS Safari (#4178) if (isNaN(newMonth)) { return; } const newDate = DateUtils.clone(currentMonth); newDate.setMonth(newMonth); goToMonth(newDate); }, [currentMonth, goToMonth]); const startMonth = displayYear === minYear ? fromDate.getMonth() : 0; const endMonth = displayYear === maxYear ? toDate.getMonth() + 1 : 12; // build the list of available months, relying on react-day-picker's default date-fns formatter or a // user-provided formatter to localize the month names const { formatMonthCaption } = formatters; const allMonths = React.useMemo(() => { const months = []; for (let i = Months.JANUARY; i <= Months.DECEMBER; i++) { const monthDate = new Date(displayYear, i); const formattedMonth = formatMonthCaption(monthDate, { locale }); months.push(innerText(formattedMonth)); } return months; }, [displayYear, formatMonthCaption, locale]); const allMonthOptions = allMonths.map((month, i) => ({ label: month, value: i })); const availableMonthOptions = allMonthOptions.slice(startMonth, endMonth); const displayedMonthText = allMonths[displayMonth]; const monthSelectRightOffset = useMonthSelectRightOffset(monthSelectElement, containerElement, displayedMonthText); const monthSelect = (React.createElement(HTMLSelect, { "aria-label": labels.labelMonthDropdown(), iconProps: { style: { right: monthSelectRightOffset } }, className: classNames(CaptionClasses.DATEPICKER3_DROPDOWN_MONTH, rdpClassNames.dropdown_month), key: "month", minimal: true, onChange: handleMonthSelectChange, ref: monthSelectElement, value: displayMonth, options: availableMonthOptions })); const handleYearSelectChange = React.useCallback((e) => { const newYear = parseInt(e.target.value, 10); // ignore change events with invalid values to prevent crash on iOS Safari (#4178) if (isNaN(newYear)) { return; } const newDate = DateUtils.clone(currentMonth); newDate.setFullYear(newYear); goToMonth(newDate); }, [currentMonth, goToMonth]); const yearSelect = (React.createElement(HTMLSelect, { "aria-label": labels.labelYearDropdown(), className: classNames(CaptionClasses.DATEPICKER3_DROPDOWN_YEAR, rdpClassNames.dropdown_year), key: "year", minimal: true, onChange: handleYearSelectChange, value: displayYear, options: allYearOptions })); const orderedSelects = reverseMonthAndYearMenus ? [yearSelect, monthSelect] : [monthSelect, yearSelect]; const hiddenCaptionLabel = (React.createElement("div", { className: ReactDayPickerClasses.RDP_VHIDDEN }, React.createElement(CaptionLabel, { displayMonth: props.displayMonth, id: props.id }))); return (React.createElement("div", { className: classNames(CaptionClasses.DATEPICKER3_CAPTION, rdpClassNames.caption), ref: containerElement }, hiddenCaptionLabel, prevButton, orderedSelects, nextButton)); }; DatePickerCaption.displayName = `${DISPLAYNAME_PREFIX}.DatePickerCaption`; //# sourceMappingURL=datePickerCaption.js.map