From: 7PH Date: Tue, 21 Feb 2023 08:29:35 +0000 (+0100) Subject: SONAR-18399 Use react day picker native navigation instead of our custom one X-Git-Tag: 10.0.0.68432~212 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1f43862c86e1680ad6185323c819b07bd559b5d8;p=sonarqube.git SONAR-18399 Use react day picker native navigation instead of our custom one --- diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index abcc49ffb97..3a191318c2f 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -23,7 +23,7 @@ "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", diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx index d5a391828aa..9474685d525 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx @@ -20,11 +20,10 @@ 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'; @@ -67,8 +66,8 @@ const ui = { 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; @@ -117,13 +116,13 @@ it('should handle download button click', async () => { 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'); diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.css b/server/sonar-web/src/main/js/components/controls/DateInput.css index 82655a31f12..6f757b8557a 100644 --- a/server/sonar-web/src/main/js/components/controls/DateInput.css +++ b/server/sonar-web/src/main/js/components/controls/DateInput.css @@ -20,7 +20,14 @@ .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 { @@ -79,21 +86,3 @@ 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; -} diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.tsx b/server/sonar-web/src/main/js/components/controls/DateInput.tsx index d6c20cdea49..e3b2dfe5c50 100644 --- a/server/sonar-web/src/main/js/components/controls/DateInput.tsx +++ b/server/sonar-web/src/main/js/components/controls/DateInput.tsx @@ -18,28 +18,20 @@ * 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; @@ -63,14 +55,12 @@ interface State { lastHovered?: Date; } -const MONTHS_IN_YEAR = 12; -const YEARS_TO_DISPLAY = 10; - export default class DateInput extends React.PureComponent { input?: HTMLInputElement | null; constructor(props: Props) { super(props); + this.state = { currentMonth: props.value || props.currentMonth || new Date(), open: false }; } @@ -109,51 +99,13 @@ export default class DateInput extends React.PureComponent { 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, @@ -163,14 +115,9 @@ export default class DateInput extends React.PureComponent { } = 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; @@ -209,54 +156,13 @@ export default class DateInput extends React.PureComponent { /> )} {open && ( -
-
- - - -
- year.value === currentMonth.getFullYear() - )} - /> -
- - - -
+
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()), @@ -264,11 +170,12 @@ export default class DateInput extends React.PureComponent { modifiers={{ highlighted }} modifiersClassNames={{ highlighted: 'highlighted' }} month={currentMonth} + onMonthChange={(currentMonth) => this.setState({ currentMonth })} selected={selectedDay} onDayClick={this.handleDayClick} onDayMouseEnter={this.handleDayMouseEnter} /> - +
)} @@ -279,10 +186,7 @@ export default class DateInput extends React.PureComponent { } type InputWrapperProps = Omit, 'value'> & - WrappedComponentProps & { - innerRef: React.Ref; - value: Date | undefined; - }; + WrappedComponentProps & { innerRef: React.Ref; value: Date | undefined }; const InputWrapper = injectIntl(({ innerRef, intl, value, ...other }: InputWrapperProps) => { const formattedValue = diff --git a/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx b/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx index a85dbdbb56d..87a6e0dfdc5 100644 --- a/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx +++ b/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx @@ -53,7 +53,7 @@ export default class DateRangeInput extends React.PureComponent { if (from && !this.to && this.toDateInput) { this.toDateInput.focus(); } - }, 0); + }); }; handleToChange = (to: Date | undefined) => { diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx index a904d7a3ad6..5294c660a2f 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx @@ -17,7 +17,7 @@ * 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'; @@ -32,7 +32,6 @@ const dateA = parseDate('2018-01-17T00:00:00.000Z'); 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(); @@ -44,23 +43,6 @@ it('should render', () => { 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 }); @@ -102,21 +84,11 @@ it('should hightlightTo range', () => { 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) { const wrapper = shallow( -
-
- - - -
- -
- - - -
- + diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index 6b354d93316..63b6960850a 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -2901,7 +2901,7 @@ __metadata: 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 @@ -8444,13 +8444,13 @@ __metadata: 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 diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 724098d8599..3242ee0e933 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -367,10 +367,6 @@ We=We 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} #------------------------------------------------------------------------------ #