diff --git a/reflex/.templates/jinja/web/pages/utils.js.jinja2 b/reflex/.templates/jinja/web/pages/utils.js.jinja2 index 03890e9b6..8e9808fbb 100644 --- a/reflex/.templates/jinja/web/pages/utils.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/utils.js.jinja2 @@ -85,7 +85,7 @@ {% macro render_match_tag(component) %} { (() => { - switch (JSON.stringify({{ component.cond._var_full_name }})) { + switch (JSON.stringify({{ component.cond._var_name_unwrapped }})) { {% for case in component.match_cases %} {% for condition in case[:-1] %} case JSON.stringify({{ condition._var_name_unwrapped }}): diff --git a/reflex/components/component.py b/reflex/components/component.py index d9b4d7b5f..20f5a454c 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -623,6 +623,8 @@ class Component(BaseComponent, ABC): Returns: The dictionary of the component style as value and the style notation as key. """ + if isinstance(self.style, Var): + return {"css": self.style} return {"css": Var.create(format_as_emotion(self.style))} def render(self) -> Dict: @@ -721,7 +723,7 @@ class Component(BaseComponent, ABC): vars.append(prop_var) # Style keeps track of its own VarData instance, so embed in a temp Var that is yielded. - if self.style: + if isinstance(self.style, dict) and self.style or isinstance(self.style, Var): vars.append( BaseVar( _var_name="style", diff --git a/reflex/components/core/match.py b/reflex/components/core/match.py index efcbe97c0..ce7b23e5b 100644 --- a/reflex/components/core/match.py +++ b/reflex/components/core/match.py @@ -64,7 +64,8 @@ class Match(MemoizationLeaf): Raises: ValueError: If the condition is not provided. """ - match_cond_var = Var.create(cond) + match_cond_var = Var.create(cond, _var_is_string=type(cond) is str) + if match_cond_var is None: raise ValueError("The condition must be set") return match_cond_var # type: ignore @@ -216,13 +217,14 @@ class Match(MemoizationLeaf): return match_cond_var._replace( _var_name=format.format_match( - cond=match_cond_var._var_full_name, + cond=match_cond_var._var_name_unwrapped, match_cases=match_cases, # type: ignore default=default, # type: ignore ), _var_type=default._var_type, # type: ignore _var_is_local=False, _var_full_name_needs_state_prefix=False, + _var_is_string=False, merge_var_data=VarData.merge(*var_data), ) @@ -247,11 +249,13 @@ class Match(MemoizationLeaf): for case in self.match_cases: if isinstance(case[-1], BaseComponent): merged_imports = imports.merge_imports( - merged_imports, case[-1].get_imports() + merged_imports, + case[-1].get_imports(), ) # Get the import of the default case component. if isinstance(self.default, BaseComponent): merged_imports = imports.merge_imports( - merged_imports, self.default.get_imports() + merged_imports, + self.default.get_imports(), ) return merged_imports diff --git a/reflex/components/radix/primitives/__init__.py b/reflex/components/radix/primitives/__init__.py index f377c04dc..da5749f30 100644 --- a/reflex/components/radix/primitives/__init__.py +++ b/reflex/components/radix/primitives/__init__.py @@ -1,6 +1,12 @@ """Radix primitive components (https://www.radix-ui.com/primitives).""" -from .accordion import accordion, accordion_item +from .accordion import ( + AccordionContent, + AccordionHeader, + AccordionRoot, + AccordionTrigger, + accordion_item, +) from .form import ( form_control, form_field, @@ -12,3 +18,10 @@ from .form import ( ) from .progress import progress from .slider import slider + +# accordion +accordion = AccordionRoot.create +accordion_root = AccordionRoot.create +accordion_header = AccordionHeader.create +accordion_trigger = AccordionTrigger.create +accordion_content = AccordionContent.create diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py index 73bf8ff5a..a5f1b6c61 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/reflex/components/radix/primitives/accordion.py @@ -1,22 +1,370 @@ """Radix accordion components.""" -from typing import Literal +from typing import Any, Dict, Literal +from reflex.components.base.fragment import Fragment from reflex.components.component import Component +from reflex.components.core import cond, match from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.components.icons import Icon -from reflex.style import Style +from reflex.style import ( + Style, + convert_dict_to_style_and_format_emotion, + format_as_emotion, +) from reflex.utils import imports -from reflex.vars import Var +from reflex.vars import BaseVar, Var LiteralAccordionType = Literal["single", "multiple"] LiteralAccordionDir = Literal["ltr", "rtl"] LiteralAccordionOrientation = Literal["vertical", "horizontal"] - +LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"] +LiteralAccordionRootColorScheme = Literal["primary", "accent"] DEFAULT_ANIMATION_DURATION = 250 +def get_theme_accordion_root(variant: Var[str], color_scheme: Var[str]) -> BaseVar: + """Get the theme for the accordion root component. + + Args: + variant: The variant of the accordion. + color_scheme: The color of the accordion. + + Returns: + The theme for the accordion root component. + """ + return match( # type: ignore + variant, + ( + "soft", + convert_dict_to_style_and_format_emotion( + { + "border_radius": "6px", + "background_color": cond( + color_scheme == "primary", "var(--accent-3)", "var(--slate-3)" + ), + "box_shadow": "0 2px 10px var(--black-a1)", + } + ), + ), + ( + "outline", + convert_dict_to_style_and_format_emotion( + { + "border_radius": "6px", + "border": cond( + color_scheme == "primary", + "1px solid var(--accent-6)", + "1px solid var(--slate-6)", + ), + "box_shadow": "0 2px 10px var(--black-a1)", + } + ), + ), + ( + "surface", + convert_dict_to_style_and_format_emotion( + { + "border_radius": "6px", + "border": cond( + color_scheme == "primary", + "1px solid var(--accent-6)", + "1px solid var(--slate-6)", + ), + "background_color": cond( + color_scheme == "primary", "var(--accent-3)", "var(--slate-3)" + ), + "box_shadow": "0 2px 10px var(--black-a1)", + } + ), + ), + ( + "ghost", + convert_dict_to_style_and_format_emotion( + { + "border_radius": "6px", + "background_color": "none", + "box_shadow": "None", + } + ), + ), + convert_dict_to_style_and_format_emotion( + { + "border_radius": "6px", + "background_color": cond( + color_scheme == "primary", "var(--accent-9)", "var(--slate-9)" + ), + "box_shadow": "0 2px 10px var(--black-a4)", + } + ) + # defaults to classic + ) + + +def get_theme_accordion_item(): + """Get the theme for the accordion item component. + + Returns: + The theme for the accordion item component. + """ + return convert_dict_to_style_and_format_emotion( + { + "overflow": "hidden", + "width": "100%", + "margin_top": "1px", + # "background_color": "var(--accent-3)", + # "background_color": cond( + # color_scheme == "primary", "var(--accent-3)", "var(--slate-3)" + # ), + "&:first-child": { + "margin_top": 0, + "border_top_left_radius": "4px", + "border_top_right_radius": "4px", + }, + "&:last-child": { + "border_bottom_left_radius": "4px", + "border_bottom_right_radius": "4px", + }, + "&:focus-within": { + "position": "relative", + "z_index": 1, + }, + } + ) + + +def get_theme_accordion_header() -> dict[str, str]: + """Get the theme for the accordion header component. + + Returns: + The theme for the accordion header component. + """ + return { + "display": "flex", + } + + +def get_theme_accordion_trigger(variant: str | Var, color_scheme: str | Var) -> BaseVar: + """Get the theme for the accordion trigger component. + + Args: + variant: The variant of the accordion. + color_scheme: The color of the accordion. + + Returns: + The theme for the accordion trigger component. + """ + return match( # type: ignore + variant, + ( + "soft", + convert_dict_to_style_and_format_emotion( + { + "color": cond( + color_scheme == "primary", + "var(--accent-9-contrast)", + "var(--slate-9-contrast)", + ), + "&:hover": { + "background_color": cond( + color_scheme == "primary", + "var(--accent-4)", + "var(--slate-4)", + ), + }, + "& > .AccordionChevron": { + "color": cond( + color_scheme == "primary", + "var(--accent-11)", + "var(--slate-11)", + ), + "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + }, + "&[data-state='open'] > .AccordionChevron": { + "transform": "rotate(180deg)", + }, + "font_family": "inherit", + "width": "100%", + "padding": "0 20px", + "height": "45px", + "flex": 1, + "display": "flex", + "align_items": "center", + "justify_content": "space-between", + "font_size": "15px", + "box_shadow": "0 1px 0 var(--accent-6)", + "line_height": 1, + } + ), + ), + ( + "outline", + "surface", + "ghost", + convert_dict_to_style_and_format_emotion( + { + "color": cond( + color_scheme == "primary", + "var(--accent-11)", + "var(--slate-11)", + ), + "&:hover": { + "background_color": cond( + color_scheme == "primary", + "var(--accent-4)", + "var(--slate-4)", + ), + }, + "& > .AccordionChevron": { + "color": cond( + color_scheme == "primary", + "var(--accent-11)", + "var(--slate-11)", + ), + "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + }, + "&[data-state='open'] > .AccordionChevron": { + "transform": "rotate(180deg)", + }, + "font_family": "inherit", + "width": "100%", + "padding": "0 20px", + "height": "45px", + "flex": 1, + "display": "flex", + "align_items": "center", + "justify_content": "space-between", + "font_size": "15px", + "box_shadow": "0 1px 0 var(--accent-6)", + "line_height": 1, + } + ), + ), + # defaults to classic + convert_dict_to_style_and_format_emotion( + { + "color": cond( + color_scheme == "primary", + "var(--accent-9-contrast)", + "var(--slate-9-contrast)", + ), + "box_shadow": "0 1px 0 var(--accent-6)", + "&:hover": { + "background_color": cond( + color_scheme == "primary", "var(--accent-10)", "var(--slate-10)" + ), + }, + "& > .AccordionChevron": { + "color": cond( + color_scheme == "primary", + "var(--accent-9-contrast)", + "var(--slate-9-contrast)", + ), + "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + }, + "&[data-state='open'] > .AccordionChevron": { + "transform": "rotate(180deg)", + }, + "font_family": "inherit", + "width": "100%", + "padding": "0 20px", + "height": "45px", + "flex": 1, + "display": "flex", + "align_items": "center", + "justify_content": "space-between", + "font_size": "15px", + "line_height": 1, + } + ), + ) + + +def get_theme_accordion_content(variant: str | Var, color_scheme: str | Var) -> BaseVar: + """Get the theme for the accordion content component. + + Args: + variant: The variant of the accordion. + color_scheme: The color of the accordion. + + Returns: + The theme for the accordion content component. + """ + return match( # type: ignore + variant, + ( + "outline", + "ghost", + convert_dict_to_style_and_format_emotion( + { + "overflow": "hidden", + "font_size": "10px", + "color": cond( + color_scheme == "primary", + "var(--accent-9-contrast)", + "var(--slate-9-contrast)", + ), + "background_color": cond( + color_scheme == "primary", "var(--accent-3)", "var(--slate-3)" + ), + "padding": "15px, 20px", + "&[data-state='open']": { + "animation": Var.create( + f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + _var_is_string=True, + ), + }, + "&[data-state='closed']": { + "animation": Var.create( + f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + _var_is_string=True, + ), + }, + } + ), + ), + convert_dict_to_style_and_format_emotion( + { + "overflow": "hidden", + "font_size": "10px", + "color": cond( + color_scheme == "primary", + "var(--accent-9-contrast)", + "var(--slate-9-contrast)", + ), + "background_color": match( + variant, + ( + "classic", + cond( + color_scheme == "primary", + "var(--accent-9)", + "var(--slate-9)", + ), + ), + cond( + color_scheme == "primary", "var(--accent-3)", "var(--slate-3)" + ), + ), + "padding": "15px, 20px", + "&[data-state='open']": { + "animation": Var.create( + f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + _var_is_string=True, + ), + }, + "&[data-state='closed']": { + "animation": Var.create( + f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", + _var_is_string=True, + ), + }, + } + ), + ) + + class AccordionComponent(RadixPrimitiveComponent): """Base class for all @radix-ui/accordion components.""" @@ -51,16 +399,79 @@ class AccordionRoot(AccordionComponent): # The orientation of the accordion. orientation: Var[LiteralAccordionOrientation] + # The variant of the accordion. + variant: Var[LiteralAccordionRootVariant] = "classic" # type: ignore + + # The color scheme of the accordion. + color_scheme: Var[LiteralAccordionRootColorScheme] = "primary" # type: ignore + + # dynamic themes of the accordion generated at compile time. + _dynamic_themes: Var[dict] + + @classmethod + def create(cls, *children, **props) -> Component: + """Create the Accordion root component. + + Args: + *children: The children of the component. + **props: The properties of the component. + + Returns: + The Accordion root Component. + """ + comp = super().create(*children, **props) + + if not comp.color_scheme._var_state: # type: ignore + # mark the vars of color string literals as strings so they can be formatted properly when performing a var operation. + comp.color_scheme._var_is_string = True # type: ignore + + if not comp.variant._var_state: # type: ignore + # mark the vars of variant string literals as strings so they are formatted properly in the match condition. + comp.variant._var_is_string = True # type: ignore + + # remove Fragment and cond wrap workaround when https://github.com/reflex-dev/reflex/issues/2393 is resolved. + return Fragment.create(comp, cond(True, Fragment.create())) + + def _get_style(self) -> dict: + """Get the style for the component. + + Returns: + The dictionary of the component style as value and the style notation as key. + """ + return {"css": self._dynamic_themes._merge(format_as_emotion(self.style))} # type: ignore + def _apply_theme(self, theme: Component): - self.style = Style( - { - "border_radius": "6px", - "background_color": "var(--accent-6)", - "box_shadow": "0 2px 10px var(--black-a4)", - **self.style, - } + + self._dynamic_themes = Var.create( # type: ignore + convert_dict_to_style_and_format_emotion( + { + "& .AccordionItem": get_theme_accordion_item(), + "& .AccordionHeader": get_theme_accordion_header(), + "& .AccordionTrigger": get_theme_accordion_trigger( + variant=self.variant, color_scheme=self.color_scheme + ), + "& .AccordionContent": get_theme_accordion_content( + variant=self.variant, color_scheme=self.color_scheme + ), + } + ) + )._merge( # type: ignore + get_theme_accordion_root( + variant=self.variant, color_scheme=self.color_scheme + ) ) + def get_event_triggers(self) -> Dict[str, Any]: + """Get the events triggers signatures for the component. + + Returns: + The signatures of the event triggers. + """ + return { + **super().get_event_triggers(), + "on_value_change": lambda e0: [e0], + } + class AccordionItem(AccordionComponent): """An accordion component.""" @@ -78,22 +489,6 @@ class AccordionItem(AccordionComponent): def _apply_theme(self, theme: Component): self.style = Style( { - "overflow": "hidden", - "margin_top": "1px", - "&:first-child": { - "margin_top": 0, - "border_top_left_radius": "4px", - "border_top_right_radius": "4px", - }, - "&:last-child": { - "border_bottom_left_radius": "4px", - "border_bottom_right_radius": "4px", - }, - "&:focus-within": { - "position": "relative", - "z_index": 1, - "box_shadow": "0 0 0 2px var(--accent-7)", - }, **self.style, } ) @@ -109,7 +504,6 @@ class AccordionHeader(AccordionComponent): def _apply_theme(self, theme: Component): self.style = Style( { - "display": "flex", **self.style, } ) @@ -125,27 +519,6 @@ class AccordionTrigger(AccordionComponent): def _apply_theme(self, theme: Component): self.style = Style( { - "font_family": "inherit", - "padding": "0 20px", - "height": "45px", - "flex": 1, - "display": "flex", - "align_items": "center", - "justify_content": "space-between", - "font_size": "15px", - "line_height": 1, - "color": "var(--accent-11)", - "box_shadow": "0 1px 0 var(--accent-6)", - "&:hover": { - "background_color": "var(--gray-2)", - }, - "& > .AccordionChevron": { - "color": "var(--accent-10)", - "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", - }, - "&[data-state='open'] > .AccordionChevron": { - "transform": "rotate(180deg)", - }, **self.style, } ) @@ -161,23 +534,6 @@ class AccordionContent(AccordionComponent): def _apply_theme(self, theme: Component): self.style = Style( { - "overflow": "hidden", - "fontSize": "15px", - "color": "var(--accent-11)", - "backgroundColor": "var(--accent-2)", - "padding": "15px, 20px", - "&[data-state='open']": { - "animation": Var.create( - f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", - _var_is_string=True, - ), - }, - "&[data-state='closed']": { - "animation": Var.create( - f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)", - _var_is_string=True, - ), - }, **self.style, } ) @@ -231,14 +587,14 @@ def accordion_item(header: Component, content: Component, **props) -> Component: tag="chevron_down", class_name="AccordionChevron", ), + class_name="AccordionTrigger", ), ), AccordionContent.create( content, + class_name="AccordionContent", ), value=value, **props, + class_name="AccordionItem", ) - - -accordion = AccordionRoot.create diff --git a/reflex/components/radix/primitives/accordion.pyi b/reflex/components/radix/primitives/accordion.pyi index ca1c660d7..7dc348827 100644 --- a/reflex/components/radix/primitives/accordion.pyi +++ b/reflex/components/radix/primitives/accordion.pyi @@ -7,19 +7,37 @@ from typing import Any, Dict, Literal, Optional, Union, overload from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style -from typing import Literal +from typing import Any, Dict, Literal +from reflex.components.base.fragment import Fragment from reflex.components.component import Component +from reflex.components.core import cond, match from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.components.icons import Icon -from reflex.style import Style +from reflex.style import ( + Style, + convert_dict_to_style_and_format_emotion, + format_as_emotion, +) from reflex.utils import imports -from reflex.vars import Var +from reflex.vars import BaseVar, Var LiteralAccordionType = Literal["single", "multiple"] LiteralAccordionDir = Literal["ltr", "rtl"] LiteralAccordionOrientation = Literal["vertical", "horizontal"] +LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"] +LiteralAccordionRootColorScheme = Literal["primary", "accent"] DEFAULT_ANIMATION_DURATION = 250 +def get_theme_accordion_root(variant: Var[str], color_scheme: Var[str]) -> BaseVar: ... +def get_theme_accordion_item(): ... +def get_theme_accordion_header() -> dict[str, str]: ... +def get_theme_accordion_trigger( + variant: str | Var, color_scheme: str | Var +) -> BaseVar: ... +def get_theme_accordion_content( + variant: str | Var, color_scheme: str | Var +) -> BaseVar: ... + class AccordionComponent(RadixPrimitiveComponent): @overload @classmethod @@ -121,6 +139,16 @@ class AccordionRoot(AccordionComponent): Literal["vertical", "horizontal"], ] ] = None, + variant: Optional[ + Union[ + Var[Literal["classic", "soft", "surface", "outline", "ghost"]], + Literal["classic", "soft", "surface", "outline", "ghost"], + ] + ] = None, + color_scheme: Optional[ + Union[Var[Literal["primary", "accent"]], Literal["primary", "accent"]] + ] = None, + _dynamic_themes: Optional[Union[Var[dict], dict]] = None, as_child: Optional[Union[Var[bool], bool]] = None, style: Optional[Style] = None, key: Optional[Any] = None, @@ -173,9 +201,12 @@ class AccordionRoot(AccordionComponent): on_unmount: Optional[ Union[EventHandler, EventSpec, list, function, BaseVar] ] = None, + on_value_change: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, **props ) -> "AccordionRoot": - """Create the component. + """Create the Accordion root component. Args: *children: The children of the component. @@ -186,6 +217,9 @@ class AccordionRoot(AccordionComponent): disabled: Whether or not the accordion is disabled. dir: The reading direction of the accordion when applicable. orientation: The orientation of the accordion. + variant: The variant of the accordion. + color_scheme: The color scheme of the accordion. + _dynamic_themes: dynamic themes of the accordion generated at compile time. as_child: Change the default rendered element for the one passed as a child. style: The style of the component. key: A unique key for the component. @@ -193,15 +227,13 @@ class AccordionRoot(AccordionComponent): 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: The props of the component. + **props: The properties of the component. Returns: - The component. - - Raises: - TypeError: If an invalid child is passed. + The Accordion root Component. """ ... + def get_event_triggers(self) -> Dict[str, Any]: ... class AccordionItem(AccordionComponent): @overload @@ -532,5 +564,3 @@ class AccordionContent(AccordionComponent): ... def accordion_item(header: Component, content: Component, **props) -> Component: ... - -accordion = AccordionRoot.create diff --git a/reflex/components/radix/primitives/base.py b/reflex/components/radix/primitives/base.py index 6b6f0fa11..bd1690c3b 100644 --- a/reflex/components/radix/primitives/base.py +++ b/reflex/components/radix/primitives/base.py @@ -15,6 +15,10 @@ class RadixPrimitiveComponent(Component): lib_dependencies: List[str] = ["@emotion/react@^11.11.1"] + +class RadixPrimitiveComponentWithClassName(RadixPrimitiveComponent): + """Basic component for radix Primitives with a class name prop.""" + def _render(self) -> Tag: return ( super() diff --git a/reflex/components/radix/primitives/base.pyi b/reflex/components/radix/primitives/base.pyi index 43a316fe9..f31a66252 100644 --- a/reflex/components/radix/primitives/base.pyi +++ b/reflex/components/radix/primitives/base.pyi @@ -93,3 +93,84 @@ class RadixPrimitiveComponent(Component): TypeError: If an invalid child is passed. """ ... + +class RadixPrimitiveComponentWithClassName(RadixPrimitiveComponent): + @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, str]]] = None, + on_blur: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "RadixPrimitiveComponentWithClassName": + """Create the component. + + Args: + *children: The children of the component. + as_child: Change the default rendered element for the one passed as a child. + 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: The props of the component. + + Returns: + The component. + + Raises: + TypeError: If an invalid child is passed. + """ + ... diff --git a/reflex/components/radix/primitives/form.py b/reflex/components/radix/primitives/form.py index df3213baa..0d5818d91 100644 --- a/reflex/components/radix/primitives/form.py +++ b/reflex/components/radix/primitives/form.py @@ -14,7 +14,7 @@ from reflex.utils import imports from reflex.utils.format import format_event_chain, to_camel_case from reflex.vars import BaseVar, Var -from .base import RadixPrimitiveComponent +from .base import RadixPrimitiveComponentWithClassName FORM_DATA = Var.create("form_data") HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string( @@ -34,7 +34,7 @@ HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string( ) -class FormComponent(RadixPrimitiveComponent): +class FormComponent(RadixPrimitiveComponentWithClassName): """Base class for all @radix-ui/react-form components.""" library = "@radix-ui/react-form@^0.0.3" diff --git a/reflex/components/radix/primitives/form.pyi b/reflex/components/radix/primitives/form.pyi index 060661fc6..208aab410 100644 --- a/reflex/components/radix/primitives/form.pyi +++ b/reflex/components/radix/primitives/form.pyi @@ -18,14 +18,14 @@ from reflex.event import EventChain from reflex.utils import imports from reflex.utils.format import format_event_chain, to_camel_case from reflex.vars import BaseVar, Var -from .base import RadixPrimitiveComponent +from .base import RadixPrimitiveComponentWithClassName FORM_DATA = Var.create("form_data") HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string( "\n const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {\n const $form = ev.target\n ev.preventDefault()\n const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}\n\n {{ on_submit_event_chain }}\n\n if ({{ reset_on_submit }}) {\n $form.reset()\n }\n })\n " ) -class FormComponent(RadixPrimitiveComponent): +class FormComponent(RadixPrimitiveComponentWithClassName): @overload @classmethod def create( # type: ignore diff --git a/reflex/components/radix/primitives/progress.py b/reflex/components/radix/primitives/progress.py index 06a8294dd..eba6e988d 100644 --- a/reflex/components/radix/primitives/progress.py +++ b/reflex/components/radix/primitives/progress.py @@ -3,12 +3,12 @@ from typing import Optional from reflex.components.component import Component -from reflex.components.radix.primitives.base import RadixPrimitiveComponent +from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.style import Style from reflex.vars import Var -class ProgressComponent(RadixPrimitiveComponent): +class ProgressComponent(RadixPrimitiveComponentWithClassName): """A Progress component.""" library = "@radix-ui/react-progress@^1.0.3" diff --git a/reflex/components/radix/primitives/progress.pyi b/reflex/components/radix/primitives/progress.pyi index 4742b7cfe..4d4544ecc 100644 --- a/reflex/components/radix/primitives/progress.pyi +++ b/reflex/components/radix/primitives/progress.pyi @@ -9,11 +9,11 @@ from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from typing import Optional from reflex.components.component import Component -from reflex.components.radix.primitives.base import RadixPrimitiveComponent +from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.style import Style from reflex.vars import Var -class ProgressComponent(RadixPrimitiveComponent): +class ProgressComponent(RadixPrimitiveComponentWithClassName): @overload @classmethod def create( # type: ignore diff --git a/reflex/components/radix/primitives/slider.py b/reflex/components/radix/primitives/slider.py index 42332bab9..e2bbd2eea 100644 --- a/reflex/components/radix/primitives/slider.py +++ b/reflex/components/radix/primitives/slider.py @@ -3,7 +3,7 @@ from typing import Any, Dict, Literal from reflex.components.component import Component -from reflex.components.radix.primitives.base import RadixPrimitiveComponent +from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.style import Style from reflex.vars import Var @@ -11,7 +11,7 @@ LiteralSliderOrientation = Literal["horizontal", "vertical"] LiteralSliderDir = Literal["ltr", "rtl"] -class SliderComponent(RadixPrimitiveComponent): +class SliderComponent(RadixPrimitiveComponentWithClassName): """Base class for all @radix-ui/react-slider components.""" library = "@radix-ui/react-slider@^1.1.2" diff --git a/reflex/components/radix/primitives/slider.pyi b/reflex/components/radix/primitives/slider.pyi index 52c0570c0..7c76f6989 100644 --- a/reflex/components/radix/primitives/slider.pyi +++ b/reflex/components/radix/primitives/slider.pyi @@ -9,14 +9,14 @@ from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from typing import Any, Dict, Literal from reflex.components.component import Component -from reflex.components.radix.primitives.base import RadixPrimitiveComponent +from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.style import Style from reflex.vars import Var LiteralSliderOrientation = Literal["horizontal", "vertical"] LiteralSliderDir = Literal["ltr", "rtl"] -class SliderComponent(RadixPrimitiveComponent): +class SliderComponent(RadixPrimitiveComponentWithClassName): @overload @classmethod def create( # type: ignore diff --git a/reflex/components/radix/themes/components/icons.py b/reflex/components/radix/themes/components/icons.py index 88530bbc6..b2e5dee97 100644 --- a/reflex/components/radix/themes/components/icons.py +++ b/reflex/components/radix/themes/components/icons.py @@ -46,6 +46,7 @@ class Icon(RadixIconComponent): f"Invalid icon tag: {props['tag']}. Please use one of the following: {sorted(ICON_LIST)}" ) props["tag"] = format.to_title_case(props["tag"]) + "Icon" + props["alias"] = f"RadixThemes{props['tag']}" return super().create(*children, **props) diff --git a/reflex/style.py b/reflex/style.py index 35591dc75..6ea000935 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -220,3 +220,18 @@ def format_as_emotion(style_dict: dict[str, Any]) -> dict[str, Any] | None: emotion_style[key] = value if emotion_style: return emotion_style + + +def convert_dict_to_style_and_format_emotion( + raw_dict: dict[str, Any] +) -> dict[str, Any] | None: + """Convert a dict to a style dict and then format as emotion. + + Args: + raw_dict: The dict to convert. + + Returns: + The emotion dict. + + """ + return format_as_emotion(Style(raw_dict)) diff --git a/reflex/vars.py b/reflex/vars.py index 04351609f..14ccf0792 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -421,6 +421,26 @@ class Var: and self._var_data == other._var_data ) + def _merge(self, other) -> Var: + """Merge two or more dicts. + + Args: + other: The other var to merge. + + Returns: + The merged var. + + Raises: + ValueError: If the other value to be merged is None. + """ + if other is None: + raise ValueError("The value to be merged cannot be None.") + if not isinstance(other, Var): + other = Var.create(other) + return self._replace( + _var_name=f"{{...{self._var_name}, ...{other._var_name}}}" # type: ignore + ) + def to_string(self, json: bool = True) -> Var: """Convert a var to a string. @@ -677,6 +697,16 @@ class Var: left_operand, right_operand = (other, self) if flip else (self, other) + def get_operand_full_name(operand): + # operand vars that are string literals need to be wrapped in back ticks. + return ( + operand._var_name_unwrapped + if operand._var_is_string + and not operand._var_state + and operand._var_is_local + else operand._var_full_name + ) + if other is not None: # check if the operation between operands is valid. if op and not self.is_valid_operation( @@ -688,18 +718,22 @@ class Var: f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}" # type: ignore ) + left_operand_full_name = get_operand_full_name(left_operand) + right_operand_full_name = get_operand_full_name(right_operand) + # apply function to operands if fn is not None: + if invoke_fn: # invoke the function on left operand. - operation_name = f"{left_operand._var_full_name}.{fn}({right_operand._var_full_name})" # type: ignore + operation_name = f"{left_operand_full_name}.{fn}({right_operand_full_name})" # type: ignore else: # pass the operands as arguments to the function. - operation_name = f"{left_operand._var_full_name} {op} {right_operand._var_full_name}" # type: ignore + operation_name = f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore operation_name = f"{fn}({operation_name})" else: # apply operator to operands (left operand right_operand) - operation_name = f"{left_operand._var_full_name} {op} {right_operand._var_full_name}" # type: ignore + operation_name = f"{left_operand_full_name} {op} {right_operand_full_name}" # type: ignore operation_name = format.wrap(operation_name, "(") else: # apply operator to left operand ( left_operand) diff --git a/tests/test_var.py b/tests/test_var.py index c5ba3dc3c..2441b13e9 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -262,6 +262,41 @@ def test_basic_operations(TestObj): assert str(v(1) | v(2)) == "{(1 || 2)}" assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}" assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}' + assert str(v("foo") == v("bar")) == '{("foo" === "bar")}' + assert ( + str( + Var.create("foo", _var_is_local=False) + == Var.create("bar", _var_is_local=False) + ) + == "{(foo === bar)}" + ) + assert ( + str( + BaseVar( + _var_name="foo", _var_type=str, _var_is_string=True, _var_is_local=True + ) + == BaseVar( + _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True + ) + ) + == "(`foo` === `bar`)" + ) + assert ( + str( + BaseVar( + _var_name="foo", + _var_type=TestObj, + _var_is_string=True, + _var_is_local=False, + ) + ._var_set_state("state") + .bar + == BaseVar( + _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True + ) + ) + == "{(state.foo.bar === `bar`)}" + ) assert ( str(BaseVar(_var_name="foo", _var_type=TestObj)._var_set_state("state").bar) == "{state.foo.bar}"