Skip to content

Commit

Permalink
[pickers] Keep the calendar header and the calendar content in sync w…
Browse files Browse the repository at this point in the history
…hen switching locale
  • Loading branch information
flaviendelangle committed Aug 6, 2024
1 parent 734d8c0 commit 8497614
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ export class AdapterDateFnsBase<DateFnsLocale extends DateFnsLocaleBase>
return /a/.test(this.locale.formatLong!.time({ width: 'short' }));
};

// There is no notion of locale on the date in Date Fns.
public setLocaleToValue = (value: Date) => {
return value;
};

public expandFormat = (format: string) => {
const longFormatRegexp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;

Expand Down
18 changes: 9 additions & 9 deletions packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,6 @@ export class AdapterDayjs implements MuiPickersAdapter<Dayjs, string> {
defaultDayjs.extend(customParseFormatPlugin);
}

private setLocaleToValue = (value: Dayjs) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale()) {
return value;
}

return value.locale(expectedLocale);
};

private hasUTCPlugin = () => typeof defaultDayjs.utc !== 'undefined';

private hasTimezonePlugin = () => typeof defaultDayjs.tz !== 'undefined';
Expand Down Expand Up @@ -389,6 +380,15 @@ export class AdapterDayjs implements MuiPickersAdapter<Dayjs, string> {
return /A|a/.test(this.getLocaleFormats().LT || '');
};

public setLocaleToValue = (value: Dayjs) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale()) {
return value;
}

return value.locale(expectedLocale);
};

public expandFormat = (format: string) => {
const localeFormats = this.getLocaleFormats();

Expand Down
18 changes: 9 additions & 9 deletions packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,6 @@ export class AdapterLuxon implements MuiPickersAdapter<DateTime, string> {
this.formats = { ...defaultFormats, ...formats };
}

private setLocaleToValue = (value: DateTime) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale) {
return value;
}

return value.setLocale(expectedLocale);
};

public date = <T extends string | null | undefined>(
value?: T,
timezone: PickersTimezone = 'default',
Expand Down Expand Up @@ -210,6 +201,15 @@ export class AdapterLuxon implements MuiPickersAdapter<DateTime, string> {
);
};

public setLocaleToValue = (value: DateTime) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale) {
return value;
}

return value.setLocale(expectedLocale);
};

public expandFormat = (format: string) => {
// Extract escaped section to avoid extending them
const catchEscapedSectionsRegexp = /''|'(''|[^'])+('|$)|[^']*/g;
Expand Down
18 changes: 9 additions & 9 deletions packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,6 @@ export class AdapterMoment implements MuiPickersAdapter<Moment, string> {
this.formats = { ...defaultFormats, ...formats };
}

private setLocaleToValue = (value: Moment) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale()) {
return value;
}

return value.locale(expectedLocale);
};

private hasTimezonePlugin = () => typeof this.moment.tz !== 'undefined';

