Browse Source

promote toast to main namespace (#3422)

Thomas Brandého 11 tháng trước cách đây
mục cha
commit
e1244b4ea6

+ 7 - 1
reflex/__init__.py

@@ -248,7 +248,12 @@ _MAPPING: dict = {
     "admin": ["AdminDash"],
     "admin": ["AdminDash"],
     "app": ["App", "UploadFile"],
     "app": ["App", "UploadFile"],
     "base": ["Base"],
     "base": ["Base"],
-    "components.component": ["Component", "NoSSRComponent", "memo"],
+    "components.component": [
+        "Component",
+        "NoSSRComponent",
+        "memo",
+        "ComponentNamespace",
+    ],
     "components.el.elements.media": ["image"],
     "components.el.elements.media": ["image"],
     "components.lucide": ["icon"],
     "components.lucide": ["icon"],
     **COMPONENTS_BASE_MAPPING,
     **COMPONENTS_BASE_MAPPING,
@@ -270,6 +275,7 @@ _MAPPING: dict = {
         "data_editor",
         "data_editor",
         "data_editor_theme",
         "data_editor_theme",
     ],
     ],
+    "components.sonner.toast": ["toast"],
     "components.datadisplay.logo": ["logo"],
     "components.datadisplay.logo": ["logo"],
     "components.gridjs": ["data_table"],
     "components.gridjs": ["data_table"],
     "components.moment": ["MomentDelta", "moment"],
     "components.moment": ["MomentDelta", "moment"],

+ 2 - 0
reflex/__init__.pyi

@@ -24,6 +24,7 @@ from .base import Base as Base
 from .components.component import Component as Component
 from .components.component import Component as Component
 from .components.component import NoSSRComponent as NoSSRComponent
 from .components.component import NoSSRComponent as NoSSRComponent
 from .components.component import memo as memo
 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.el.elements.media import image as image
 from .components.lucide import icon as icon
 from .components.lucide import icon as icon
 from .components.base.fragment import fragment as fragment
 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.code import code_block as code_block
 from .components.datadisplay.dataeditor import data_editor as data_editor
 from .components.datadisplay.dataeditor import data_editor as data_editor
 from .components.datadisplay.dataeditor import data_editor_theme as data_editor_theme
 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.datadisplay.logo import logo as logo
 from .components.gridjs import data_table as data_table
 from .components.gridjs import data_table as data_table
 from .components.moment import MomentDelta as MomentDelta
 from .components.moment import MomentDelta as MomentDelta

+ 30 - 14
reflex/components/sonner/toast.py

@@ -106,7 +106,7 @@ class ToastProps(PropsBase):
     cancel: Optional[ToastAction]
     cancel: Optional[ToastAction]
 
 
     # Custom id for the toast.
     # Custom id for the toast.
-    id: Optional[str]
+    id: Optional[Union[str, Var]]
 
 
     # Removes the default styling, which allows for easier customization.
     # Removes the default styling, which allows for easier customization.
     unstyled: Optional[bool]
     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).
     # Function that gets called when the toast disappears automatically after it's timeout (duration` prop).
     on_auto_close: Optional[Any]
     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.
         """Convert the object to a dictionary.
 
 
         Args:
         Args:
@@ -137,7 +137,7 @@ class ToastProps(PropsBase):
         Returns:
         Returns:
             The object as a dictionary with ToastAction fields intact.
             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)
         d = super().dict(*args, **kwargs)
         # Keep these fields as ToastAction so they can be serialized specially
         # Keep these fields as ToastAction so they can be serialized specially
         if "action" in d:
         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.
     # 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]
     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(
         hook = Var.create_safe(
             f"{toast_ref} = toast",
             f"{toast_ref} = toast",
             _var_is_local=True,
             _var_is_local=True,
@@ -219,7 +224,7 @@ class Toaster(Component):
                 }
                 }
             ),
             ),
         )
         )
-        return hook
+        return [hook]
 
 
     @staticmethod
     @staticmethod
     def send_toast(message: str, level: str | None = None, **props) -> EventSpec:
     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
         toast_command = f"{toast_ref}.{level}" if level is not None else toast_ref
         if props:
         if props:
-            args = serialize(ToastProps(**props))
+            args = serialize(ToastProps(**props))  # type: ignore
             toast = f"{toast_command}(`{message}`, {args})"
             toast = f"{toast_command}(`{message}`, {args})"
         else:
         else:
             toast = f"{toast_command}(`{message}`)"
             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
     @staticmethod
     def toast_info(message: str, **kwargs):
     def toast_info(message: str, **kwargs):
@@ -295,7 +300,8 @@ class Toaster(Component):
         """
         """
         return Toaster.send_toast(message, level="success", **kwargs)
         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.
         """Dismiss a toast.
 
 
         Args:
         Args:
