1- import { Autocomplete , TextField , Typography , useTheme , Stack } from "@mui/material" ;
1+ import {
2+ Autocomplete ,
3+ AutocompleteProps ,
4+ Stack ,
5+ SxProps ,
6+ TextField ,
7+ Theme ,
8+ Typography ,
9+ useTheme ,
10+ } from "@mui/material" ;
211import "./index.css" ;
3- import { AutoCompleteFieldProps } from "../../../types/widget.types" ;
4- import { AutoCompleteOption } from "../../../../domain/interfaces/i.widget" ;
512import { getAutocompleteStyles } from "../../../utils/inputStyles" ;
613
7- function AutoCompleteField ( {
8- id,
9- type,
10- options = [ ] ,
11- placeholder = "Type to search" ,
12- disabled,
13- sx,
14- width,
15- autoCompleteValue,
16- setAutoCompleteValue,
17- error,
18- multiple = false ,
19- value,
20- onChange,
14+ interface AutoCompleteFieldProps <
15+ T ,
16+ Multiple extends boolean | undefined = undefined ,
17+ DisableClearable extends boolean | undefined = undefined ,
18+ FreeSolo extends boolean | undefined = undefined
19+ > extends Omit < AutocompleteProps < T , Multiple , DisableClearable , FreeSolo > , "renderInput" | "sx" > {
20+ label ?: string ;
21+ placeholder ?: string ;
22+ error ?: string ;
23+ isRequired ?: boolean ;
24+ isOptional ?: boolean ;
25+ optionalLabel ?: string ;
26+ sx ?: SxProps < Theme > ;
27+ }
28+
29+ function AutoCompleteField <
30+ T ,
31+ Multiple extends boolean | undefined = undefined ,
32+ DisableClearable extends boolean | undefined = undefined ,
33+ FreeSolo extends boolean | undefined = undefined
34+ > ( {
2135 label,
22- isRequired = false ,
23- } : AutoCompleteFieldProps ) {
36+ placeholder,
37+ error,
38+ isRequired,
39+ isOptional,
40+ optionalLabel,
41+ sx,
42+ disabled,
43+ ...autocompleteProps
44+ } : AutoCompleteFieldProps < T , Multiple , DisableClearable , FreeSolo > ) {
2445 const theme = useTheme ( ) ;
2546
26- // For multiple selection with string options
27- if ( multiple && options . length > 0 && typeof options [ 0 ] === 'string' ) {
28- const stringOptions = options as string [ ] ;
29- const stringValue = ( value || [ ] ) as string [ ] ;
47+ // Extract layout props from sx to apply to wrapper Stack
48+ const extractedLayoutProps = ( ( ) => {
49+ if ( ! sx || typeof sx !== "object" || Array . isArray ( sx ) ) return { } ;
50+ const s = sx as Record < string , unknown > ;
51+ return {
52+ width : s . width as string | number | undefined ,
53+ flexGrow : s . flexGrow as number | undefined ,
54+ minWidth : s . minWidth as string | number | undefined ,
55+ maxWidth : s . maxWidth as string | number | undefined ,
56+ } ;
57+ } ) ( ) ;
3058
31- return (
32- < Stack gap = { theme . spacing ( 2 ) } sx = { sx } >
33- { label && (
34- < Typography
35- component = "p"
36- variant = "body1"
37- color = { theme . palette . text . secondary }
38- fontWeight = { 500 }
39- fontSize = { "13px" }
40- sx = { {
41- margin : 0 ,
42- height : '22px' ,
43- display : "flex" ,
44- alignItems : "center" ,
45- } }
46- >
47- { label }
48- { isRequired && (
49- < Typography
50- component = "span"
51- ml = { theme . spacing ( 1 ) }
52- color = { theme . palette . error . text }
53- >
54- *
55- </ Typography >
56- ) }
57- </ Typography >
58- ) }
59- < Autocomplete
60- multiple
61- id = { id }
62- options = { stringOptions }
63- value = { stringValue }
64- onChange = { ( _event , newValue ) => {
65- onChange ?.( newValue ) ;
66- } }
67- disabled = { disabled }
68- renderInput = { ( params ) => (
69- < TextField
70- { ...params }
71- size = "small"
72- placeholder = { placeholder }
73- error = { ! ! error }
74- sx = { {
75- "& .MuiOutlinedInput-root" : {
76- minHeight : "34px" ,
77- paddingTop : "2px !important" ,
78- paddingBottom : "2px !important" ,
79- } ,
80- "& ::placeholder" : {
81- fontSize : "13px" ,
82- } ,
83- } }
84- />
85- ) }
86- sx = { {
87- ...getAutocompleteStyles ( theme , { hasError : ! ! error } ) ,
88- width : "100%" ,
89- backgroundColor : theme . palette . background . main ,
90- "& .MuiOutlinedInput-root" : {
91- borderRadius : theme . shape . borderRadius ,
92- } ,
93- "& .MuiChip-root" : {
94- borderRadius : theme . shape . borderRadius ,
95- height : "22px" ,
96- margin : "1px 2px" ,
97- fontSize : "13px" ,
98- } ,
99- } }
100- slotProps = { {
101- popper : {
102- sx : {
103- "& ul" : { p : 0 } ,
104- "& li" : {
105- fontSize : 13 ,
106- borderRadius : theme . shape . borderRadius ,
107- transition : "color 0.2s ease, background-color 0.2s ease" ,
108- "&:hover" : {
109- color : theme . palette . primary . main ,
110- backgroundColor : theme . palette . background . accent ,
111- } ,
112- } ,
113- } ,
114- } ,
115- paper : {
116- sx : {
117- p : 2 ,
118- fontSize : 13 ,
119- borderRadius : theme . shape . borderRadius ,
120- boxShadow : theme . boxShadow ,
121- } ,
122- } ,
123- } }
124- />
125- { error && (
126- < Typography
127- className = "input-error"
128- color = { theme . palette . status . error . text }
129- sx = { {
130- opacity : 0.8 ,
131- fontSize : 11 ,
132- } }
133- >
134- { error }
135- </ Typography >
136- ) }
137- </ Stack >
59+ // Pass remaining sx props to the Autocomplete (excluding layout props already on wrapper)
60+ const sxWithoutLayoutProps = ( ( ) => {
61+ if ( ! sx || typeof sx !== "object" || Array . isArray ( sx ) ) return sx ;
62+ const s = sx as Record < string , unknown > ;
63+ return Object . fromEntries (
64+ Object . entries ( s ) . filter (
65+ ( [ key ] ) => ! [ "width" , "flexGrow" , "minWidth" , "maxWidth" ] . includes ( key )
66+ )
13867 ) ;
139- }
68+ } ) ( ) ;
14069
141- // Original single selection with object options
14270 return (
143- < >
144- < Autocomplete
145- sx = { {
146- ...getAutocompleteStyles ( theme , { hasError : ! ! error } ) ,
147- cursor : 'pointer' ,
148- ...sx ,
149- } }
150- className = "auto-complete-field"
151- id = { id }
152- value = { autoCompleteValue }
153- onChange = { ( _ , newValue ) => {
154- setAutoCompleteValue ?.( newValue ) ;
155- } }
156- options = { options as AutoCompleteOption [ ] }
157- getOptionLabel = { ( option ) => ( option && option . name ? option . name : "" ) }
158- disableClearable
71+ < Stack gap = { theme . spacing ( 2 ) } sx = { extractedLayoutProps } >
72+ { label && (
73+ < Typography
74+ component = "p"
75+ variant = "body1"
76+ color = { theme . palette . text . secondary }
77+ fontWeight = { 500 }
78+ fontSize = { "13px" }
79+ sx = { { margin : 0 , height : "22px" } }
80+ >
81+ { label }
82+ { isRequired && (
83+ < Typography
84+ component = "span"
85+ ml = { theme . spacing ( 1 ) }
86+ color = { theme . palette . error . text }
87+ >
88+ *
89+ </ Typography >
90+ ) }
91+ { isOptional && (
92+ < Typography
93+ component = "span"
94+ fontSize = "inherit"
95+ fontWeight = { 400 }
96+ ml = { theme . spacing ( 2 ) }
97+ sx = { { opacity : 0.6 } }
98+ >
99+ { optionalLabel || "(optional)" }
100+ </ Typography >
101+ ) }
102+ </ Typography >
103+ ) }
104+ < Autocomplete < T , Multiple , DisableClearable , FreeSolo >
159105 disabled = { disabled }
160- isOptionEqualToValue = { ( option , value ) => option . _id === value . _id }
161106 renderInput = { ( params ) => (
162107 < TextField
163- error = { ! ! error }
164108 { ...params }
165- type = { type }
109+ size = "small"
166110 placeholder = { placeholder }
167- InputProps = { {
168- ...params . InputProps ,
169- readOnly : true ,
170- sx : {
171- width : width ,
172- height : 34 ,
173- fontSize : 13 ,
174- p : 0 ,
175- borderRadius : theme . shape . borderRadius ,
176- "& input" : {
177- p : 0 ,
178- } ,
179- "&.Mui-disabled input" : {
180- cursor : "default" ,
181- } ,
111+ sx = { {
112+ "& .MuiOutlinedInput-root" : {
113+ minHeight : "34px" ,
114+ paddingTop : "2px !important" ,
115+ paddingBottom : "2px !important" ,
116+ } ,
117+ "& ::placeholder" : {
118+ fontSize : "13px" ,
182119 } ,
183120 } }
184121 />
185122 ) }
186- renderOption = { ( props , option ) => {
187- const { key : _key , ...optionProps } = props ;
188- return (
189- < li key = { option . _id } { ...optionProps } >
190- < div > { < span > { option . name } </ span > } </ div >
191- </ li >
192- ) ;
123+ sx = { {
124+ ...getAutocompleteStyles ( theme , { hasError : ! ! error } ) ,
125+ backgroundColor : theme . palette . background . main ,
126+ "& .MuiOutlinedInput-root" : {
127+ ...getAutocompleteStyles ( theme , { hasError : ! ! error } ) [ "& .MuiOutlinedInput-root" ] ,
128+ borderRadius : theme . shape . borderRadius ,
129+ } ,
130+ "& .MuiChip-root" : {
131+ borderRadius : theme . shape . borderRadius ,
132+ height : "22px" ,
133+ margin : "1px 2px" ,
134+ fontSize : "13px" ,
135+ } ,
136+ ...sxWithoutLayoutProps ,
193137 } }
194138 slotProps = { {
195139 popper : {
196140 sx : {
197141 "& ul" : { p : 0 } ,
198142 "& li" : {
143+ fontSize : 13 ,
199144 borderRadius : theme . shape . borderRadius ,
200145 transition : "color 0.2s ease, background-color 0.2s ease" ,
201146 "&:hover" : {
202147 color : theme . palette . primary . main ,
148+ backgroundColor : theme . palette . background . accent ,
203149 } ,
204150 } ,
205151 } ,
@@ -208,25 +154,28 @@ function AutoCompleteField({
208154 sx : {
209155 p : 2 ,
210156 fontSize : 13 ,
157+ borderRadius : theme . shape . borderRadius ,
158+ boxShadow : theme . boxShadow ,
211159 } ,
212160 } ,
213161 } }
162+ { ...autocompleteProps }
214163 />
215164 { error && (
216165 < Typography
217166 component = "span"
218167 className = "input-error"
219- color = { theme . palette . error . main }
168+ color = { theme . palette . status . error . text }
220169 mt = { theme . spacing ( 2 ) }
221170 sx = { {
222171 opacity : 0.8 ,
223- fontSize : 13 ,
172+ fontSize : 11 ,
224173 } }
225174 >
226175 { error }
227176 </ Typography >
228177 ) }
229- </ >
178+ </ Stack >
230179 ) ;
231180}
232181
0 commit comments