Alek Petuskey 1 anno fa
parent
commit
d466c2aaa2

+ 1 - 1
reflex/.templates/jinja/web/pages/utils.js.jinja2

@@ -85,7 +85,7 @@
 {% macro render_match_tag(component) %}
 {% 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 case in component.match_cases %}
             {% for condition in case[:-1] %}
             {% for condition in case[:-1] %}
                 case JSON.stringify({{ condition._var_name_unwrapped }}):
                 case JSON.stringify({{ condition._var_name_unwrapped }}):

+ 3 - 1
reflex/components/component.py

@@ -623,6 +623,8 @@ class Component(BaseComponent, ABC):
         Returns:
         Returns:
             The dictionary of the component style as value and the style notation as key.
             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))}
         return {"css": Var.create(format_as_emotion(self.style))}
 
 
     def render(self) -> Dict:
     def render(self) -> Dict:
@@ -721,7 +723,7 @@ class Component(BaseComponent, ABC):
                 vars.append(prop_var)
                 vars.append(prop_var)
 
 
         # Style keeps track of its own VarData instance, so embed in a temp Var that is yielded.
         # 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(
             vars.append(
                 BaseVar(
                 BaseVar(
                     _var_name="style",
                     _var_name="style",

+ 8 - 4
reflex/components/core/match.py

@@ -64,7 +64,8 @@ class Match(MemoizationLeaf):
         Raises:
         Raises:
             ValueError: If the condition is not provided.
             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:
         if match_cond_var is None:
             raise ValueError("The condition must be set")
             raise ValueError("The condition must be set")
         return match_cond_var  # type: ignore
         return match_cond_var  # type: ignore
@@ -216,13 +217,14 @@ class Match(MemoizationLeaf):
 
 
         return match_cond_var._replace(
         return match_cond_var._replace(
             _var_name=format.format_match(
             _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
                 match_cases=match_cases,  # type: ignore
                 default=default,  # type: ignore
                 default=default,  # type: ignore
             ),
             ),
             _var_type=default._var_type,  # type: ignore
             _var_type=default._var_type,  # type: ignore
             _var_is_local=False,
             _var_is_local=False,
             _var_full_name_needs_state_prefix=False,
             _var_full_name_needs_state_prefix=False,
+            _var_is_string=False,
             merge_var_data=VarData.merge(*var_data),
             merge_var_data=VarData.merge(*var_data),
         )
         )
 
 
@@ -247,11 +249,13 @@ class Match(MemoizationLeaf):
         for case in self.match_cases:
         for case in self.match_cases:
             if isinstance(case[-1], BaseComponent):
             if isinstance(case[-1], BaseComponent):
                 merged_imports = imports.merge_imports(
                 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.
         # Get the import of the default case component.
         if isinstance(self.default, BaseComponent):
         if isinstance(self.default, BaseComponent):
             merged_imports = imports.merge_imports(
             merged_imports = imports.merge_imports(
-                merged_imports, self.default.get_imports()
+                merged_imports,
+                self.default.get_imports(),
             )
             )
         return merged_imports
         return merged_imports

+ 14 - 1
reflex/components/radix/primitives/__init__.py

@@ -1,6 +1,12 @@
 """Radix primitive components (https://www.radix-ui.com/primitives)."""
 """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 (
 from .form import (
     form_control,
     form_control,
     form_field,
     form_field,
@@ -12,3 +18,10 @@ from .form import (
 )
 )
 from .progress import progress
 from .progress import progress
 from .slider import slider
 from .slider import slider
+
+# accordion
+accordion = AccordionRoot.create
+accordion_root = AccordionRoot.create
+accordion_header = AccordionHeader.create
+accordion_trigger = AccordionTrigger.create
+accordion_content = AccordionContent.create

+ 425 - 69
reflex/components/radix/primitives/accordion.py

@@ -1,22 +1,370 @@
 """Radix accordion components."""
 """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.component import Component
+from reflex.components.core import cond, match
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.themes.components.icons import Icon
 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.utils import imports
-from reflex.vars import Var
+from reflex.vars import BaseVar, Var
 
 
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
-
+LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
+LiteralAccordionRootColorScheme = Literal["primary", "accent"]
 
 
 DEFAULT_ANIMATION_DURATION = 250
 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):
 class AccordionComponent(RadixPrimitiveComponent):
     """Base class for all @radix-ui/accordion components."""
     """Base class for all @radix-ui/accordion components."""
 
 
@@ -51,16 +399,79 @@ class AccordionRoot(AccordionComponent):
     # The orientation of the accordion.
     # The orientation of the accordion.
     orientation: Var[LiteralAccordionOrientation]
     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):
     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):
 class AccordionItem(AccordionComponent):
     """An accordion component."""
     """An accordion component."""
