@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
JavaScript
// 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