Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update mui #395

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16,493 changes: 9,403 additions & 7,090 deletions ui/package-lock.json

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
"@emotion/styled": "^11.13.5",
"@lingui/core": "^4.14.0",
"@lingui/react": "^4.14.0",
"@mui/icons-material": "^5.16.7",
"@mui/lab": "5.0.0-alpha.132",
"@mui/material": "5.14.11",
"@mui/x-date-pickers": "^5.0.20",
"@mui/icons-material": "^6.2.1",
"@mui/material": "^6.2.1",
"@mui/x-date-pickers": "^7.23.2",
"@reduxjs/toolkit": "^1.9.7",
"@types/node": "^20.16.13",
"@types/react": "^18.3.12",
Expand All @@ -31,7 +30,6 @@
"axios": "^1.7.8",
"formik": "^2.4.6",
"formik-mui": "5.0.0-alpha.0",
"formik-mui-lab": "^1.0.0",
"luxon": "^3.5.0",
"nanoid": "^5.0.9",
"query-string": "^9.1.1",
Expand All @@ -52,6 +50,7 @@
"yup": "^1.4.0"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/types": "^7.26.0",
"@lingui/cli": "^4.14.0",
"@lingui/macro": "^4.14.0",
Expand All @@ -71,7 +70,7 @@
"markdownlint": "^0.36.1",
"markdownlint-cli2": "^0.15.0",
"miragejs": "^0.1.48",
"prettier": "^2.8.8",
"prettier": "^3.4.2",
"react-scripts": "^5.0.1",
"source-map-explorer": "^2.5.3"
},
Expand All @@ -80,7 +79,7 @@
"start": "npm run redocly && npm run compile && react-scripts start",
"build": "npm run redocly && npm run compile -- strict && react-scripts build",
"build:nonprod": "npm run redocly && npm run compile -- strict && env-cmd -f ./.env.nonprod react-scripts build",
"test": "npm run compile && react-scripts test --reporters=default --reporters=jest-html-reporter",
"test": "npm run compile && react-scripts test",
"test-debug": "npm run compile && react-scripts --inspect-brk test --runInBand --no-cache",
"test-precommit": "react-scripts test --watchAll=false --env=jsdom --coverage --reporters=default --reporters=jest-html-reporter --silent",
"extract": "lingui extract",
Expand Down
15 changes: 10 additions & 5 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const AppRoutes = () => {
const { classes } = useStyles();
const dispatch: AppDispatch = useDispatch();
const currentUser = useSelector((state: RootState) =>
selectCurrentUser(state, "self")
selectCurrentUser(state, "self"),
);

// load current user in app and not in NavBar since it's global throughout the app
Expand Down Expand Up @@ -214,9 +214,9 @@ const ThemedApp = () => {
gradient: colors.gradient,
gradientText: colors.gradientText,
},
}
},
),
[prefersDarkMode, colors]
[prefersDarkMode, colors],
);

theme = React.useMemo(
Expand Down Expand Up @@ -279,7 +279,7 @@ const ThemedApp = () => {
},
},
}),
[theme, prefersDarkMode]
[theme, prefersDarkMode],
);

