feat(DateTimeFields): implement free typing time input with dropdown

- Replace default TimePicker field with custom EditableTimeField
- Allow free typing with multiple formats (HH:mm, HHmm, H, etc.)
- Show dropdown on click while maintaining typing capability
- Center-align time input text
This commit is contained in:
lenhanphung
2025-12-10 11:47:05 +07:00
committed by Benoit TELLIER
parent 12776d36a4
commit 627e9bf44b
13 changed files with 605 additions and 223 deletions
+4 -1
View File
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import TextField from "@mui/material/TextField";
import { userAttendee } from "../../features/User/userDataTypes";
import { PeopleSearch, User } from "./PeopleSearch";
@@ -37,7 +38,9 @@ export default function UserSearch({
selectedUsers={selectedUsers}
objectTypes={["user", "contact"]}
disabled={disabled}
small={small}
inputSlot={
small ? (params) => <TextField {...params} size="small" /> : undefined
}
onChange={(event: any, value: User[]) => {
setAttendees(
value.map((a: User) => ({
+103 -49
View File
@@ -1,4 +1,6 @@
import Autocomplete from "@mui/material/Autocomplete";
import Autocomplete, {
AutocompleteRenderInputParams,
} from "@mui/material/Autocomplete";
import Avatar from "@mui/material/Avatar";
import CircularProgress from "@mui/material/CircularProgress";
import ListItem from "@mui/material/ListItem";
@@ -22,6 +24,14 @@ export interface User {
color?: Record<string, string>;
}
export interface ExtendedAutocompleteRenderInputParams extends AutocompleteRenderInputParams {
error?: boolean;
helperText?: string | null;
placeholder?: string;
label?: string;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
}
export function PeopleSearch({
selectedUsers,
onChange,
@@ -30,7 +40,7 @@ export function PeopleSearch({
freeSolo,
onToggleEventPreview,
placeholder,
small,
inputSlot,
}: {
selectedUsers: User[];
onChange: Function;
@@ -39,7 +49,9 @@ export function PeopleSearch({
freeSolo?: boolean;
onToggleEventPreview?: () => void;
placeholder?: string;
small?: boolean;
inputSlot?: (
params: ExtendedAutocompleteRenderInputParams
) => React.ReactNode;
}) {
const { t } = useI18n();
const [query, setQuery] = useState("");
@@ -146,53 +158,95 @@ export function PeopleSearch({
);
onChange(event, mapped);
}}
renderInput={(params) => (
<>
<label htmlFor={params.id} className="visually-hidden">
{t("peopleSearch.label")}
</label>
<TextField
{...params}
size={small ? "small" : "medium"}
error={!!inputError}
helperText={inputError}
placeholder={searchPlaceholder}
label=""
inputProps={{
...params.inputProps,
renderInput={(params) => {
const inputProps = {
...params.InputProps,
startAdornment: (
<>
<PeopleOutlineOutlinedIcon
sx={{ mr: 1, color: "action.active" }}
/>
{params.InputProps.startAdornment}
</>
),
endAdornment: (
<>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</>
),
};
const enhancedParamsWithInputProps = {
...params,
InputProps: inputProps,
inputProps: {
...params.inputProps,
autoComplete: "off",
},
};
const { InputProps, ...enhancedParams } =
enhancedParamsWithInputProps;
const defaultTextFieldProps = {
error: !!inputError,
helperText: inputError,
placeholder: searchPlaceholder,
label: "",
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && onToggleEventPreview) {
e.preventDefault();
onToggleEventPreview();
}
},
slotProps: {
input: {
...inputProps,
autoComplete: "off",
}}
onKeyDown={(e) => {
if (e.key === "Enter" && onToggleEventPreview) {
e.preventDefault();
onToggleEventPreview();
}
}}
slotProps={{
input: {
...params.InputProps,
autoComplete: "off",
startAdornment: (
<>
<PeopleOutlineOutlinedIcon
style={{ marginRight: 8, color: "rgba(0, 0, 0, 0.54)" }}
/>
{params.InputProps.startAdornment}
</>
),
endAdornment: (
<>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</>
),
},
}}
/>
</>
)}
},
},
};
if (inputSlot) {
return (
<>
<label htmlFor={params.id} className="visually-hidden">
{t("peopleSearch.label")}
</label>
{inputSlot({
...enhancedParamsWithInputProps,
error: !!inputError,
helperText: inputError,
placeholder: searchPlaceholder,
label: "",
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && onToggleEventPreview) {
e.preventDefault();
onToggleEventPreview();
}
},
})}
</>
);
}
return (
<>
<label htmlFor={params.id} className="visually-hidden">
{t("peopleSearch.label")}
</label>
<TextField
{...enhancedParams}
{...defaultTextFieldProps}
InputProps={inputProps}
size="medium"
/>
</>
);
}}
renderOption={(props, option) => {
if (selectedUsers.find((u) => u.email === option.email)) return null;
const { key, ...otherProps } = props as any;