"lodash": "4.17.21",
"lunr": "2.3.9",
"react": "16.14.0",
- "react-day-picker": "8.5.1",
+ "react-day-picker": "8.6.0",
"react-dom": "16.14.0",
"react-draggable": "4.4.5",
"react-helmet-async": "1.3.0",
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getDate, getMonth, getYear, subDays } from 'date-fns';
-import selectEvent from 'react-select-event';
import { byPlaceholderText, byRole, byText } from 'testing-library-selector';
import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { now } from '../../../../helpers/dates';
-import { getShortMonthName } from '../../../../helpers/l10n';
+import { getMonthName } from '../../../../helpers/l10n';
import { renderAppWithAdminContext } from '../../../../helpers/testReactTestingUtils';
import { AdminPageExtension } from '../../../../types/extension';
import { SettingsKey } from '../../../../types/settings';
downloadSentenceStart: byText('audit_logs.download_start.sentence.1'),
startDateInput: byPlaceholderText('start_date'),
endDateInput: byPlaceholderText('end_date'),
- dateInputMonthSelect: byRole('combobox', { name: 'select_month' }),
- dateInputYearSelect: byRole('combobox', { name: 'select_year' }),
+ dateInputMonthSelect: byRole('combobox', { name: 'Month:' }),
+ dateInputYearSelect: byRole('combobox', { name: 'Year:' }),
};
let handler: SettingsServiceMock;
expect(ui.downloadButton.get()).toHaveAttribute('aria-disabled', 'true');
await user.click(ui.startDateInput.get());
- await selectEvent.select(ui.dateInputMonthSelect.get(), [getShortMonthName(getMonth(startDay))]);
- await selectEvent.select(ui.dateInputYearSelect.get(), [getYear(startDay)]);
+ await user.selectOptions(ui.dateInputMonthSelect.get(), getMonthName(getMonth(startDay)));
+ await user.selectOptions(ui.dateInputYearSelect.get(), getYear(startDay).toString());
await user.click(screen.getByText(getDate(startDay)));
await user.click(ui.endDateInput.get());
- await selectEvent.select(ui.dateInputMonthSelect.get(), [getShortMonthName(getMonth(endDate))]);
- await selectEvent.select(ui.dateInputYearSelect.get(), [getYear(endDate)]);
+ await user.selectOptions(ui.dateInputMonthSelect.get(), getMonthName(getMonth(endDate)));
+ await user.selectOptions(ui.dateInputYearSelect.get(), getYear(endDate).toString());
await user.click(screen.getByText(getDate(endDate)));
expect(await ui.downloadButton.find()).toHaveAttribute('aria-disabled', 'false');
.rdp {
--rdp-cell-size: 30px;
- --rdp-outline: none;
+ /* Ensures the month/year dropdowns do not move on click, but rdp outline is not shown */
+ --rdp-outline: 2px solid transparent;
+ --rdp-outline-selected: 2px solid transparent;
+}
+
+.rdp-caption_label {
+ /* Avoid calendar width to change when we cycle through months */
+ font-size: 115%;
}
.rdp-day_selected {
left: initial;
right: 0;
}
-
-.date-input-calendar-nav {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: var(--gridSize);
- padding-left: var(--gridSize);
- padding-right: var(--gridSize);
-}
-
-.date-input-calender-month {
- display: flex;
- justify-content: center;
-}
-
-.date-input-calender-month .date-input-calender-month-select {
- width: 70px;
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
-import { addMonths, setMonth, setYear, subMonths } from 'date-fns';
-import { range } from 'lodash';
import * as React from 'react';
import { ActiveModifiers, DayPicker, Matcher } from 'react-day-picker';
import 'react-day-picker/dist/style.css';
import { injectIntl, WrappedComponentProps } from 'react-intl';
-import { ButtonIcon, ClearButton } from '../../components/controls/buttons';
+import { ClearButton } from '../../components/controls/buttons';
import OutsideClickHandler from '../../components/controls/OutsideClickHandler';
import CalendarIcon from '../../components/icons/CalendarIcon';
-import ChevronLeftIcon from '../../components/icons/ChevronLeftIcon';
-import ChevronRightIcon from '../../components/icons/ChevronRightIcon';
-import {
- getMonthName,
- getShortMonthName,
- getShortWeekDayName,
- translate,
- translateWithParameters,
-} from '../../helpers/l10n';
+import { getShortWeekDayName, translate } from '../../helpers/l10n';
import './DateInput.css';
import EscKeydownHandler from './EscKeydownHandler';
import FocusOutHandler from './FocusOutHandler';
-import Select from './Select';
+
+// When no minDate is given, year dropdown will show year options up to PAST_MAX_YEARS in the past
+const YEARS_TO_DISPLAY = 10;
interface Props {
alignRight?: boolean;
lastHovered?: Date;
}
-const MONTHS_IN_YEAR = 12;
-const YEARS_TO_DISPLAY = 10;
-
export default class DateInput extends React.PureComponent<Props, State> {
input?: HTMLInputElement | null;
constructor(props: Props) {
super(props);
+
this.state = { currentMonth: props.value || props.currentMonth || new Date(), open: false };
}
this.setState({ lastHovered: modifiers.disabled ? undefined : day });
};
- handleCurrentMonthChange = ({ value }: { value: number }) => {
- this.setState((state: State) => ({ currentMonth: setMonth(state.currentMonth, value) }));
- };
-
- handleCurrentYearChange = ({ value }: { value: number }) => {
- this.setState((state) => ({ currentMonth: setYear(state.currentMonth, value) }));
- };
-
- handlePreviousMonthClick = () => {
- this.setState((state) => ({ currentMonth: subMonths(state.currentMonth, 1) }));
- };
-
- handleNextMonthClick = () => {
- this.setState((state) => ({ currentMonth: addMonths(state.currentMonth, 1) }));
- };
-
- getPreviousMonthAriaLabel = () => {
- const { currentMonth } = this.state;
- const previous = (currentMonth.getMonth() + MONTHS_IN_YEAR - 1) % MONTHS_IN_YEAR;
-
- return translateWithParameters(
- 'show_month_x_of_year_y',
- getMonthName(previous),
- currentMonth.getFullYear() - Math.floor(previous / (MONTHS_IN_YEAR - 1))
- );
- };
-
- getNextMonthAriaLabel = () => {
- const { currentMonth } = this.state;
-
- const next = (currentMonth.getMonth() + MONTHS_IN_YEAR + 1) % MONTHS_IN_YEAR;
-
- return translateWithParameters(
- 'show_month_x_of_year_y',
- getMonthName(next),
- currentMonth.getFullYear() + 1 - Math.ceil(next / (MONTHS_IN_YEAR - 1))
- );
- };
-
render() {
const {
alignRight,
highlightFrom,
highlightTo,
minDate,
+ maxDate = new Date(),
value: selectedDay,
name,
className,
} = this.props;
const { lastHovered, currentMonth, open } = this.state;
- const after = this.props.maxDate || new Date();
-
- const years = range(new Date().getFullYear() - YEARS_TO_DISPLAY, new Date().getFullYear() + 1);
- const yearOptions = years.map((year) => ({ label: String(year), value: year }));
- const monthOptions = range(MONTHS_IN_YEAR).map((month) => ({
- label: getShortMonthName(month),
- value: month,
- }));
+ // Infer start and end dropdown year from min/max dates, if set
+ const fromYear = minDate ? minDate.getFullYear() : new Date().getFullYear() - YEARS_TO_DISPLAY;
+ const toYear = maxDate ? maxDate.getFullYear() : new Date().getFullYear() + 1;
let highlighted: Matcher = false;
const lastHoveredOrValue = lastHovered || selectedDay;
/>
)}
{open && (
- <form className={classNames('date-input-calendar', { 'align-right': alignRight })}>
- <fieldset
- className="date-input-calendar-nav"
- aria-label={translateWithParameters(
- 'date.select_month_and_year_x',
- `${getMonthName(currentMonth.getMonth())}, ${currentMonth.getFullYear()}`
- )}
- >
- <ButtonIcon
- className="button-small"
- aria-label={this.getPreviousMonthAriaLabel()}
- onClick={this.handlePreviousMonthClick}
- >
- <ChevronLeftIcon />
- </ButtonIcon>
- <div className="date-input-calender-month">
- <Select
- aria-label={translate('select_month')}
- className="date-input-calender-month-select"
- onChange={this.handleCurrentMonthChange}
- options={monthOptions}
- value={monthOptions.find(
- (month) => month.value === currentMonth.getMonth()
- )}
- />
- <Select
- aria-label={translate('select_year')}
- className="date-input-calender-month-select spacer-left"
- onChange={this.handleCurrentYearChange}
- options={yearOptions}
- value={yearOptions.find(
- (year) => year.value === currentMonth.getFullYear()
- )}
- />
- </div>
- <ButtonIcon
- className="button-small"
- aria-label={this.getNextMonthAriaLabel()}
- onClick={this.handleNextMonthClick}
- >
- <ChevronRightIcon />
- </ButtonIcon>
- </fieldset>
+ <div className={classNames('date-input-calendar', { 'align-right': alignRight })}>
<DayPicker
mode="default"
- disableNavigation={true}
- components={{ CaptionLabel: () => null }}
- disabled={{ after, before: minDate }}
+ captionLayout="dropdown-buttons"
+ fromYear={fromYear}
+ toYear={toYear}
+ disabled={{ after: maxDate, before: minDate }}
weekStartsOn={1}
formatters={{
formatWeekdayName: (date) => getShortWeekDayName(date.getDay()),
modifiers={{ highlighted }}
modifiersClassNames={{ highlighted: 'highlighted' }}
month={currentMonth}
+ onMonthChange={(currentMonth) => this.setState({ currentMonth })}
selected={selectedDay}
onDayClick={this.handleDayClick}
onDayMouseEnter={this.handleDayMouseEnter}
/>
- </form>
+ </div>
)}
</span>
</EscKeydownHandler>
}
type InputWrapperProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'> &
- WrappedComponentProps & {
- innerRef: React.Ref<HTMLInputElement>;
- value: Date | undefined;
- };
+ WrappedComponentProps & { innerRef: React.Ref<HTMLInputElement>; value: Date | undefined };
const InputWrapper = injectIntl(({ innerRef, intl, value, ...other }: InputWrapperProps) => {
const formattedValue =
if (from && !this.to && this.toDateInput) {
this.toDateInput.focus();
}
- }, 0);
+ });
};
handleToChange = (to: Date | undefined) => {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { addDays, setMonth, setYear, subDays, subMonths } from 'date-fns';
+import { addDays, subDays } from 'date-fns';
import { shallow } from 'enzyme';
import * as React from 'react';
import { DayPicker } from 'react-day-picker';
const dateB = parseDate('2018-02-05T00:00:00.000Z');
it('should render', () => {
- // pass `maxDate` and `minDate` to avoid differences in snapshots
const { wrapper } = shallowRender();
expect(wrapper).toMatchSnapshot();
expect(wrapper).toMatchSnapshot();
});
-it('should change current month', () => {
- const { wrapper, instance } = shallowRender();
- expect(wrapper.state().currentMonth).toEqual(dateA);
-
- instance.handlePreviousMonthClick();
- expect(wrapper.state().currentMonth).toEqual(subMonths(dateA, 1));
-
- instance.handleNextMonthClick();
- expect(wrapper.state().currentMonth).toEqual(dateA);
-
- instance.handleCurrentMonthChange({ value: 5 });
- expect(wrapper.state().currentMonth).toEqual(setMonth(dateA, 5));
-
- instance.handleCurrentYearChange({ value: 2015 });
- expect(wrapper.state().currentMonth).toEqual(setYear(setMonth(dateA, 5), 2015));
-});
-
it('should select a day', () => {
const onChange = jest.fn();
const { wrapper, instance } = shallowRender({ onChange });
expect(dayPicker.props().modifiers).toEqual({ highlighted: { from: dateC, to: dateB } });
});
-it('should announce the proper month and year for next/previous buttons aria label', () => {
- const { wrapper, instance } = shallowRender();
- expect(wrapper.state().currentMonth).toEqual(dateA);
- expect(instance.getPreviousMonthAriaLabel()).toEqual('show_month_x_of_year_y.December.2017');
- expect(instance.getNextMonthAriaLabel()).toEqual('show_month_x_of_year_y.February.2018');
-
- instance.handleCurrentMonthChange({ value: 11 });
- expect(instance.getPreviousMonthAriaLabel()).toEqual('show_month_x_of_year_y.November.2018');
- expect(instance.getNextMonthAriaLabel()).toEqual('show_month_x_of_year_y.January.2019');
-});
-
function shallowRender(props?: Partial<DateInput['props']>) {
const wrapper = shallow<DateInput>(
<DateInput
currentMonth={dateA}
+ // pass `maxDate` and `minDate` to avoid differences in snapshots
maxDate={dateB}
minDate={dateA}
onChange={jest.fn()}
}
onClick={[Function]}
/>
- <form
+ <div
className="date-input-calendar"
>
- <fieldset
- aria-label="date.select_month_and_year_x.January, 2018"
- className="date-input-calendar-nav"
- >
- <ButtonIcon
- aria-label="show_month_x_of_year_y.December.2017"
- className="button-small"
- onClick={[Function]}
- >
- <ChevronLeftIcon />
- </ButtonIcon>
- <div
- className="date-input-calender-month"
- >
- <Select
- aria-label="select_month"
- className="date-input-calender-month-select"
- onChange={[Function]}
- options={
- [
- {
- "label": "Jan",
- "value": 0,
- },
- {
- "label": "Feb",
- "value": 1,
- },
- {
- "label": "Mar",
- "value": 2,
- },
- {
- "label": "Apr",
- "value": 3,
- },
- {
- "label": "May",
- "value": 4,
- },
- {
- "label": "Jun",
- "value": 5,
- },
- {
- "label": "Jul",
- "value": 6,
- },
- {
- "label": "Aug",
- "value": 7,
- },
- {
- "label": "Sep",
- "value": 8,
- },
- {
- "label": "Oct",
- "value": 9,
- },
- {
- "label": "Nov",
- "value": 10,
- },
- {
- "label": "Dec",
- "value": 11,
- },
- ]
- }
- value={
- {
- "label": "Jan",
- "value": 0,
- }
- }
- />
- <Select
- aria-label="select_year"
- className="date-input-calender-month-select spacer-left"
- onChange={[Function]}
- options={
- [
- {
- "label": "2008",
- "value": 2008,
- },
- {
- "label": "2009",
- "value": 2009,
- },
- {
- "label": "2010",
- "value": 2010,
- },
- {
- "label": "2011",
- "value": 2011,
- },
- {
- "label": "2012",
- "value": 2012,
- },
- {
- "label": "2013",
- "value": 2013,
- },
- {
- "label": "2014",
- "value": 2014,
- },
- {
- "label": "2015",
- "value": 2015,
- },
- {
- "label": "2016",
- "value": 2016,
- },
- {
- "label": "2017",
- "value": 2017,
- },
- {
- "label": "2018",
- "value": 2018,
- },
- ]
- }
- value={
- {
- "label": "2018",
- "value": 2018,
- }
- }
- />
- </div>
- <ButtonIcon
- aria-label="show_month_x_of_year_y.February.2018"
- className="button-small"
- onClick={[Function]}
- >
- <ChevronRightIcon />
- </ButtonIcon>
- </fieldset>
<DayPicker
- components={
- {
- "CaptionLabel": [Function],
- }
- }
- disableNavigation={true}
+ captionLayout="dropdown-buttons"
disabled={
{
"after": 2018-02-05T00:00:00.000Z,
"formatWeekdayName": [Function],
}
}
+ fromYear={2018}
mode="default"
modifiers={
{
month={2018-01-17T00:00:00.000Z}
onDayClick={[Function]}
onDayMouseEnter={[Function]}
+ onMonthChange={[Function]}
selected={2018-01-17T00:00:00.000Z}
+ toYear={2018}
weekStartsOn={1}
/>
- </form>
+ </div>
</span>
</EscKeydownHandler>
</OutsideClickHandler>
postcss-custom-properties: 12.1.11
prettier: 2.8.3
react: 16.14.0
- react-day-picker: 8.5.1
+ react-day-picker: 8.6.0
react-dom: 16.14.0
react-draggable: 4.4.5
react-helmet-async: 1.3.0
languageName: node
linkType: hard
-"react-day-picker@npm:8.5.1":
- version: 8.5.1
- resolution: "react-day-picker@npm:8.5.1"
+"react-day-picker@npm:8.6.0":
+ version: 8.6.0
+ resolution: "react-day-picker@npm:8.6.0"
peerDependencies:
date-fns: ^2.28.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
- checksum: 0d444710bccf07db869673f19975e68962b44a1de8a176710e2a0297e0c7d98e610b7d7e5b24a19dc695dac7e3745191cfef3f656d87f6fbafc1ecc8833a212c
+ checksum: f5bc2f9b093ddffc5e504b48c52ca6fccd9180bd63d77641312b0df80cdf5285d440b191d6379262798ab40926936144ca216f1c2a1fb78b998105f9e49c6379
languageName: node
linkType: hard
Th=Th
Fr=Fr
Sa=Sa
-select_month=Select a month
-select_year=Select a year
-show_month_x_of_year_y=Show {0} of {1}
-date.select_month_and_year_x=Select the month and year, currently {0}
#------------------------------------------------------------------------------
#