UNPKG

@awsui/components-react

Version:

On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en

178 lines 12 kB
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { useState } from 'react'; import clsx from 'clsx'; import { addMonths, addYears, endOfDay, isAfter, isBefore, isSameMonth, isSameYear, startOfDay, startOfMonth, startOfYear, } from 'date-fns'; import { useUniqueId } from '@awsui/component-toolkit/internal'; import { getDateLabel, renderTimeLabel } from '../../calendar/utils/intl'; import { getBaseDay } from '../../calendar/utils/navigation-day'; import { getBaseMonth } from '../../calendar/utils/navigation-month'; import { useInternalI18n } from '../../i18n/context.js'; import { useMobile } from '../../internal/hooks/use-mobile/index.js'; import { formatDate, formatDateTime, parseDate, splitDateTime } from '../../internal/utils/date-time'; import { normalizeLocale, normalizeStartOfWeek } from '../../internal/utils/locale'; import InternalLiveRegion from '../../live-region/internal'; import SpaceBetween from '../../space-between/internal'; import { Grids } from './grids'; import CalendarHeader from './header'; import RangeInputs from './range-inputs.js'; import { findDateToFocus, findMonthToDisplay, findMonthToFocus, findYearToDisplay } from './utils'; import styles from '../styles.css.js'; import testutilStyles from '../test-classes/styles.css.js'; export default function DateRangePickerCalendar({ value, setValue, locale = '', startOfWeek, isDateEnabled = () => true, dateDisabledReason = () => '', i18nStrings, dateOnly = false, timeInputFormat, dateInputFormat, customAbsoluteRangeControl, granularity = 'day', }) { var _a, _b; const isSingleGrid = useMobile(); const isMonthPicker = granularity === 'month'; const hideTime = isMonthPicker || dateOnly; const normalizedLocale = normalizeLocale('DateRangePicker', locale); const normalizedStartOfWeek = normalizeStartOfWeek(startOfWeek, normalizedLocale); const i18n = useInternalI18n('date-range-picker'); const [announcement, setAnnouncement] = useState(''); const findPageToDisplay = isMonthPicker ? findYearToDisplay : findMonthToDisplay; const isSamePage = isMonthPicker ? isSameYear : isSameMonth; const addPage = isMonthPicker ? addYears : addMonths; const startOfPage = isMonthPicker ? startOfYear : startOfMonth; const findItemToFocus = isMonthPicker ? findMonthToFocus : findDateToFocus; const [currentPage, setCurrentPage] = useState(() => findPageToDisplay(value, isSingleGrid)); const [focusedDate, setFocusedDate] = useState(() => { if (value.start.date) { const startDate = parseDate(value.start.date); if (isSamePage(startDate, currentPage)) { return startDate; } if (!isSingleGrid && isSamePage(startDate, addPage(currentPage, -1))) { return startDate; } } return findItemToFocus(parseDate(value.start.date), currentPage, isDateEnabled); }); const updateCurrentPage = (startDate) => { if ((isMonthPicker && startDate.length >= 4) || startDate.length >= 8) { const newCurrentPage = startOfPage(parseDate(startDate)); setCurrentPage(isSingleGrid ? newCurrentPage : addPage(newCurrentPage, 1)); } }; // recommended to include the start/end time announced with the selection // because the user is not aware of the fact that a start/end time is also set as soon as they select a date const announceStart = (startDate) => { return `${i18n('i18nStrings.startDateLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.startDateLabel)}, ${getDateLabel(normalizedLocale, startDate)}${hideTime ? '' : `, ${i18n('i18nStrings.startTimeLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.startTimeLabel)}, ${renderTimeLabel(normalizedLocale, startDate, timeInputFormat)}`}. `; }; const announceEnd = (endDate) => { return `${i18n('i18nStrings.endDateLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.endDateLabel)}, ${getDateLabel(normalizedLocale, endDate)}${hideTime ? '' : `, ${i18n('i18nStrings.endTimeLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.endTimeLabel)}, ${renderTimeLabel(normalizedLocale, endDate, timeInputFormat)}`}. `; }; const renderSelectedAbsoluteRangeAriaLive = i18n('i18nStrings.renderSelectedAbsoluteRangeAriaLive', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.renderSelectedAbsoluteRangeAriaLive, format => (startDate, endDate) => format({ startDate, endDate })); const announceRange = (startDate, endDate) => { if (!renderSelectedAbsoluteRangeAriaLive) { return `${getDateLabel(normalizedLocale, startDate)} – ${getDateLabel(normalizedLocale, endDate)}`; } return renderSelectedAbsoluteRangeAriaLive(getDateLabel(normalizedLocale, startDate), getDateLabel(normalizedLocale, endDate)); }; const onSelectDateHandler = (selectedDate) => { const { start, end } = value; let newStart = undefined; let newEnd = undefined; let announcement = ''; // If both fields are empty, we set the start date if (!start.date && !end.date) { newStart = startOfDay(selectedDate); announcement = announceStart(newStart); } // If both fields are set, we start new else if (start.date && end.date) { newStart = startOfDay(selectedDate); newEnd = null; announcement = announceStart(newStart); } // If only the END date is empty, we fill it (and swap dates if needed) else if (start.date && !end.date) { const parsedStartDate = parseDate(start.date); if (isBefore(selectedDate, parsedStartDate)) { // The user has selected the range backwards, so we swap start and end newStart = startOfDay(selectedDate); newEnd = endOfDay(parsedStartDate); announcement = announceStart(newStart) + announceRange(newStart, newEnd); } else { newEnd = endOfDay(selectedDate); announcement = announceEnd(newEnd) + announceRange(parsedStartDate, newEnd); } } // If only the START date is empty, we fill it (and swap dates if needed) else if (!start.date && end.date) { const existingEndDate = parseDate(end.date); if (isAfter(selectedDate, existingEndDate)) { // The user has selected the range backwards, so we swap start and end newStart = startOfDay(existingEndDate); newEnd = endOfDay(selectedDate); announcement = announceEnd(newEnd) + announceRange(newStart, newEnd); } else { newStart = startOfDay(selectedDate); announcement = announceStart(newStart) + announceRange(newStart, existingEndDate); } } const formatValue = (date, previous, includeTime) => { if (date === null) { // explicitly reset to empty return { date: '', time: '' }; } else if (date === undefined) { // keep old value return previous; } if (!includeTime) { return { date: formatDate(date), time: '', }; } return splitDateTime(formatDateTime(date)); }; setValue({ start: formatValue(newStart, value.start, !hideTime), end: formatValue(newEnd, value.end, !hideTime), }); setAnnouncement(announcement); }; const onHeaderChangePageHandler = (amount) => { const addPageFn = isMonthPicker ? addYears : addMonths; const getBaseFn = isMonthPicker ? getBaseMonth : getBaseDay; const newBasePage = addPageFn(currentPage, amount); setCurrentPage(newBasePage); const newBaseDate = getBaseFn(newBasePage, isDateEnabled); setFocusedDate(newBaseDate); }; const onChangeStartDate = (value) => { setValue((oldValue) => ({ ...oldValue, start: { ...oldValue.start, date: value }, })); updateCurrentPage(value); }; const interceptedSetValue = newValue => { setValue(oldValue => { const updated = typeof newValue === 'function' ? newValue(oldValue) : newValue; updateCurrentPage(updated.start.date); return updated; }); }; const headingIdPrefix = useUniqueId('date-range-picker-calendar-heading'); return (React.createElement(React.Fragment, null, React.createElement("div", { className: clsx(styles['calendar-container'], { [styles['one-grid']]: isSingleGrid, }) }, React.createElement(SpaceBetween, { size: "s" }, React.createElement("div", { className: clsx(styles.calendar, { [styles['one-grid']]: isSingleGrid, }) }, React.createElement(CalendarHeader, { granularity: granularity, baseDate: currentPage, locale: normalizedLocale, onChangePage: onHeaderChangePageHandler, previousPageLabel: isMonthPicker ? i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.previousYearAriaLabel : i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.previousMonthAriaLabel, nextPageLabel: isMonthPicker ? i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.nextYearAriaLabel : i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.nextMonthAriaLabel, isSingleGrid: isSingleGrid, headingIdPrefix: headingIdPrefix }), React.createElement(Grids, { granularity: granularity, isSingleGrid: isSingleGrid, locale: normalizedLocale, baseDate: currentPage, focusedDate: focusedDate, onFocusedDateChange: setFocusedDate, isDateEnabled: isDateEnabled, dateDisabledReason: dateDisabledReason, onSelectDate: onSelectDateHandler, onPageChange: setCurrentPage, startOfWeek: normalizedStartOfWeek, todayAriaLabel: i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.todayAriaLabel, currentMonthAriaLabel: i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.currentMonthAriaLabel, selectedStartDate: ((_a = value === null || value === void 0 ? void 0 : value.start) === null || _a === void 0 ? void 0 : _a.date) ? parseDate(value.start.date, !isMonthPicker) : null, selectedEndDate: ((_b = value === null || value === void 0 ? void 0 : value.end) === null || _b === void 0 ? void 0 : _b.date) ? parseDate(value.end.date, !isMonthPicker) : null, headingIdPrefix: headingIdPrefix })), React.createElement(RangeInputs, { startDate: value.start.date, onChangeStartDate: onChangeStartDate, startTime: value.start.time, onChangeStartTime: value => setValue(oldValue => ({ ...oldValue, start: { ...oldValue.start, time: value } })), endDate: value.end.date, onChangeEndDate: value => setValue(oldValue => ({ ...oldValue, end: { ...oldValue.end, date: value } })), endTime: value.end.time, onChangeEndTime: value => setValue(oldValue => ({ ...oldValue, end: { ...oldValue.end, time: value } })), i18nStrings: i18nStrings, dateOnly: dateOnly, timeInputFormat: timeInputFormat, dateInputFormat: dateInputFormat, granularity: granularity }), customAbsoluteRangeControl && React.createElement("div", null, customAbsoluteRangeControl(value, interceptedSetValue)))), React.createElement(InternalLiveRegion, { className: testutilStyles['calendar-aria-live'], hidden: true, tagName: "span" }, announcement))); } //# sourceMappingURL=index.js.map