1717from reflex .components .core .cond import cond
1818from reflex .components .el .elements .forms import Input
1919from reflex .components .radix .themes .layout .box import Box
20+ from reflex .components .sonner .toast import toast
2021from reflex .constants import Dirs
2122from reflex .constants .compiler import Hooks , Imports
2223from reflex .environment import environment
3637from reflex .vars import VarData
3738from reflex .vars .base import Var , get_unique_variable_name
3839from reflex .vars .function import FunctionVar
39- from reflex .vars .sequence import LiteralStringVar
40+ from reflex .vars .object import ObjectVar
41+ from reflex .vars .sequence import ArrayVar , LiteralStringVar
4042
4143DEFAULT_UPLOAD_ID : str = "default"
4244
@@ -178,6 +180,34 @@ def _on_drop_spec(files: Var) -> tuple[Var[Any]]:
178180 return (files ,)
179181
180182
183+ def _default_drop_rejected (rejected_files : ArrayVar [list [dict [str , Any ]]]) -> EventSpec :
184+ """Event handler for showing a toast with rejected file info.
185+
186+ Args:
187+ rejected_files: The files that were rejected.
188+
189+ Returns:
190+ An event spec that shows a toast with the rejected file info when triggered.
191+ """
192+
193+ def _format_rejected_file_record (rf : ObjectVar [dict [str , Any ]]) -> str :
194+ rf = rf .to (ObjectVar , dict [str , dict [str , Any ]])
195+ file = rf ["file" ].to (ObjectVar , dict [str , Any ])
196+ errors = rf ["errors" ].to (ArrayVar , list [dict [str , Any ]])
197+ return (
198+ f"{ file ['path' ]} : { errors .foreach (lambda err : err ['message' ]).join (', ' )} "
199+ )
200+
201+ return toast .error (
202+ title = "Files not Accepted" ,
203+ description = rejected_files .to (ArrayVar )
204+ .foreach (_format_rejected_file_record )
205+ .join ("\n \n " ),
206+ close_button = True ,
207+ style = {"white_space" : "pre-line" },
208+ )
209+
210+
181211class UploadFilesProvider (Component ):
182212 """AppWrap component that provides a dict of selected files by ID via useContext."""
183213
@@ -191,6 +221,9 @@ class GhostUpload(Fragment):
191221 # Fired when files are dropped.
192222 on_drop : EventHandler [_on_drop_spec ]
193223
224+ # Fired when dropped files do not meet the specified criteria.
225+ on_drop_rejected : EventHandler [_on_drop_spec ]
226+
194227
195228class Upload (MemoizationLeaf ):
196229 """A file upload component."""
@@ -234,6 +267,9 @@ class Upload(MemoizationLeaf):
234267 # Fired when files are dropped.
235268 on_drop : EventHandler [_on_drop_spec ]
236269
270+ # Fired when dropped files do not meet the specified criteria.
271+ on_drop_rejected : EventHandler [_on_drop_spec ]
272+
237273 # Style rules to apply when actively dragging.
238274 drag_active_style : Style | None = field (default = None , is_javascript_property = False )
239275
@@ -295,6 +331,10 @@ def create(cls, *children, **props) -> Component:
295331 on_drop [ix ] = event
296332 upload_props ["on_drop" ] = on_drop
297333
334+ if upload_props .get ("on_drop_rejected" ) is None :
335+ # If on_drop_rejected is not provided, show an error toast.
336+ upload_props ["on_drop_rejected" ] = _default_drop_rejected
337+
298338 input_props_unique_name = get_unique_variable_name ()
299339 root_props_unique_name = get_unique_variable_name ()
300340 is_drag_active_unique_name = get_unique_variable_name ()
@@ -313,22 +353,22 @@ def create(cls, *children, **props) -> Component:
313353 ),
314354 )
315355
316- event_var , callback_str = StatefulComponent ._get_memoized_event_triggers (
317- GhostUpload .create (on_drop = upload_props ["on_drop" ])
318- )["on_drop" ]
319-
320- upload_props ["on_drop" ] = event_var
356+ event_triggers = StatefulComponent ._get_memoized_event_triggers (
357+ GhostUpload .create (
358+ on_drop = upload_props ["on_drop" ],
359+ on_drop_rejected = upload_props ["on_drop_rejected" ],
360+ )
361+ )
362+ callback_hooks = []
363+ for trigger_name , (event_var , callback_str ) in event_triggers .items ():
364+ upload_props [trigger_name ] = event_var
365+ callback_hooks .append (callback_str )
321366
322367 upload_props = {
323368 format .to_camel_case (key ): value for key , value in upload_props .items ()
324369 }
325370
326- use_dropzone_arguments = Var .create (
327- {
328- "onDrop" : event_var ,
329- ** upload_props ,
330- }
331- )
371+ use_dropzone_arguments = Var .create (upload_props )
332372
333373 left_side = (
334374 "const { "
@@ -344,11 +384,10 @@ def create(cls, *children, **props) -> Component:
344384 imports = Imports .EVENTS ,
345385 hooks = {Hooks .EVENTS : None },
346386 ),
347- event_var ._get_all_var_data (),
348387 use_dropzone_arguments ._get_all_var_data (),
349388 VarData (
350389 hooks = {
351- callback_str : None ,
390+ ** dict . fromkeys ( callback_hooks , None ) ,
352391 f"{ left_side } = { right_side } ;" : None ,
353392 },
354393 imports = {
0 commit comments