@@ -78,22 +489,6 @@ class AccordionItem(AccordionComponent):
     def _apply_theme(self, theme: Component):
     def _apply_theme(self, theme: Component):
         self.style = Style(
         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,
                 **self.style,
             }
             }
         )
         )
@@ -109,7 +504,6 @@ class AccordionHeader(AccordionComponent):
     def _apply_theme(self, theme: Component):
     def _apply_theme(self, theme: Component):
         self.style = Style(
         self.style = Style(
             {
             {
-                "display": "flex",
                 **self.style,
                 **self.style,
             }
             }
         )
         )
@@ -125,27 +519,6 @@ class AccordionTrigger(AccordionComponent):
     def _apply_theme(self, theme: Component):
     def _apply_theme(self, theme: Component):
         self.style = Style(
         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,
                 **self.style,
             }
             }
         )
         )
@@ -161,23 +534,6 @@ class AccordionContent(AccordionComponent):
     def _apply_theme(self, theme: Component):
     def _apply_theme(self, theme: Component):
         self.style = Style(
         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,
                 **self.style,
             }
             }
         )
         )
@@ -231,14 +587,14 @@ def accordion_item(header: Component, content: Component, **props) -> Component:
                     tag="chevron_down",
                     tag="chevron_down",
                     class_name="AccordionChevron",
                     class_name="AccordionChevron",
                 ),
                 ),
+                class_name="AccordionTrigger",
             ),
             ),
         ),
         ),
         AccordionContent.create(
         AccordionContent.create(
             content,
             content,
+            class_name="AccordionContent",
         ),
         ),
         value=value,
         value=value,
         **props,
         **props,
+        class_name="AccordionItem",
     )
     )
-
-
-accordion = AccordionRoot.create

+ 41 - 11
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.vars import Var, BaseVar, ComputedVar
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 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.component import Component
+from reflex.components.core import cond, match
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.themes.components.icons import Icon
 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.utils import imports
-from reflex.vars import Var
+from reflex.vars import BaseVar, Var
 
 
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
+LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
+LiteralAccordionRootColorScheme = Literal["primary", "accent"]
 DEFAULT_ANIMATION_DURATION = 250
 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):
 class AccordionComponent(RadixPrimitiveComponent):
     @overload
     @overload
     @classmethod
     @classmethod
@@ -121,6 +139,16 @@ class AccordionRoot(AccordionComponent):
                 Literal["vertical", "horizontal"],
                 Literal["vertical", "horizontal"],
             ]
             ]
         ] = None,
         ] = 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,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
         key: Optional[Any] = None,
@@ -173,9 +201,12 @@ class AccordionRoot(AccordionComponent):
         on_unmount: Optional[
         on_unmount: Optional[
             Union[EventHandler, EventSpec, list, function, BaseVar]
             Union[EventHandler, EventSpec, list, function, BaseVar]
         ] = None,
         ] = None,
+        on_value_change: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
         **props
         **props
     ) -> "AccordionRoot":
     ) -> "AccordionRoot":
-        """Create the component.
+        """Create the Accordion root component.
 
 
         Args:
         Args:
             *children: The children of the component.
             *children: The children of the component.
