-
Notifications
You must be signed in to change notification settings - Fork 100
Expand file tree
/
Copy pathDatepicker.tsx
More file actions
191 lines (183 loc) · 5.55 KB
/
Copy pathDatepicker.tsx
File metadata and controls
191 lines (183 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import { InputProps, TextField } from "@mui/material";
import {
DatePicker,
DatePickerProps,
usePickerAdapter,
usePickerContext,
} from "@mui/x-date-pickers";
import { useTranslation } from "i18n";
import { getMuiDateFormat } from "i18n/dates";
import { Control, Controller, UseControllerProps } from "react-hook-form";
import dayjs, { Dayjs } from "utils/dayjs";
interface DatepickerProps {
className?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
control: Control<any>;
defaultValue?: Dayjs | null;
error: boolean;
helperText: React.ReactNode;
id: string;
rules?: UseControllerProps["rules"];
label?: string;
name: string;
minDate?: Dayjs;
maxDate?: Dayjs;
openTo?: "year" | "month" | "day";
onPostChange?(date: Dayjs | null): void;
testId?: string;
variant?: "standard" | "outlined" | "filled";
inputProps?: InputProps;
// When true the field can only be set through the picker: no text input, no
// mask placeholder, clicking anywhere (not just the icon) opens the calendar,
// and the chosen date is shown as a localized long date with the month name
// (e.g. "May 25, 1990"). Otherwise the field is an editable, parseable numeric
// date in the locale's format.
pickerInputOnly?: boolean;
}
interface ReadOnlyDateFieldProps {
className?: string;
id?: string;
label?: React.ReactNode;
error?: boolean;
helperText?: React.ReactNode;
variant?: "standard" | "outlined" | "filled";
ariaLabel: string;
inputProps?: InputProps;
}
// Read-only field used by `pickerInputOnly` pickers. It renders the selected
// date as a localized long date (month name) and is blank when no date is set,
// so there is never an editable section mask or format placeholder. Clicking it
// opens the calendar, which is the only way to set the value.
const ReadOnlyDateField = ({
className,
id,
label,
error,
helperText,
variant,
ariaLabel,
inputProps,
}: ReadOnlyDateFieldProps) => {
const pickerContext = usePickerContext<Dayjs | null>();
const adapter = usePickerAdapter();
const value = pickerContext.value;
// Format through the picker adapter so the long date respects the picker's
// adapterLocale (e.g. German "8. April 1990"), not just dayjs's global locale.
const display = value ? adapter.formatByString(value, "LL") : "";
return (
<TextField
ref={pickerContext.triggerRef}
className={className}
id={id}
label={label}
error={error}
helperText={helperText}
variant={variant}
fullWidth
value={display}
onClick={() => pickerContext.setOpen(true)}
slotProps={{
inputLabel: { shrink: true },
htmlInput: { readOnly: true, "aria-label": ariaLabel },
input: inputProps,
}}
/>
);
};
const Datepicker = ({
className,
control,
defaultValue,
error,
helperText,
id,
rules,
label,
minDate = dayjs(),
maxDate,
name,
openTo = "day",
onPostChange,
testId,
variant = "standard",
inputProps = {},
pickerInputOnly = false,
}: DatepickerProps) => {
const { t, i18n } = useTranslation();
const ariaLabel = t("components.datepicker.change_date");
const helperNode = (
<span data-testid={`${name}-helper-text`}>{helperText}</span>
);
// `pickerInputOnly` swaps the editable masked field for a read-only field slot
// (ReadOnlyDateField) so there is never a format placeholder. ReadOnlyDateField
// deliberately doesn't conform to MUI's injected field props (it reads value
// and open-state from the picker context), so the slot wiring is cast through
// `unknown` — MUI's own custom-field pattern requires this.
const pickerOnlyProps = {
slots: { field: ReadOnlyDateField },
slotProps: {
field: {
className,
id,
label,
error,
helperText: helperNode,
variant,
ariaLabel,
inputProps,
},
},
} as unknown as Partial<DatePickerProps>;
return (
<Controller
control={control}
defaultValue={defaultValue}
name={name}
rules={rules}
render={({ field }) => (
<DatePicker
data-testid={testId}
{...field}
label={label}
value={field.value}
minDate={minDate}
maxDate={maxDate}
onChange={(date) => {
field.onChange(date);
onPostChange?.(date);
}}
openTo={openTo}
views={["year", "month", "day"]}
// Picker-only fields show a localized long date with the month name;
// editable fields use the parseable numeric locale format.
format={pickerInputOnly ? "LL" : getMuiDateFormat(i18n.language)}
{...(pickerInputOnly
? pickerOnlyProps
: {
slotProps: {
textField: {
// Apply the consumer className to the field root (FormControl)
// so layout styles like margins wrap the whole field, not the
// input box (which would push the helper text away).
className,
fullWidth: true,
id,
error,
helperText: helperNode,
variant,
slotProps: {
inputLabel: { shrink: true },
},
InputProps: {
...(inputProps || {}),
"aria-label": ariaLabel,
},
},
},
})}
/>
)}
/>
);
};
export default Datepicker;