Skip to content

Commit

Permalink
⬆️ deps: Upgrade @mui/x-date-pickers to v6.
Browse files Browse the repository at this point in the history
Fixes #752.
Had to upgrade react monorepo.
Had to apply workaround found at
testing-library/react-testing-library#1248 (comment).
  • Loading branch information
renovate[bot] authored and make-github-pseudonymous-again committed Mar 5, 2024
1 parent e2a2bab commit 0c41f9d
Show file tree
Hide file tree
Showing 14 changed files with 592 additions and 385 deletions.
39 changes: 21 additions & 18 deletions imports/api/publication/useTracker.ts
Expand Up @@ -51,24 +51,27 @@ const useTrackerClientImpl = <T = any>(
): T => {
const [iteration, forceUpdate] = useUniqueObject();

const data = useMemo(() => {
let data: T = shouldNeverBeReturned as T;

// Use Tracker.nonreactive in case we are inside a Tracker Computation.
// This can happen if someone calls `ReactDOM.render` inside a Computation.
// In that case, we want to opt out of the normal behavior of nested
// Computations, where if the outer one is invalidated or stopped,
// it stops the inner one.

Tracker.nonreactive(() =>
Tracker.autorun((c: Tracker.Computation) => {
assert(c.firstRun);
data = reactiveFn(c);
}),
).stop();

return data;
}, deps && [iteration, ...deps]);
const data = useMemo(
() => {
let data: T = shouldNeverBeReturned as T;

// Use Tracker.nonreactive in case we are inside a Tracker Computation.
// This can happen if someone calls `ReactDOM.render` inside a Computation.
// In that case, we want to opt out of the normal behavior of nested
// Computations, where if the outer one is invalidated or stopped,
// it stops the inner one.

Tracker.nonreactive(() =>
Tracker.autorun((c: Tracker.Computation) => {
assert(c.firstRun);
data = reactiveFn(c);
}),
).stop();

return data;

Check warning on line 71 in imports/api/publication/useTracker.ts

View check run for this annotation

Codecov / codecov/patch

imports/api/publication/useTracker.ts#L70-L71

Added lines #L70 - L71 were not covered by tests
},
deps === undefined ? [] : [iteration, ...deps],
);

useEffect(() => {
let prevData = data;
Expand Down
167 changes: 126 additions & 41 deletions imports/i18n/datetime.ts
@@ -1,5 +1,7 @@
import {useState, useMemo, useEffect} from 'react';

import {type PickersLocaleText} from '@mui/x-date-pickers';

import dateFormat from 'date-fns/format';
import dateFormatDistance from 'date-fns/formatDistance';
import dateFormatDistanceStrict from 'date-fns/formatDistanceStrict';
Expand Down Expand Up @@ -28,76 +30,169 @@ const localeLoaders: Readonly<Record<string, () => Promise<Locale>>> = {
'fr-BE': async () => import('date-fns/locale/fr/index.js') as Promise<Locale>,
};

const loadLocale = async (key: string): Promise<Locale | undefined> =>
localeLoaders[key]?.();
type LocaleText = Partial<PickersLocaleText<any>>;

export const dateMaskMap = {
'en-US': '__/__/____',
'nl-BE': '__.__.____',
'fr-BE': '__/__/____',
type PickersLocalization = {
components: {
MuiLocalizationProvider: {
defaultProps: {
localeText: LocaleText;
};
};
};

Check warning on line 42 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L40-L42

Added lines #L40 - L42 were not covered by tests
};

export const dateTimeMaskMap = {
'en-US': `${dateMaskMap['en-US']} __:__ _M`,
'nl-BE': `${dateMaskMap['nl-BE']} __:__`,
'fr-BE': `${dateMaskMap['fr-BE']} __:__`,
const pickersLocalizationLoaders: Readonly<
Record<string, () => Promise<PickersLocalization>>
> = {
async 'nl-BE'() {
const module = await import('@mui/x-date-pickers/locales/nlNL.js');
return module.nlNL;
},
async 'fr-BE'() {
const module = await import('@mui/x-date-pickers/locales/frFR.js');

Check warning on line 53 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L53

Added line #L53 was not covered by tests
return module.frFR;
},
};

const loadLocale = async (key: string): Promise<Locale | undefined> =>
localeLoaders[key]?.();

const loadPickersLocalization = async (
key: string,

Check warning on line 62 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L60-L62

Added lines #L60 - L62 were not covered by tests
): Promise<PickersLocalization | undefined> =>
pickersLocalizationLoaders[key]?.();

type Cache<T> = Map<string, T>;

const localesCache = new Map<string, Locale | undefined>();

const getLocale = async (owner: string): Promise<Locale | undefined> => {
const key = getSetting(owner, 'lang');
if (localesCache.has(key)) {
return localesCache.get(key);
const pickersLocalizationsCache = new Map<
string,
PickersLocalization | undefined
>();

const _load = async <T>(
kind: string,
cache: Cache<T>,
load: (key: string) => Promise<T>,
key: string,
): Promise<T | undefined> => {

Check warning on line 80 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L79-L80

Added lines #L79 - L80 were not covered by tests
if (cache.has(key)) {
return cache.get(key);
}

return loadLocale(key).then(
(loadedLocale) => {
localesCache.set(key, loadedLocale);
return loadedLocale;
return load(key).then(
(value) => {
cache.set(key, value);
return value;
},
(error) => {
const message = error instanceof Error ? error.message : 'unknown error';
console.error(`failed to load locale ${key}: ${message}`);
console.error(`failed to load ${kind} ${key}: ${message}`);
console.debug({error});
return undefined;
},
);
};

export const useLocale = () => {
const key = useLocaleKey();
const [lastLoadedLocale, setLastLoadedLocale] = useState<Locale | undefined>(
const _getLocale = async (key: string): Promise<Locale | undefined> => {
return _load<Locale | undefined>('locale', localesCache, loadLocale, key);
};

const getLocale = async (owner: string): Promise<Locale | undefined> => {
const key = getSetting(owner, 'lang');
return _getLocale(key);
};

const _getPickersLocalization = async (
key: string,
): Promise<PickersLocalization | undefined> => {
return _load<PickersLocalization | undefined>(
'pickers localization',
pickersLocalizationsCache,
loadPickersLocalization,
key,
);
};

const _pickersLocalizationToLocaleText = (
localization: PickersLocalization | undefined,
) => {
return localization?.components.MuiLocalizationProvider.defaultProps
.localeText;

Check warning on line 123 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L123

Added line #L123 was not covered by tests
};

export const getLocaleText = async (
owner: string,
): Promise<LocaleText | undefined> => {

Check warning on line 128 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L128

Added line #L128 was not covered by tests
const key = getSetting(owner, 'lang');
const localization = await _getPickersLocalization(key);
return _pickersLocalizationToLocaleText(localization);
};

Check warning on line 132 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L130-L132

Added lines #L130 - L132 were not covered by tests

const useLoadedValue = <T>(
kind: string,
cache: Cache<T>,
load: (key: string) => Promise<T>,
key: string,
): T | undefined => {
const [lastLoadedValue, setLastLoadedValue] = useState<T | undefined>(
undefined,
);

useEffect(() => {
if (localesCache.has(key)) {
setLastLoadedLocale(localesCache.get(key));
if (cache.has(key)) {
setLastLoadedValue(cache.get(key));
return undefined;
}

let isCancelled = false;
loadLocale(key).then(
(loadedLocale) => {
localesCache.set(key, loadedLocale);
load(key).then(
(value) => {
cache.set(key, value);
if (!isCancelled) {
setLastLoadedLocale(loadedLocale);
setLastLoadedValue(value);
}
},
(error) => {
const message =
error instanceof Error ? error.message : 'unknown error';
console.error(`failed to load locale ${key}: ${message}`);
console.error(`failed to load ${kind} ${key}: ${message}`);
console.debug({error});
},
);
return () => {
isCancelled = true;
};
}, [key, setLastLoadedLocale]);
}, [key, setLastLoadedValue]);

return cache.has(key) ? cache.get(key) : lastLoadedValue;
};

export const useLocale = () => {
const key = useLocaleKey();
return useLoadedValue<Locale | undefined>(
'locale',
localesCache,
loadLocale,
key,
);
};

const usePickersLocalization = (key: string) => {
return useLoadedValue<PickersLocalization | undefined>(
'pickers localization',
pickersLocalizationsCache,
loadPickersLocalization,
key,
);
};

return localesCache.has(key) ? localesCache.get(key) : lastLoadedLocale;
export const useLocaleText = () => {
const key = useLocaleKey();
const localization = usePickersLocalization(key);
return _pickersLocalizationToLocaleText(localization);

Check warning on line 195 in imports/i18n/datetime.ts

View check run for this annotation

Codecov / codecov/patch

imports/i18n/datetime.ts#L195

Added line #L195 was not covered by tests
};

export type WeekStartsOn = WeekDay;
Expand Down Expand Up @@ -162,16 +257,6 @@ export const useDefaultDateFormatOptions = () => {
);
};

export const useDateMask = () => {
const key = useLocaleKey();
return dateMaskMap[key];
};

export const useDateTimeMask = () => {
const key = useLocaleKey();
return dateTimeMaskMap[key];
};

const stringifyOptions = (options) => {
if (options === undefined) return undefined;

Expand Down
11 changes: 3 additions & 8 deletions imports/ui/App.tsx
Expand Up @@ -10,11 +10,7 @@ import {SnackbarProvider} from 'notistack';

import CssBaseline from '@mui/material/CssBaseline';

import {LocalizationProvider} from '@mui/x-date-pickers/LocalizationProvider';
import {AdapterDateFns} from '@mui/x-date-pickers/AdapterDateFns';

import {useLocale} from '../i18n/datetime';

import DateTimeLocalizationProvider from './i18n/DateTimeLocalizationProvider';
import CustomWholeWindowDropZone from './input/CustomWholeWindowDropZone';
import ModalProvider from './modal/ModelProvider';
import ErrorBoundary from './ErrorBoundary';
Expand All @@ -29,12 +25,11 @@ export const muiCache = createCache({
});

const App = () => {
const locale = useLocale();
const theme = useUserTheme();

return (
<BrowserRouter>
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={locale}>
<DateTimeLocalizationProvider>
<CacheProvider value={muiCache}>
<ThemeProvider theme={theme}>
<ModalProvider>
Expand All @@ -51,7 +46,7 @@ const App = () => {
</ModalProvider>
</ThemeProvider>
</CacheProvider>
</LocalizationProvider>
</DateTimeLocalizationProvider>
</BrowserRouter>
);
};
Expand Down
39 changes: 15 additions & 24 deletions imports/ui/appointments/AppointmentDialog.tsx
Expand Up @@ -31,8 +31,6 @@ import TextField from '../input/TextField';

import CancelButton from '../button/CancelButton';

import {useDateMask} from '../../i18n/datetime';

import {msToString} from '../../api/duration';

import {type AppointmentDocument} from '../../api/collection/appointments';
Expand Down Expand Up @@ -153,8 +151,6 @@ const AppointmentDialog = ({
]);
const patientIsReadOnly = Boolean(initialPatient);

const localizedDateMask = useDateMask();

const datetime = unserializeDatetime(date, time);
const appointmentIsInThePast = isBefore(
unserializeDate(date),
Expand Down Expand Up @@ -257,21 +253,17 @@ const AppointmentDialog = ({
<Grid container spacing={3}>
<Grid item xs={4}>
<DatePicker
mask={localizedDateMask}
value={unserializeDate(date)}
label="Date"
renderInput={(props) => (
<TextField
{...props}
InputLabelProps={{shrink: true}}
error={!validDate || displayAppointmentIsInThePast}
helperText={
displayAppointmentIsInThePast
? 'Date dans le passé!'
: undefined
}
/>
)}
slotProps={{
textField: {
InputLabelProps: {shrink: true},
error: !validDate || displayAppointmentIsInThePast,
helperText: displayAppointmentIsInThePast
? 'Date dans le passé!'
: undefined,
},
}}
onChange={(pickedDatetime) => {
if (isValid(pickedDatetime)) {
setDate(serializeDate(pickedDatetime!));
Expand All @@ -284,13 +276,12 @@ const AppointmentDialog = ({
</Grid>
<Grid item xs={4}>
<TimePicker
renderInput={(props) => (
<TextField
{...props}
InputLabelProps={{shrink: true}}
error={!validTime}
/>
)}
slotProps={{
textField: {
InputLabelProps: {shrink: true},
error: !validTime,
},
}}
label="Time"
value={time === '' ? null : unserializeTime(time)}
onChange={(pickedDatetime) => {
Expand Down
7 changes: 2 additions & 5 deletions imports/ui/birthdate/useBirthdatePickerProps.ts
Expand Up @@ -5,17 +5,14 @@ import endOfToday from 'date-fns/endOfToday';
import parseISO from 'date-fns/parseISO';
import subYears from 'date-fns/subYears';

import {useDateMask} from '../../i18n/datetime';

const useBirthdatePickerProps = () => {
const mask = useDateMask();
const maxDateString = format(endOfToday(), 'yyyy-MM-dd');

return useMemo(() => {
const maxDate = parseISO(maxDateString);
const minDate = subYears(maxDate, 200);
return {minDate, maxDate, mask};
}, [maxDateString, mask]);
return {minDate, maxDate};
}, [maxDateString]);
};

export default useBirthdatePickerProps;

0 comments on commit 0c41f9d

Please sign in to comment.