feat: make date fields read only
This commit is contained in:
committed by
Benoit TELLIER
parent
d68b4146d3
commit
3025672b49
@@ -84,8 +84,7 @@ describe("DateTimeFields", () => {
|
||||
showMore: true,
|
||||
});
|
||||
|
||||
const calendarButton = screen.getAllByRole("button");
|
||||
await userEvent.click(calendarButton[0]);
|
||||
await userEvent.click(screen.getByTestId("start-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "3" });
|
||||
await userEvent.click(dayButton);
|
||||
|
||||
@@ -129,8 +128,7 @@ describe("DateTimeFields", () => {
|
||||
showMore: true,
|
||||
});
|
||||
|
||||
const calendarButton = screen.getAllByRole("button");
|
||||
await userEvent.click(calendarButton[2]);
|
||||
await userEvent.click(screen.getByTestId("end-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "3" });
|
||||
await userEvent.click(dayButton);
|
||||
|
||||
@@ -151,8 +149,7 @@ describe("DateTimeFields", () => {
|
||||
showEndDate: true,
|
||||
});
|
||||
|
||||
const calendarButton = screen.getAllByRole("button");
|
||||
await userEvent.click(calendarButton[0]);
|
||||
await userEvent.click(screen.getByTestId("start-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "10" });
|
||||
await userEvent.click(dayButton);
|
||||
|
||||
@@ -173,8 +170,7 @@ describe("DateTimeFields", () => {
|
||||
showEndDate: true,
|
||||
});
|
||||
|
||||
const calendarButton = screen.getAllByRole("button");
|
||||
await userEvent.click(calendarButton[1]);
|
||||
await userEvent.click(screen.getByTestId("end-date-input"));
|
||||
const dayButton = screen.getByRole("gridcell", { name: "1" });
|
||||
await userEvent.click(dayButton);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import "dayjs/locale/vi";
|
||||
|
||||
import { PickerValue } from "@mui/x-date-pickers/internals";
|
||||
import { dtDate, dtTime, toDateTime } from "../utils/dateTimeHelpers";
|
||||
import { ReadOnlyDateField } from "./ReadOnlyPickerField";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
@@ -256,6 +257,16 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
},
|
||||
});
|
||||
|
||||
const getFieldSlotProps = (testId: string, hasError = false) => ({
|
||||
size: "small" as const,
|
||||
margin: "dense" as const,
|
||||
fullWidth: true,
|
||||
InputLabelProps: { shrink: true },
|
||||
error: hasError,
|
||||
sx: { width: "100%" },
|
||||
inputProps: { "data-testid": testId },
|
||||
});
|
||||
|
||||
return (
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDayjs}
|
||||
@@ -280,7 +291,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={startDateValue}
|
||||
onChange={handleStartDateChange}
|
||||
slotProps={getSlotProps("start-date-input")}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-date-input"),
|
||||
field: getFieldSlotProps("start-date-input") as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!allday && (
|
||||
@@ -302,10 +317,14 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={endDateValue}
|
||||
onChange={handleEndDateChange}
|
||||
slotProps={getSlotProps(
|
||||
"end-date-input",
|
||||
!!validation.errors.dateTime
|
||||
)}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("end-date-input", !!validation.errors.dateTime),
|
||||
field: getFieldSlotProps(
|
||||
"end-date-input",
|
||||
!!validation.errors.dateTime
|
||||
) as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!allday && (
|
||||
@@ -332,7 +351,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={startDateValue}
|
||||
onChange={handleStartDateChange}
|
||||
slotProps={getSlotProps("start-date-input")}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-date-input"),
|
||||
field: getFieldSlotProps("start-date-input") as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
@@ -341,10 +364,14 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={endDateValue}
|
||||
onChange={handleEndDateChange}
|
||||
slotProps={getSlotProps(
|
||||
"end-date-input",
|
||||
!!validation.errors.dateTime
|
||||
)}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("end-date-input", !!validation.errors.dateTime),
|
||||
field: getFieldSlotProps(
|
||||
"end-date-input",
|
||||
!!validation.errors.dateTime
|
||||
) as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -356,7 +383,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
format={LONG_DATE_FORMAT}
|
||||
value={startDateValue}
|
||||
onChange={handleStartDateChange}
|
||||
slotProps={getSlotProps("start-date-input")}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-date-input"),
|
||||
field: getFieldSlotProps("start-date-input") as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ maxWidth: "110px" }}>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from "react";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {
|
||||
PickerFieldAdapter,
|
||||
PickerValidationScope,
|
||||
useValidation,
|
||||
validateDate,
|
||||
} from "@mui/x-date-pickers/validation";
|
||||
import {
|
||||
useSplitFieldProps,
|
||||
useParsedFormat,
|
||||
usePickerContext,
|
||||
} from "@mui/x-date-pickers/hooks";
|
||||
import { PickerFieldProps } from "@mui/x-date-pickers/models";
|
||||
import { DatePickerFieldProps } from "@mui/x-date-pickers/DatePicker";
|
||||
|
||||
type FieldType = "date" | "time" | "date-time";
|
||||
|
||||
type GenericPickerFieldProps = PickerFieldProps<any, any, any> & {
|
||||
fieldType: FieldType;
|
||||
validator: (
|
||||
value: any,
|
||||
context: PickerValidationScope,
|
||||
adapter: PickerFieldAdapter<any>
|
||||
) => string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared read-only field for date/time pickers. Disables typing, removes icon,
|
||||
* and opens the picker when clicking anywhere in the field.
|
||||
*/
|
||||
function ReadOnlyPickerField(props: GenericPickerFieldProps) {
|
||||
const { fieldType, validator, ...fieldProps } = props;
|
||||
const { internalProps, forwardedProps } = useSplitFieldProps(
|
||||
fieldProps,
|
||||
fieldType
|
||||
);
|
||||
|
||||
const pickerContext = usePickerContext();
|
||||
const parsedFormat = useParsedFormat();
|
||||
|
||||
const { hasValidationError } = useValidation({
|
||||
validator,
|
||||
value: pickerContext.value,
|
||||
timezone: pickerContext.timezone,
|
||||
props: internalProps,
|
||||
});
|
||||
|
||||
const valueToDisplay =
|
||||
pickerContext.value == null
|
||||
? ""
|
||||
: pickerContext.value.isValid()
|
||||
? pickerContext.value.format(pickerContext.fieldFormat)
|
||||
: "";
|
||||
|
||||
const mergedInputProps = {
|
||||
...forwardedProps.InputProps,
|
||||
ref: pickerContext.triggerRef,
|
||||
readOnly: true,
|
||||
sx: {
|
||||
cursor: "pointer",
|
||||
"& *": { cursor: "inherit" },
|
||||
...forwardedProps.InputProps?.sx,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...forwardedProps}
|
||||
value={valueToDisplay}
|
||||
placeholder={parsedFormat as string}
|
||||
InputProps={mergedInputProps}
|
||||
error={hasValidationError}
|
||||
focused={pickerContext.open}
|
||||
onClick={() => pickerContext.setOpen((prev) => !prev)}
|
||||
className={pickerContext.rootClassName}
|
||||
sx={pickerContext.rootSx}
|
||||
ref={pickerContext.rootRef}
|
||||
name={pickerContext.name}
|
||||
label={pickerContext.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadOnlyDateField(props: DatePickerFieldProps) {
|
||||
return (
|
||||
<ReadOnlyPickerField
|
||||
{...props}
|
||||
fieldType="date"
|
||||
validator={validateDate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user