@@ -186,6 +217,9 @@ class AccordionRoot(AccordionComponent):
             disabled: Whether or not the accordion is disabled.
             disabled: Whether or not the accordion is disabled.
             dir: The reading direction of the accordion when applicable.
             dir: The reading direction of the accordion when applicable.
             orientation: The orientation of the accordion.
             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.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             style: The style of the component.
             key: A unique key for the component.
             key: A unique key for the component.
@@ -193,15 +227,13 @@ class AccordionRoot(AccordionComponent):
             class_name: The class name for the component.
             class_name: The class name for the component.
             autofocus: Whether the component should take the focus once the page is loaded
             autofocus: Whether the component should take the focus once the page is loaded
             custom_attrs: custom attribute
             custom_attrs: custom attribute
-            **props: The props of the component.
+            **props: The properties of the component.
 
 
         Returns:
         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):
 class AccordionItem(AccordionComponent):
     @overload
     @overload
@@ -532,5 +564,3 @@ class AccordionContent(AccordionComponent):
         ...
         ...
 
 
 def accordion_item(header: Component, content: Component, **props) -> Component: ...
 def accordion_item(header: Component, content: Component, **props) -> Component: ...
-
-accordion = AccordionRoot.create

+ 4 - 0
reflex/components/radix/primitives/base.py

