]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18399 Use react day picker native navigation instead of our custom one
author7PH <benjamin.raymond@sonarsource.com>
Tue, 21 Feb 2023 08:29:35 +0000 (09:29 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 21 Feb 2023 20:03:00 +0000 (20:03 +0000)
server/sonar-web/package.json
server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx
server/sonar-web/src/main/js/components/controls/DateInput.css
server/sonar-web/src/main/js/components/controls/DateInput.tsx
server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx
server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap
server/sonar-web/yarn.lock
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index abcc49ffb9788aff3e7d959b48639f77bbf721b9..3a191318c2fc8865c650db667dd41c9fd6a3261c 100644 (file)
@@ -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",
index d5a391828aa31998856cba9e095f7cb5c00f073c..9474685d525978d1899783371eaac9956f3e42aa 100644 (file)
 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');
index 82655a31f1209696bb9c90702d3528885c19d3be..6f757b8557a0300a5222fccb0a5a0a9c3cd2baa8 100644 (file)
 
 .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;
-}
index d6c20cdea4921e210c9084fc1fe0ac48537548ed..e3b2dfe5c5029bcb366514dd610e590c08aaa35e 100644 (file)
  * 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<Props, State> {
   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<Props, State> {
     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<Props, State> {
     } = 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<Props, State> {
                 />
               )}
               {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()),
@@ -264,11 +170,12 @@ export default class DateInput extends React.PureComponent<Props, State> {
                     modifiers={{ highlighted }}
                     modifiersClassNames={{ highlighted: 'highlighted' }}
                     month={currentMonth}
+                    onMonthChange={(currentMonth) => this.setState({ currentMonth })}
                     selected={selectedDay}
                     onDayClick={this.handleDayClick}
                     onDayMouseEnter={this.handleDayMouseEnter}
                   />
-                </form>
+                </div>
               )}
             </span>
           </EscKeydownHandler>
@@ -279,10 +186,7 @@ export default class DateInput extends React.PureComponent<Props, State> {
 }
 
 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 =
index a85dbdbb56d076587b3eeb2c68c712951d6edd6f..87a6e0dfdc572aace49d47022c5801df73631f64 100644 (file)
@@ -53,7 +53,7 @@ export default class DateRangeInput extends React.PureComponent<Props> {
       if (from && !this.to && this.toDateInput) {
         this.toDateInput.focus();
       }
-    }, 0);
+    });
   };
 
   handleToChange = (to: Date | undefined) => {
index a904d7a3ad6c8e215ee8d8efd67222fcf9e75722..5294c660a2fbf55e1648a326c3f2358384a07969 100644 (file)
@@ -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<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()}
index 647cd74437b56ffd8eddcbe4b39f6a9c1f8e1313..655dd56b03a0bcb3767045b3a7e05b3f8cd33e21 100644 (file)
@@ -109,161 +109,11 @@ exports[`should render 3`] = `
           }
           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,
@@ -275,6 +125,7 @@ exports[`should render 3`] = `
                 "formatWeekdayName": [Function],
               }
             }
+            fromYear={2018}
             mode="default"
             modifiers={
               {
@@ -289,10 +140,12 @@ exports[`should render 3`] = `
             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>
index 6b354d93316f6046bc0f64e000f4ec98d7d6b421..63b6960850a9a8b9a3fe9dfec8a751f1bf61ba25 100644 (file)
@@ -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
 
index 724098d8599ff1854b40dfe7fd0471bd5a2cf371..3242ee0e933b63280f5aa5e229c9461fccfaa761 100644 (file)
@@ -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}
 
 #------------------------------------------------------------------------------
 #