@@ -304,12 +310,22 @@ class Toaster(Component):
         Returns:
         Returns:
             The toast dismiss event.
             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:
         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
 # TODO: figure out why loading toast stay open forever

+ 8 - 4
reflex/components/sonner/toast.pyi

@@ -46,7 +46,7 @@ class ToastProps(PropsBase):
     dismissible: Optional[bool]
     dismissible: Optional[bool]
     action: Optional[ToastAction]
     action: Optional[ToastAction]
     cancel: Optional[ToastAction]
     cancel: Optional[ToastAction]
-    id: Optional[str]
+    id: Optional[Union[str, Var]]
     unstyled: Optional[bool]
     unstyled: Optional[bool]
     style: Optional[Style]
     style: Optional[Style]
     action_button_styles: Optional[Style]
     action_button_styles: Optional[Style]
@@ -54,9 +54,10 @@ class ToastProps(PropsBase):
     on_dismiss: Optional[Any]
     on_dismiss: Optional[Any]
     on_auto_close: Optional[Any]
     on_auto_close: Optional[Any]
 
 
-    def dict(self, *args, **kwargs) -> dict: ...
+    def dict(self, *args, **kwargs) -> dict[str, Any]: ...
 
 
 class Toaster(Component):
 class Toaster(Component):
+    def add_hooks(self) -> list[Var | str]: ...
     @staticmethod
     @staticmethod
     def send_toast(message: str, level: str | None = None, **props) -> EventSpec: ...
     def send_toast(message: str, level: str | None = None, **props) -> EventSpec: ...
     @staticmethod
     @staticmethod
@@ -67,7 +68,8 @@ class Toaster(Component):
     def toast_error(message: str, **kwargs): ...
     def toast_error(message: str, **kwargs): ...
     @staticmethod
     @staticmethod
     def toast_success(message: str, **kwargs): ...
     def toast_success(message: str, **kwargs): ...
-    def toast_dismiss(self, id: str | None): ...
+    @staticmethod
+    def toast_dismiss(id: Var | str | None = None): ...
     @overload
     @overload
     @classmethod
     @classmethod
     def create(  # type: ignore
     def create(  # type: ignore
@@ -202,7 +204,9 @@ class ToastNamespace(ComponentNamespace):
     dismiss = staticmethod(Toaster.toast_dismiss)
     dismiss = staticmethod(Toaster.toast_dismiss)
 
 
     @staticmethod
     @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.
         """Send a toast message.
 
 
         Args:
         Args:

+ 1 - 1
reflex/event.py

@@ -714,7 +714,7 @@ def _callback_arg_spec(eval_result):
 
 
 
 
 def call_script(
 def call_script(
-    javascript_code: str,
+    javascript_code: str | Var[str],
     callback: EventSpec
     callback: EventSpec
     | EventHandler
     | EventHandler
     | Callable
     | Callable

+ 22 - 2
reflex/experimental/__init__.py

@@ -17,7 +17,28 @@ warn(
     "`rx._x` contains experimental features and might be removed at any time in the future .",
     "`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,
     asset=asset,
     client_state=ClientStateVar.create,
     client_state=ClientStateVar.create,
     hooks=hooks,
     hooks=hooks,
@@ -25,5 +46,4 @@ _x = SimpleNamespace(
     progress=progress,
     progress=progress,
     PropsBase=PropsBase,
     PropsBase=PropsBase,
     run_in_thread=run_in_thread,
     run_in_thread=run_in_thread,
-    toast=toast,
 )
 )

+ 3 - 1
reflex/utils/pyi_generator.py

@@ -488,7 +488,9 @@ def _generate_staticmethod_call_functiondef(
         kwonlyargs=[],
         kwonlyargs=[],
         kw_defaults=[],
         kw_defaults=[],
         kwarg=ast.arg(arg="props"),
         kwarg=ast.arg(arg="props"),
-        defaults=[],
+        defaults=[ast.Constant(value=default) for default in fullspec.defaults]
+        if fullspec.defaults
+        else [],
     )
     )
     definition = ast.FunctionDef(
     definition = ast.FunctionDef(
         name="__call__",
         name="__call__",