[#420] passed a new param to people search to customize slotprops (#424)

* [#420] passed a new param to people search to customize slotprops

* Added back function lost in rebase

---------

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-12-17 07:29:01 +01:00
committed by GitHub
parent a28f41fab9
commit 89103b5787
2 changed files with 116 additions and 48 deletions
+96 -48
View File
@@ -7,7 +7,13 @@ import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar"; import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { type ReactNode, useCallback, useEffect, useState } from "react"; import {
type ReactNode,
useCallback,
useEffect,
useState,
HTMLAttributes,
} from "react";
import { searchUsers } from "../../features/User/userAPI"; import { searchUsers } from "../../features/User/userAPI";
import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined"; import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
@@ -15,6 +21,7 @@ import { useTheme } from "@mui/material/styles";
import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils"; import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { SnackbarAlert } from "../Loading/SnackBarAlert"; import { SnackbarAlert } from "../Loading/SnackBarAlert";
import { PopperProps, PaperProps } from "@mui/material";
export interface User { export interface User {
email: string; email: string;
@@ -42,6 +49,7 @@ export function PeopleSearch({
placeholder, placeholder,
inputSlot, inputSlot,
customRenderInput, customRenderInput,
customSlotProps,
}: { }: {
selectedUsers: User[]; selectedUsers: User[];
onChange: (event: any, users: User[]) => void; onChange: (event: any, users: User[]) => void;
@@ -58,6 +66,11 @@ export function PeopleSearch({
query: string, query: string,
setQuery: (value: string) => void setQuery: (value: string) => void
) => ReactNode; ) => ReactNode;
customSlotProps?: {
popper?: Partial<PopperProps>;
paper?: Partial<PaperProps>;
listbox?: Partial<HTMLAttributes<HTMLUListElement>>;
};
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -119,53 +132,87 @@ export function PeopleSearch({
}, [objectTypes, query, t]); }, [objectTypes, query, t]);
const defaultRenderInput = useCallback( const defaultRenderInput = useCallback(
(params: AutocompleteRenderInputParams) => ( (params: AutocompleteRenderInputParams) => {
<> const inputProps = {
<label htmlFor={params.id} className="visually-hidden"> ...params.InputProps,
{t("peopleSearch.label")} startAdornment: (
</label> <>
<TextField <PeopleOutlineOutlinedIcon sx={{ mr: 1, color: "action.active" }} />
{...params} {params.InputProps.startAdornment}
error={!!inputError} </>
helperText={inputError} ),
placeholder={searchPlaceholder} endAdornment: (
label="" <>
inputProps={{ {loading ? <CircularProgress color="inherit" size={20} /> : null}
...params.inputProps, {params.InputProps.endAdornment}
autoComplete: "off", </>
}} ),
onKeyDown={(e) => { };
if (e.key === "Enter" && onToggleEventPreview) {
e.preventDefault(); const enhancedParamsWithInputProps = {
onToggleEventPreview(); ...params,
} InputProps: inputProps,
}} inputProps: {
slotProps={{ ...params.inputProps,
input: { autoComplete: "off",
...params.InputProps, },
autoComplete: "off", };
startAdornment: (
<> const { InputProps, ...enhancedParams } = enhancedParamsWithInputProps;
<PeopleOutlineOutlinedIcon
style={{ marginRight: 8, color: "rgba(0, 0, 0, 0.54)" }} const handleEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
/> if (e.key === "Enter" && onToggleEventPreview) {
{params.InputProps.startAdornment} e.preventDefault();
</> onToggleEventPreview();
), }
endAdornment: ( };
<>
{loading ? ( const defaultTextFieldProps = {
<CircularProgress color="inherit" size={20} /> error: !!inputError,
) : null} helperText: inputError,
{params.InputProps.endAdornment} placeholder: searchPlaceholder,
</> label: "",
), onKeyDown: handleEnterKey,
}, slotProps: {
}} input: {
/> ...inputProps,
</> },
), },
[inputError, t, onToggleEventPreview, loading] };
if (inputSlot) {
return (
<>
<label htmlFor={params.id} className="visually-hidden">
{t("peopleSearch.label")}
</label>
{inputSlot({
...enhancedParamsWithInputProps,
error: !!inputError,
helperText: inputError,
placeholder: searchPlaceholder,
label: "",
onKeyDown: handleEnterKey,
})}
</>
);
}
return (
<>
<label htmlFor={params.id} className="visually-hidden">
{t("peopleSearch.label")}
</label>
<TextField
{...enhancedParams}
{...defaultTextFieldProps}
InputProps={inputProps}
size="medium"
/>
</>
);
},
[inputError, t, onToggleEventPreview, loading, searchPlaceholder]
); );
return ( return (
@@ -225,6 +272,7 @@ export function PeopleSearch({
); );
onChange(event, mapped); onChange(event, mapped);
}} }}
slotProps={customSlotProps}
renderInput={(params) => renderInput={(params) =>
customRenderInput customRenderInput
? customRenderInput(params, query, setQuery) ? customRenderInput(params, query, setQuery)
+20
View File
@@ -233,6 +233,26 @@ export default function SearchBar() {
}} }}
objectTypes={["user", "contact"]} objectTypes={["user", "contact"]}
onToggleEventPreview={() => {}} onToggleEventPreview={() => {}}
customSlotProps={{
popper: {
anchorEl: containerRef.current,
placement: "bottom-start",
sx: {
minWidth: searchWidth,
"& .MuiPaper-root": {
width: "100%",
},
},
modifiers: [
{
name: "offset",
options: {
offset: [0, 8],
},
},
],
},
}}
customRenderInput={( customRenderInput={(
params: AutocompleteRenderInputParams, params: AutocompleteRenderInputParams,
query: string, query: string,