diff --git a/reflex/__init__.py b/reflex/__init__.py index 46ed9e400..dd3f141e8 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -248,7 +248,12 @@ _MAPPING: dict = { "admin": ["AdminDash"], "app": ["App", "UploadFile"], "base": ["Base"], - "components.component": ["Component", "NoSSRComponent", "memo"], + "components.component": [ + "Component", + "NoSSRComponent", + "memo", + "ComponentNamespace", + ], "components.el.elements.media": ["image"], "components.lucide": ["icon"], **COMPONENTS_BASE_MAPPING, @@ -270,6 +275,7 @@ _MAPPING: dict = { "data_editor", "data_editor_theme", ], + "components.sonner.toast": ["toast"], "components.datadisplay.logo": ["logo"], "components.gridjs": ["data_table"], "components.moment": ["MomentDelta", "moment"], diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 8bea0c90a..da47e6a6f 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -24,6 +24,7 @@ from .base import Base as Base from .components.component import Component as Component from .components.component import NoSSRComponent as NoSSRComponent from .components.component import memo as memo +from .components.component import ComponentNamespace as ComponentNamespace from .components.el.elements.media import image as image from .components.lucide import icon as icon from .components.base.fragment import fragment as fragment @@ -143,6 +144,7 @@ from .components.core.upload import upload as upload from .components.datadisplay.code import code_block as code_block from .components.datadisplay.dataeditor import data_editor as data_editor from .components.datadisplay.dataeditor import data_editor_theme as data_editor_theme +from .components.sonner.toast import toast as toast from .components.datadisplay.logo import logo as logo from .components.gridjs import data_table as data_table from .components.moment import MomentDelta as MomentDelta diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index 648b0db9c..f35b77515 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -106,7 +106,7 @@ class ToastProps(PropsBase): cancel: Optional[ToastAction] # Custom id for the toast. - id: Optional[str] + id: Optional[Union[str, Var]] # Removes the default styling, which allows for easier customization. unstyled: Optional[bool] @@ -127,7 +127,7 @@ class ToastProps(PropsBase): # Function that gets called when the toast disappears automatically after it's timeout (duration` prop). on_auto_close: Optional[Any] - def dict(self, *args, **kwargs) -> dict: + def dict(self, *args, **kwargs) -> dict[str, Any]: """Convert the object to a dictionary. Args: @@ -137,7 +137,7 @@ class ToastProps(PropsBase): Returns: The object as a dictionary with ToastAction fields intact. """ - kwargs.setdefault("exclude_none", True) + kwargs.setdefault("exclude_none", True) # type: ignore d = super().dict(*args, **kwargs) # Keep these fields as ToastAction so they can be serialized specially if "action" in d: @@ -208,7 +208,12 @@ class Toaster(Component): # Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked. pause_when_page_is_hidden: Var[bool] - def _get_hooks(self) -> Var[str]: + def add_hooks(self) -> list[Var | str]: + """Add hooks for the toaster component. + + Returns: + The hooks for the toaster component. + """ hook = Var.create_safe( f"{toast_ref} = toast", _var_is_local=True, @@ -219,7 +224,7 @@ class Toaster(Component): } ), ) - return hook + return [hook] @staticmethod def send_toast(message: str, level: str | None = None, **props) -> EventSpec: @@ -235,13 +240,13 @@ class Toaster(Component): """ toast_command = f"{toast_ref}.{level}" if level is not None else toast_ref if props: - args = serialize(ToastProps(**props)) + args = serialize(ToastProps(**props)) # type: ignore toast = f"{toast_command}(`{message}`, {args})" else: toast = f"{toast_command}(`{message}`)" - toast_action = Var.create(toast, _var_is_string=False, _var_is_local=True) - return call_script(toast_action) # type: ignore + toast_action = Var.create_safe(toast, _var_is_string=False, _var_is_local=True) + return call_script(toast_action) @staticmethod def toast_info(message: str, **kwargs): @@ -295,7 +300,8 @@ class Toaster(Component): """ return Toaster.send_toast(message, level="success", **kwargs) - def toast_dismiss(self, id: str | None): + @staticmethod + def toast_dismiss(id: Var | str | None = None): """Dismiss a toast. Args: @@ -304,12 +310,22 @@ class Toaster(Component): Returns: The toast dismiss event. """ - if id is None: - dismiss = f"{toast_ref}.dismiss()" + dismiss_var_data = None + + if isinstance(id, Var): + dismiss = f"{toast_ref}.dismiss({id._var_name_unwrapped})" + dismiss_var_data = id._var_data + elif isinstance(id, str): + dismiss = f"{toast_ref}.dismiss('{id}')" else: - dismiss = f"{toast_ref}.dismiss({id})" - dismiss_action = Var.create(dismiss, _var_is_string=False, _var_is_local=True) - return call_script(dismiss_action) # type: ignore + dismiss = f"{toast_ref}.dismiss()" + dismiss_action = Var.create_safe( + dismiss, + _var_is_string=False, + _var_is_local=True, + _var_data=dismiss_var_data, + ) + return call_script(dismiss_action) # TODO: figure out why loading toast stay open forever diff --git a/reflex/components/sonner/toast.pyi b/reflex/components/sonner/toast.pyi index 6bc5ab2b5..25273d8e6 100644 --- a/reflex/components/sonner/toast.pyi +++ b/reflex/components/sonner/toast.pyi @@ -46,7 +46,7 @@ class ToastProps(PropsBase): dismissible: Optional[bool] action: Optional[ToastAction] cancel: Optional[ToastAction] - id: Optional[str] + id: Optional[Union[str, Var]] unstyled: Optional[bool] style: Optional[Style] action_button_styles: Optional[Style] @@ -54,9 +54,10 @@ class ToastProps(PropsBase): on_dismiss: Optional[Any] on_auto_close: Optional[Any] - def dict(self, *args, **kwargs) -> dict: ... + def dict(self, *args, **kwargs) -> dict[str, Any]: ... class Toaster(Component): + def add_hooks(self) -> list[Var | str]: ... @staticmethod def send_toast(message: str, level: str | None = None, **props) -> EventSpec: ... @staticmethod @@ -67,7 +68,8 @@ class Toaster(Component): def toast_error(message: str, **kwargs): ... @staticmethod def toast_success(message: str, **kwargs): ... - def toast_dismiss(self, id: str | None): ... + @staticmethod + def toast_dismiss(id: Var | str | None = None): ... @overload @classmethod def create( # type: ignore @@ -202,7 +204,9 @@ class ToastNamespace(ComponentNamespace): dismiss = staticmethod(Toaster.toast_dismiss) @staticmethod - def __call__(message: str, level: Optional[str], **props) -> "Optional[EventSpec]": + def __call__( + message: str, level: Optional[str] = None, **props + ) -> "Optional[EventSpec]": """Send a toast message. Args: diff --git a/reflex/event.py b/reflex/event.py index 4a2b34df3..01abb64b9 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -714,7 +714,7 @@ def _callback_arg_spec(eval_result): def call_script( - javascript_code: str, + javascript_code: str | Var[str], callback: EventSpec | EventHandler | Callable diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index b4ebc1086..f0eca0c84 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -17,7 +17,28 @@ warn( "`rx._x` contains experimental features and might be removed at any time in the future .", ) -_x = SimpleNamespace( +_EMITTED_PROMOTION_WARNINGS = set() + + +class ExperimentalNamespace(SimpleNamespace): + """Namespace for experimental features.""" + + @property + def toast(self): + """Temporary property returning the toast namespace. + + Remove this property when toast is fully promoted. + + Returns: + The toast namespace. + """ + if "toast" not in _EMITTED_PROMOTION_WARNINGS: + _EMITTED_PROMOTION_WARNINGS.add("toast") + warn(f"`rx._x.toast` was promoted to `rx.toast`.") + return toast + + +_x = ExperimentalNamespace( asset=asset, client_state=ClientStateVar.create, hooks=hooks, @@ -25,5 +46,4 @@ _x = SimpleNamespace( progress=progress, PropsBase=PropsBase, run_in_thread=run_in_thread, - toast=toast, ) diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 518cbcb38..3b5352a04 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -488,7 +488,9 @@ def _generate_staticmethod_call_functiondef( kwonlyargs=[], kw_defaults=[], kwarg=ast.arg(arg="props"), - defaults=[], + defaults=[ast.Constant(value=default) for default in fullspec.defaults] + if fullspec.defaults + else [], ) definition = ast.FunctionDef( name="__call__",