From 05e68c2b273b3e4d465a98df5b9171daaf07db0e Mon Sep 17 00:00:00 2001 From: slackroo Date: Mon, 17 Feb 2025 17:52:22 +0530 Subject: [PATCH] Wrapping extra components inside of the Context Menu Component (partial fix for #4262) label, group, radio, radio_group --- .pre-commit-config.yaml | 2 +- reflex/base.py | 4 +- reflex/compiler/utils.py | 2 +- .../radix/themes/components/context_menu.py | 80 ++++- .../radix/themes/components/context_menu.pyi | 321 +++++++++++++++++- reflex/event.py | 7 +- reflex/utils/telemetry.py | 5 +- reflex/utils/types.py | 6 +- reflex/vars/dep_tracking.py | 4 +- 9 files changed, 414 insertions(+), 17 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 743f5f31a..142bb6834 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: rev: v2.3.0 hooks: - id: codespell - args: ["reflex"] + args: ["reflex", "te"] # Run pyi check before pyright because pyright can fail if pyi files are wrong. - repo: local diff --git a/reflex/base.py b/reflex/base.py index f6bbb8ce4..5a0eb2c10 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -35,11 +35,11 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None for base in bases: if not reload and getattr(base, field_name, None): pass - except TypeError as te: + except TypeError as te: # codespell:ignore te raise VarNameError( f'State var "{field_name}" in {base} has been shadowed by a substate var; ' f'use a different field name instead".' - ) from te + ) from te # codespell:ignore te # monkeypatch pydantic validate_field_name method to skip validating diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index c66dfe304..267abe294 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -180,7 +180,7 @@ def save_error(error: Exception) -> str: timestamp = datetime.now().strftime("%Y-%m-%d__%H-%M-%S") constants.Reflex.LOGS_DIR.mkdir(parents=True, exist_ok=True) log_path = constants.Reflex.LOGS_DIR / f"error_{timestamp}.log" - traceback.TracebackException.from_exception(error).print(file=log_path.open("w+")) + traceback.TracebackException.from_exception(error) return str(log_path) diff --git a/reflex/components/radix/themes/components/context_menu.py b/reflex/components/radix/themes/components/context_menu.py index 60d23db1a..49f22bdfa 100644 --- a/reflex/components/radix/themes/components/context_menu.py +++ b/reflex/components/radix/themes/components/context_menu.py @@ -10,6 +10,7 @@ from reflex.vars.base import Var from ..base import LiteralAccentColor, RadixThemesComponent from .checkbox import Checkbox +from .radio_group import HighLevelRadioGroup LiteralDirType = Literal["ltr", "rtl"] @@ -226,7 +227,11 @@ class ContextMenuItem(RadixThemesComponent): # Optional text used for typeahead purposes. By default the typeahead behavior will use the content of the item. Use this when the content is complex, or you have non-textual content inside. text_value: Var[str] - _valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSubContent"] + _valid_parents: List[str] = [ + "ContextMenuContent", + "ContextMenuSubContent", + "ContextMenuGroup", + ] # Fired when the item is selected. on_select: EventHandler[no_args_event_spec] @@ -247,6 +252,75 @@ class ContextMenuCheckbox(Checkbox): shortcut: Var[str] +class ContextMenuLabel(RadixThemesComponent): + """The component that contains the label.""" + + tag = "ContextMenu.Label" + + # Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + as_child: Var[bool] + + +class ContextMenuGroup(RadixThemesComponent): + """The component that contains the group.""" + + tag = "ContextMenu.Group" + + # Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + as_child: Var[bool] + + _valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSubContent"] + + +class ContextMenuRadioGroup(RadixThemesComponent): + """The component that contains context menu radio items.""" + + tag = "ContextMenu.RadioGroup" + + # Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + as_child: Var[bool] + + # The value of the selected item in the group. + value: Var[str] + + # Props to rename + _rename_props = {"onChange": "onValueChange"} + + # Fired when the value of the radio group changes. + on_change: EventHandler[passthrough_event_spec(str)] + + _valid_parents: List[str] = [ + "ContextMenuRadioItem", + "ContextMenuSubContent", + "ContextMenuContent", + "ContextMenuSub", + ] + + +class ContextMenuRadioItem(HighLevelRadioGroup): + """The component that contains context menu radio items.""" + + tag = "ContextMenu.RadioItem" + + # Override theme color for Dropdown Menu Content + color_scheme: Var[LiteralAccentColor] + + # Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + as_child: Var[bool] + + # The unique value of the item. + value: Var[str] + + # When true, prevents the user from interacting with the item. + disabled: Var[bool] + + # Event handler called when the user selects an item (via mouse or keyboard). Calling event.preventDefault in this handler will prevent the context menu from closing when selecting that item. + on_select: EventHandler[no_args_event_spec] + + # Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside. + text_value: Var[str] + + class ContextMenu(ComponentNamespace): """Menu representing a set of actions, displayed at the origin of a pointer right-click or long-press.""" @@ -259,6 +333,10 @@ class ContextMenu(ComponentNamespace): item = staticmethod(ContextMenuItem.create) separator = staticmethod(ContextMenuSeparator.create) checkbox = staticmethod(ContextMenuCheckbox.create) + label = staticmethod(ContextMenuLabel.create) + group = staticmethod(ContextMenuGroup.create) + radio_group = staticmethod(ContextMenuRadioGroup.create) + radio = staticmethod(ContextMenuRadioItem.create) context_menu = ContextMenu() diff --git a/reflex/components/radix/themes/components/context_menu.pyi b/reflex/components/radix/themes/components/context_menu.pyi index 81ccb125b..34aa36f45 100644 --- a/reflex/components/radix/themes/components/context_menu.pyi +++ b/reflex/components/radix/themes/components/context_menu.pyi @@ -3,7 +3,7 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ -from typing import Any, Dict, Literal, Optional, Union, overload +from typing import Any, Dict, List, Literal, Optional, Union, overload from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Breakpoints @@ -13,6 +13,7 @@ from reflex.vars.base import Var from ..base import RadixThemesComponent from .checkbox import Checkbox +from .radio_group import HighLevelRadioGroup LiteralDirType = Literal["ltr", "rtl"] LiteralSizeType = Literal["1", "2"] @@ -820,6 +821,320 @@ class ContextMenuCheckbox(Checkbox): """ ... +class ContextMenuLabel(RadixThemesComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + as_child: Optional[Union[Var[bool], bool]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[()]] = None, + on_click: Optional[EventType[()]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_mount: Optional[EventType[()]] = None, + on_mouse_down: Optional[EventType[()]] = None, + on_mouse_enter: Optional[EventType[()]] = None, + on_mouse_leave: Optional[EventType[()]] = None, + on_mouse_move: Optional[EventType[()]] = None, + on_mouse_out: Optional[EventType[()]] = None, + on_mouse_over: Optional[EventType[()]] = None, + on_mouse_up: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "ContextMenuLabel": + """Create a new component instance. + + Will prepend "RadixThemes" to the component tag to avoid conflicts with + other UI libraries for common names, like Text and Button. + + Args: + *children: Child components. + as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: Component properties. + + Returns: + A new component instance. + """ + ... + +class ContextMenuGroup(RadixThemesComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + as_child: Optional[Union[Var[bool], bool]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[()]] = None, + on_click: Optional[EventType[()]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_mount: Optional[EventType[()]] = None, + on_mouse_down: Optional[EventType[()]] = None, + on_mouse_enter: Optional[EventType[()]] = None, + on_mouse_leave: Optional[EventType[()]] = None, + on_mouse_move: Optional[EventType[()]] = None, + on_mouse_out: Optional[EventType[()]] = None, + on_mouse_over: Optional[EventType[()]] = None, + on_mouse_up: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "ContextMenuGroup": + """Create a new component instance. + + Will prepend "RadixThemes" to the component tag to avoid conflicts with + other UI libraries for common names, like Text and Button. + + Args: + *children: Child components. + as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: Component properties. + + Returns: + A new component instance. + """ + ... + +class ContextMenuRadioGroup(RadixThemesComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + as_child: Optional[Union[Var[bool], bool]] = None, + value: Optional[Union[Var[str], str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[()]] = None, + on_change: Optional[Union[EventType[()], EventType[str]]] = None, + on_click: Optional[EventType[()]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_mount: Optional[EventType[()]] = None, + on_mouse_down: Optional[EventType[()]] = None, + on_mouse_enter: Optional[EventType[()]] = None, + on_mouse_leave: Optional[EventType[()]] = None, + on_mouse_move: Optional[EventType[()]] = None, + on_mouse_out: Optional[EventType[()]] = None, + on_mouse_over: Optional[EventType[()]] = None, + on_mouse_up: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "ContextMenuRadioGroup": + """Create a new component instance. + + Will prepend "RadixThemes" to the component tag to avoid conflicts with + other UI libraries for common names, like Text and Button. + + Args: + *children: Child components. + as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + value: The value of the selected item in the group. + on_change: Fired when the value of the radio group changes. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: Component properties. + + Returns: + A new component instance. + """ + ... + +class ContextMenuRadioItem(HighLevelRadioGroup): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + color_scheme: Optional[ + Union[ + Literal[ + "amber", + "blue", + "bronze", + "brown", + "crimson", + "cyan", + "gold", + "grass", + "gray", + "green", + "indigo", + "iris", + "jade", + "lime", + "mint", + "orange", + "pink", + "plum", + "purple", + "red", + "ruby", + "sky", + "teal", + "tomato", + "violet", + "yellow", + ], + Var[ + Literal[ + "amber", + "blue", + "bronze", + "brown", + "crimson", + "cyan", + "gold", + "grass", + "gray", + "green", + "indigo", + "iris", + "jade", + "lime", + "mint", + "orange", + "pink", + "plum", + "purple", + "red", + "ruby", + "sky", + "teal", + "tomato", + "violet", + "yellow", + ] + ], + ] + ] = None, + as_child: Optional[Union[Var[bool], bool]] = None, + value: Optional[Union[Var[str], str]] = None, + disabled: Optional[Union[Var[bool], bool]] = None, + text_value: Optional[Union[Var[str], str]] = None, + items: Optional[Union[List[str], Var[List[str]]]] = None, + direction: Optional[ + Union[ + Literal["column", "column-reverse", "row", "row-reverse"], + Var[Literal["column", "column-reverse", "row", "row-reverse"]], + ] + ] = None, + spacing: Optional[ + Union[ + Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + Var[Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]], + ] + ] = None, + size: Optional[ + Union[Literal["1", "2", "3"], Var[Literal["1", "2", "3"]]] + ] = None, + variant: Optional[ + Union[ + Literal["classic", "soft", "surface"], + Var[Literal["classic", "soft", "surface"]], + ] + ] = None, + high_contrast: Optional[Union[Var[bool], bool]] = None, + default_value: Optional[Union[Var[str], str]] = None, + name: Optional[Union[Var[str], str]] = None, + required: Optional[Union[Var[bool], bool]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[()]] = None, + on_click: Optional[EventType[()]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_mount: Optional[EventType[()]] = None, + on_mouse_down: Optional[EventType[()]] = None, + on_mouse_enter: Optional[EventType[()]] = None, + on_mouse_leave: Optional[EventType[()]] = None, + on_mouse_move: Optional[EventType[()]] = None, + on_mouse_out: Optional[EventType[()]] = None, + on_mouse_over: Optional[EventType[()]] = None, + on_mouse_up: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_select: Optional[EventType[()]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "ContextMenuRadioItem": + """Create a radio group component. + + Args: + items: The items of the radio group. + color_scheme: The color of the radio group + as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. + value: The controlled value of the radio item to check. Should be used in conjunction with on_change. + disabled: Whether the radio group is disabled + on_select: Event handler called when the user selects an item (via mouse or keyboard). Calling event.preventDefault in this handler will prevent the context menu from closing when selecting that item. + text_value: Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside. + items: The items of the radio group. + direction: The direction of the radio group. + spacing: The gap between the items of the radio group. + size: The size of the radio group. + variant: The variant of the radio group + high_contrast: Whether to render the radio group with higher contrast color against background + default_value: The initial value of checked radio item. Should be used in conjunction with on_change. + name: The name of the group. Submitted with its owning form as part of a name/value pair. + required: Whether the radio group is required + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: Additional properties to apply to the accordion item. + + Returns: + The created radio group component. + + Raises: + TypeError: If the type of items is invalid. + """ + ... + class ContextMenu(ComponentNamespace): root = staticmethod(ContextMenuRoot.create) trigger = staticmethod(ContextMenuTrigger.create) @@ -830,5 +1145,9 @@ class ContextMenu(ComponentNamespace): item = staticmethod(ContextMenuItem.create) separator = staticmethod(ContextMenuSeparator.create) checkbox = staticmethod(ContextMenuCheckbox.create) + label = staticmethod(ContextMenuLabel.create) + group = staticmethod(ContextMenuGroup.create) + radio_group = staticmethod(ContextMenuRadioGroup.create) + radio = staticmethod(ContextMenuRadioItem.create) context_menu = ContextMenu() diff --git a/reflex/event.py b/reflex/event.py index c2eb8db3a..6f97e1027 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -599,7 +599,6 @@ def no_args_event_spec() -> Tuple[()]: stop_propagation = EventChain(events=[], args_spec=no_args_event_spec).stop_propagation prevent_default = EventChain(events=[], args_spec=no_args_event_spec).prevent_default - T = TypeVar("T") U = TypeVar("U") @@ -1300,10 +1299,10 @@ def call_event_handler( compare_result = typehint_issubclass( args_types_without_vars[i], type_hints_of_provided_callback[arg] ) - except TypeError as te: + except TypeError as te: # codespell:ignore te raise TypeError( f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_callback.fn.__qualname__} provided for {key}." - ) from te + ) from te # codespell:ignore te if compare_result: type_match_found[arg] = True @@ -1887,7 +1886,6 @@ class LambdaEventCallback(Protocol[Unpack[P]]): ARGS = TypeVarTuple("ARGS") - LAMBDA_OR_STATE = TypeAliasType( "LAMBDA_OR_STATE", LambdaEventCallback[Unpack[ARGS]] | EventCallback[Unpack[ARGS]], @@ -1910,7 +1908,6 @@ EventType = TypeAliasType( "EventType", ItemOrList[IndividualEventType[Unpack[ARGS]]], type_params=(ARGS,) ) - if TYPE_CHECKING: from reflex.state import BaseState diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index ecfd52428..4212c1130 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -6,14 +6,15 @@ import asyncio import dataclasses import multiprocessing import platform +import sys import warnings from contextlib import suppress from reflex.config import environment -try: +if sys.version_info >= (3, 11): from datetime import UTC, datetime -except ImportError: +else: from datetime import datetime UTC = None diff --git a/reflex/utils/types.py b/reflex/utils/types.py index b432319e0..4f9563f90 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -516,10 +516,12 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None) # Check if the types match. try: return cls_check_base == Any or issubclass(cls_base, cls_check_base) - except TypeError as te: + except TypeError as te: # codespell:ignore te # These errors typically arise from bad annotations and are hard to # debug without knowing the type that we tried to compare. - raise TypeError(f"Invalid type for issubclass: {cls_base}") from te + raise TypeError( + f"Invalid type for issubclass: {cls_base}" + ) from te # codespell:ignore te def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool: diff --git a/reflex/vars/dep_tracking.py b/reflex/vars/dep_tracking.py index 0b2367799..197b125e4 100644 --- a/reflex/vars/dep_tracking.py +++ b/reflex/vars/dep_tracking.py @@ -239,8 +239,8 @@ class DependencyTracker: """ # Get the original source code and eval it to get the Var. module = inspect.getmodule(self.func) - positions0 = self._getting_var_instructions[0].positions - positions1 = self._getting_var_instructions[-1].positions + positions0 = self._getting_var_instructions[0].positions # type: ignore[attr-defined] + positions1 = self._getting_var_instructions[-1].positions # type: ignore[attr-defined] if module is None or positions0 is None or positions1 is None: raise VarValueError( f"Cannot determine the source code for the var in {self.func!r}."