feat: make date fields read only

This commit is contained in:
Lê Nhân Phụng
2025-12-04 13:52:07 +07:00
committed by Benoit TELLIER
parent d68b4146d3
commit 3025672b49
3 changed files with 141 additions and 19 deletions
+4 -8
View File
@@ -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}
/>
);
}