// TODO i18n: Date/Time pickers have changed the way they handle i18n
Expand All @@ -290,7 +290,12 @@ const ThemedApp = () => {
return (
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={DateAdapter}>
<Router>
<Router
future={{
v7_startTransition: true,
v7_relativeSplatPath: true,
}}
>
<AppRoutes />
</Router>
</LocalizationProvider>
Expand Down
186 changes: 113 additions & 73 deletions ui/src/components/FormikPickers.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import TextField from "@mui/material/TextField";
import { DateTime } from "luxon";
import { AdapterLuxon as DateAdapter } from "@mui/x-date-pickers/AdapterLuxon";
import { FieldProps, getIn } from "formik";
import {
DesktopDateTimePicker,
DateTimePickerProps,
DesktopDateTimePickerProps,
} from "@mui/x-date-pickers";
import { useLingui } from "@lingui/react";
import { t } from "@lingui/macro";
import { useState } from "react";
import { browserLanguage } from "App";
import { InputBaseProps } from "@mui/material";

// Formik wrapper for Material UI date/time pickers
// adapted from Material-UI picker Formik sample:
Expand All @@ -20,17 +22,18 @@ import { useState } from "react";

interface DatePickerFieldProps
extends FieldProps,
DateTimePickerProps<any, any> {
DesktopDateTimePickerProps<DateTime> {
id?: string;
getShouldDisableDateError: (date: Date) => string;
placeholder?: string;
getShouldDisableDateError: (date: DateTime | null) => string;
size?: "small" | "medium";
style?: React.CSSProperties;
// allow overriding default error messages
// carry-over from legacy KeyboardDateTimePicker
invalidDateMessage?: string;
minDateMessage?: string;
maxDateMessage?: string;
placeholder?: string;
helperText?: string;
}

// for picker format argument, see:
Expand All @@ -41,7 +44,6 @@ const DatePickerField = (props: DatePickerFieldProps) => {
field,
form,
getShouldDisableDateError,
renderInput,
onChange,
value,
invalidDateMessage,
Expand All @@ -52,18 +54,71 @@ const DatePickerField = (props: DatePickerFieldProps) => {
const [muiError, setMuiError] = useState<string | null>(null);
const formikError = getIn(form.errors, field.name);
const hasError = formikError ?? muiError;
const localeText = props.localeText ? { ...props.localeText } : {};
if (!localeText?.toolbarTitle) {
localeText.toolbarTitle = i18n._(t`Select date`);
}
if (!localeText?.cancelButtonLabel) {
localeText.cancelButtonLabel = i18n._(t`Cancel`);
}
if (!localeText?.clearButtonLabel) {
localeText.clearButtonLabel = i18n._(t`Clear`);
}
if (!localeText?.okButtonLabel) {
localeText.okButtonLabel = i18n._(t`OK`);
}
if (!localeText?.previousMonth) {
localeText.previousMonth = i18n._(t`Previous month`);
}
if (!localeText?.nextMonth) {
localeText.nextMonth = i18n._(t`Next month`);
}
// x-date-pickers v6 expects the picker's field value to be in the adapter's date/time format - it no longer performs this conversion
// so this wrapper will now handle this conversion
const adapter = new DateAdapter({ locale: browserLanguage });
const fieldValue = adapter.date(field.value);

// for a11y assign an additional title to the input field separate from the value, since v6 x-date-pickers picker value contains additional non-display characters
// const displayValue = fieldValue
// ? fieldValue.toFormat(other.format ?? "yyyy/LL/dd HH:mm")
// : "";
const displayValue = "";
const inputProps: InputBaseProps["inputProps"] = {
id: props.id,
title: displayValue,
};
if (props.placeholder) {
inputProps.placeholder = props.placeholder;
}

// explicitly use the _Desktop_ variant of DateTimePicker, i.e., DesktopDateTimePicker
// Using the generic DateTimePicker component causes the variant (Desktop or Mobile) to be automatically resolved based on media queries
// UI has not yet been tested on mobile and there are some significant differences to Desktop picker that cause unit tests to fail
return (
<DesktopDateTimePicker
componentsProps={{
slotProps={{
actionBar: {
actions: ["clear", "cancel", "accept"],
},
textField: {
name: field.name,
InputLabelProps: { htmlFor: props.id },
inputProps: inputProps,
style: props.style ?? undefined,
size: props.size ?? "medium",
error: Boolean(hasError),
helperText: hasError ?? props.helperText,
onBlur: () => {
form.setFieldTouched(field.name, true, false);
if (muiError) {
form.setFieldError(field.name, muiError);
} else {
form.validateField(field.name);
}
},
},
}}
value={field.value}
value={fieldValue}
label={props?.label}
onChange={(date) => {
form.setFieldTouched(field.name, true, false);
Expand All @@ -76,7 +131,7 @@ const DatePickerField = (props: DatePickerFieldProps) => {
}}
onError={(code, value) => {
// map error enum to a formik field error
let dt: any;
let dt: DateTime | undefined;
let format: string;
let error: string | null = null;

Expand Down Expand Up @@ -104,14 +159,18 @@ const DatePickerField = (props: DatePickerFieldProps) => {
error = maxDateMessage;
} else {
dt = props?.maxDate ?? props?.maxDateTime;
if (props?.inputFormat) {
format = dt.toFormat(props.inputFormat);
} else {
format = dt.toLocaleString(
props?.maxDate ? DateTime.DATE_SHORT : DateTime.DATETIME_SHORT
);
if (dt) {
if (props?.format) {
format = dt.toFormat(props.format);
} else {
format = dt.toLocaleString(
props?.maxDate
? DateTime.DATE_SHORT
: DateTime.DATETIME_SHORT,
);
}
error = i18n._(t`Date can not be after ${format}`);
}
error = i18n._(t`Date can not be after ${format}`);
}
break;
}
Expand All @@ -120,14 +179,18 @@ const DatePickerField = (props: DatePickerFieldProps) => {
error = minDateMessage;
} else {
dt = props?.minDate ?? props?.minDateTime;
if (props?.inputFormat) {
format = dt.toFormat(props.inputFormat);
} else {
format = dt.toLocaleString(
props?.minDate ? DateTime.DATE_SHORT : DateTime.DATETIME_SHORT
);
if (dt) {
if (props?.format) {
format = dt.toFormat(props.format);
} else {
format = dt.toLocaleString(
props?.minDate
? DateTime.DATE_SHORT
: DateTime.DATETIME_SHORT,
);
}
error = i18n._(t`Date can not be before ${format}`);
}
error = i18n._(t`Date can not be before ${format}`);
}
break;
}
Expand All @@ -136,18 +199,20 @@ const DatePickerField = (props: DatePickerFieldProps) => {
error = maxDateMessage;
} else {
dt = props?.maxTime ?? props?.maxDateTime;
if (props?.inputFormat) {
format = dt.toFormat(props.inputFormat);
} else {
format = dt.toLocaleString(
props?.maxTime
? DateTime.TIME_SIMPLE
: DateTime.DATETIME_SHORT
);
if (dt) {
if (props?.format) {
format = dt.toFormat(props.format);
} else {
format = dt.toLocaleString(
props?.maxTime
? DateTime.TIME_SIMPLE
: DateTime.DATETIME_SHORT,
);
}
error = props?.maxTime
? i18n._(t`Time can not be after ${format}`)
: i18n._(t`Date can not be after ${format}`);
}
error = props?.maxTime
? i18n._(t`Time can not be after ${format}`)
: i18n._(t`Date can not be after ${format}`);
}
break;
}
Expand All @@ -156,18 +221,20 @@ const DatePickerField = (props: DatePickerFieldProps) => {
error = minDateMessage;
} else {
dt = props?.minTime ?? props?.minDateTime;
if (props?.inputFormat) {
format = dt.toFormat(props.inputFormat);
} else {
format = dt.toLocaleString(
props?.minTime
? DateTime.TIME_SIMPLE
: DateTime.DATETIME_SHORT
);
if (dt) {
if (props?.format) {
format = dt.toFormat(props.format);
} else {
format = dt.toLocaleString(
props?.minTime
? DateTime.TIME_SIMPLE
: DateTime.DATETIME_SHORT,
);
}
error = props?.minTime
? i18n._(t`Time can not be before ${format}`)
: i18n._(t`Date can not be before ${format}`);
}
error = props?.minTime
? i18n._(t`Time can not be before ${format}`)
: i18n._(t`Date can not be before ${format}`);
}
break;
}
Expand All @@ -194,34 +261,7 @@ const DatePickerField = (props: DatePickerFieldProps) => {
form.setFieldError(field.name, error);
}
}}
toolbarTitle={props?.toolbarTitle ?? i18n._(t`Select date`)}
renderInput={(inputProps) => {
if (props?.placeholder && inputProps?.inputProps) {
inputProps.inputProps.placeholder = props.placeholder;
}
if (props?.id && inputProps?.inputProps) {
inputProps.inputProps.id = props.id;
}
return (
<TextField
name={field.name}
{...inputProps}
InputLabelProps={{ htmlFor: props.id }}
style={props.style ?? undefined}
size={props.size ?? "medium"}
error={Boolean(hasError)}
helperText={hasError ?? inputProps.helperText}
onBlur={() => {
form.setFieldTouched(field.name, true, false);
if (muiError) {
form.setFieldError(field.name, muiError);
} else {
form.validateField(field.name);
}
}}
/>
);
}}
localeText={localeText}
{...other}
/>
);
Expand Down
Loading
Loading