private createSystemDate = (value: string | undefined): Moment => {
Expand Down Expand Up @@ -284,6 +275,15 @@ export class AdapterMoment implements MuiPickersAdapter<Moment, string> {
return /A|a/.test(defaultMoment.localeData(this.getCurrentLocaleCode()).longDateFormat('LT'));
};

public setLocaleToValue = (value: Moment) => {
const expectedLocale = this.getCurrentLocaleCode();
if (expectedLocale === value.locale()) {
return value;
}

return value.locale(expectedLocale);
};

public expandFormat = (format: string) => {
// @see https://github.com/moment/moment/blob/develop/src/lib/format/format.js#L6
const localFormattingTokens = /(\[[^[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})|./g;
Expand Down
13 changes: 0 additions & 13 deletions packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export function DayCalendar<TDate extends PickerValidDate>(inProps: DayCalendarP
key={i.toString()}
variant="caption"
role="columnheader"
aria-label={utils.format(utils.addDays(startOfCurrentWeek, i), 'weekday')}
aria-label={utils.format(weekday, 'weekday')}
className={classes.weekDayLabel}
>
{dayOfWeekFormatter(weekday)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import moment, { Moment } from 'moment';
import { DateCalendar } from '@mui/x-date-pickers/DateCalendar';

<DateCalendar />;
Expand All @@ -8,3 +9,13 @@ import { DateCalendar } from '@mui/x-date-pickers/DateCalendar';
<DateCalendar value={new Date()} />;

<DateCalendar value={null} />;

// External components are generic as well
<DateCalendar<Moment>
view="day"
views={['day']}
value={moment()}
minDate={moment()}
maxDate={moment()}
onChange={(date) => date?.format()}
/>;
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as React from 'react';
import { expect } from 'chai';
import { screen } from '@mui/internal-test-utils';
import { DateCalendar } from '@mui/x-date-pickers/DateCalendar';
import { createPickerRenderer, AdapterName } from 'test/utils/pickers';
import { he } from 'date-fns/locale';
import { screen, createRenderer } from '@mui/internal-test-utils';
import { DateCalendar, dayCalendarClasses } from '@mui/x-date-pickers/DateCalendar';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { createPickerRenderer, AdapterName, availableAdapters } from 'test/utils/pickers';
import { he, fr } from 'date-fns/locale';
import 'dayjs/locale/he';
import 'moment/locale/he';
import 'dayjs/locale/fr';
import 'moment/locale/fr';

const ADAPTERS_TO_USE: AdapterName[] = ['date-fns', 'dayjs', 'luxon', 'moment'];

Expand All @@ -17,11 +20,33 @@ describe('<DateCalendar /> - localization', () => {
adapterName,
});

const { render: renderWithoutWrapper } = createRenderer();

it('should display correct week day labels in Hebrew locale ', () => {
render(<DateCalendar />);

expect(screen.getByText('א')).toBeVisible();
});

it('should correctly switch between locale with week starting in Monday and week starting in Sunday', () => {
const { setProps } = renderWithoutWrapper(
<LocalizationProvider dateAdapter={availableAdapters[adapterName]}>
<DateCalendar />
</LocalizationProvider>,
);

expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal(
'Sunday',
);

setProps({
adapterLocale: adapterName === 'date-fns' ? fr : 'fr',
});

expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal(
'lundi',
);
});
});
});
});
3 changes: 2 additions & 1 deletion packages/x-date-pickers/src/internals/utils/date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export const getWeekdays = <TDate extends PickerValidDate>(
utils: MuiPickersAdapter<TDate>,
date: TDate,
) => {
const start = utils.startOfWeek(date);
const cleanDate = utils.setLocaleToValue(date);
const start = utils.startOfWeek(cleanDate);
return [0, 1, 2, 3, 4, 5, 6].map((diff) => utils.addDays(start, diff));
};
6 changes: 6 additions & 0 deletions packages/x-date-pickers/src/models/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ export interface MuiPickersAdapter<TDate extends PickerValidDate, TLocale = any>
* @returns {boolean} `true` if the current locale is using 12 hours cycle.
*/
is12HourCycleInCurrentLocale(): boolean;
/**
* Set the locale of the adapter to the given value.
* @param {TDate} value The value to set the locale to.
* @returns {TDate} The value with the locale set.
*/
setLocaleToValue(value: TDate): TDate;
/**
* Create a format with no meta-token (e.g: `LLL` or `PP`).
* @param {string} format The format to expand.
Expand Down
14 changes: 13 additions & 1 deletion test/utils/pickers/describeGregorianAdapter/testLocalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ import { cleanText } from 'test/utils/pickers';
import { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types';
import { TEST_DATE_ISO_STRING } from './describeGregorianAdapter.utils';

export const testLocalization: DescribeGregorianAdapterTestSuite = ({ adapter }) => {
export const testLocalization: DescribeGregorianAdapterTestSuite = ({
adapter,
adapterFr,
getLocaleFromDate,
}) => {
const testDateIso = adapter.date(TEST_DATE_ISO_STRING)!;

it('Method: formatNumber', () => {
expect(adapter.formatNumber('1')).to.equal('1');
});

it('Method: setLocaleToValue', () => {
if (getLocaleFromDate) {
const dateEn = adapter.date();
const dateFr = adapterFr.setLocaleToValue(dateEn);
expect(getLocaleFromDate(dateFr)).to.equal('fr');
}
});

it('Method: expandFormat', () => {
const testFormat = (formatKey: keyof AdapterFormats) => {
const formatString = adapter.formats[formatKey];
Expand Down

0 comments on commit 8497614

Please sign in to comment.