@@ -15,6 +15,10 @@ class RadixPrimitiveComponent(Component):
 
 
     lib_dependencies: List[str] = ["@emotion/react@^11.11.1"]
     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:
     def _render(self) -> Tag:
         return (
         return (
             super()
             super()

+ 81 - 0
reflex/components/radix/primitives/base.pyi

@@ -93,3 +93,84 @@ class RadixPrimitiveComponent(Component):
             TypeError: If an invalid child is passed.
             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.
+        """
+        ...

+ 2 - 2
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.utils.format import format_event_chain, to_camel_case
 from reflex.vars import BaseVar, Var
 from reflex.vars import BaseVar, Var
 
 
-from .base import RadixPrimitiveComponent
+from .base import RadixPrimitiveComponentWithClassName
 
 
 FORM_DATA = Var.create("form_data")
 FORM_DATA = Var.create("form_data")
 HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
 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."""
     """Base class for all @radix-ui/react-form components."""
 
 
     library = "@radix-ui/react-form@^0.0.3"
     library = "@radix-ui/react-form@^0.0.3"

+ 2 - 2
reflex/components/radix/primitives/form.pyi

@@ -18,14 +18,14 @@ from reflex.event import EventChain
 from reflex.utils import imports
 from reflex.utils import imports
 from reflex.utils.format import format_event_chain, to_camel_case
 from reflex.utils.format import format_event_chain, to_camel_case
 from reflex.vars import BaseVar, Var
 from reflex.vars import BaseVar, Var
-from .base import RadixPrimitiveComponent
+from .base import RadixPrimitiveComponentWithClassName
 
 
 FORM_DATA = Var.create("form_data")
 FORM_DATA = Var.create("form_data")
 HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
 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    "
     "\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
     @overload
     @classmethod
     @classmethod
     def create(  # type: ignore
     def create(  # type: ignore

+ 2 - 2
reflex/components/radix/primitives/progress.py

@@ -3,12 +3,12 @@
 from typing import Optional
 from typing import Optional
 
 
 from reflex.components.component import Component
 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.style import Style
 from reflex.vars import Var
 from reflex.vars import Var
 
 
 
 
-class ProgressComponent(RadixPrimitiveComponent):
+class ProgressComponent(RadixPrimitiveComponentWithClassName):
     """A Progress component."""
     """A Progress component."""
 
 
     library = "@radix-ui/react-progress@^1.0.3"
     library = "@radix-ui/react-progress@^1.0.3"

+ 2 - 2
reflex/components/radix/primitives/progress.pyi

@@ -9,11 +9,11 @@ from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from reflex.style import Style
 from typing import Optional
 from typing import Optional
 from reflex.components.component import Component
 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.style import Style
 from reflex.vars import Var
 from reflex.vars import Var
 
 
-class ProgressComponent(RadixPrimitiveComponent):
+class ProgressComponent(RadixPrimitiveComponentWithClassName):
     @overload
     @overload
     @classmethod
     @classmethod
     def create(  # type: ignore
     def create(  # type: ignore

+ 2 - 2
reflex/components/radix/primitives/slider.py

@@ -3,7 +3,7 @@
 from typing import Any, Dict, Literal
 from typing import Any, Dict, Literal
 
 
 from reflex.components.component import Component
 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.style import Style
 from reflex.vars import Var
 from reflex.vars import Var
 
 
@@ -11,7 +11,7 @@ LiteralSliderOrientation = Literal["horizontal", "vertical"]
 LiteralSliderDir = Literal["ltr", "rtl"]
 LiteralSliderDir = Literal["ltr", "rtl"]
 
 
 
 
-class SliderComponent(RadixPrimitiveComponent):
+class SliderComponent(RadixPrimitiveComponentWithClassName):
     """Base class for all @radix-ui/react-slider components."""
     """Base class for all @radix-ui/react-slider components."""
 
 
     library = "@radix-ui/react-slider@^1.1.2"
     library = "@radix-ui/react-slider@^1.1.2"

+ 2 - 2
reflex/components/radix/primitives/slider.pyi

@@ -9,14 +9,14 @@ from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from reflex.style import Style
 from typing import Any, Dict, Literal
 from typing import Any, Dict, Literal
 from reflex.components.component import Component
 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.style import Style
 from reflex.vars import Var
 from reflex.vars import Var
 
 
 LiteralSliderOrientation = Literal["horizontal", "vertical"]
 LiteralSliderOrientation = Literal["horizontal", "vertical"]
 LiteralSliderDir = Literal["ltr", "rtl"]
 LiteralSliderDir = Literal["ltr", "rtl"]
 
 
-class SliderComponent(RadixPrimitiveComponent):
+class SliderComponent(RadixPrimitiveComponentWithClassName):
     @overload
     @overload
     @classmethod
     @classmethod
     def create(  # type: ignore
     def create(  # type: ignore

+ 1 - 0
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)}"
                 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["tag"] = format.to_title_case(props["tag"]) + "Icon"
+        props["alias"] = f"RadixThemes{props['tag']}"
         return super().create(*children, **props)
         return super().create(*children, **props)
 
 
 
 

+ 15 - 0
reflex/style.py

@@ -220,3 +220,18 @@ def format_as_emotion(style_dict: dict[str, Any]) -> dict[str, Any] | None:
             emotion_style[key] = value
             emotion_style[key] = value
     if emotion_style:
     if emotion_style:
         return 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))

+ 37 - 3
reflex/vars.py

@@ -421,6 +421,26 @@ class Var:
             and self._var_data == other._var_data
             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:
     def to_string(self, json: bool = True) -> Var:
         """Convert a var to a string.
         """Convert a var to a string.
 
 
@@ -677,6 +697,16 @@ class Var:
 
 
         left_operand, right_operand = (other, self) if flip else (self, other)
         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:
         if other is not None:
             # check if the operation between operands is valid.
             # check if the operation between operands is valid.
             if op and not self.is_valid_operation(
             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
                     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
             # apply function to operands
             if fn is not None:
             if fn is not None:
+
                 if invoke_fn:
                 if invoke_fn:
                     # invoke the function on left operand.
                     # 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:
                 else:
                     # pass the operands as arguments to the function.
                     # 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})"
                     operation_name = f"{fn}({operation_name})"
             else:
             else:
                 # apply operator to operands (left operand <operator> right_operand)
                 # apply operator to operands (left operand <operator> 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, "(")
                 operation_name = format.wrap(operation_name, "(")
         else:
         else:
             # apply operator to left operand (<operator> left_operand)
             # apply operator to left operand (<operator> left_operand)

+ 35 - 0
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) | v(2)) == "{(1 || 2)}"
     assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}"
     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({"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 (
     assert (
         str(BaseVar(_var_name="foo", _var_type=TestObj)._var_set_state("state").bar)
         str(BaseVar(_var_name="foo", _var_type=TestObj)._var_set_state("state").bar)
         == "{state.foo.bar}"
         == "{state.foo.bar}"