Radix Accordion (#2310)

This commit is contained in:
Alek Petuskey 2024-01-18 14:20:21 -08:00 committed by GitHub
parent 6fcc4fd357
commit d466c2aaa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 677 additions and 102 deletions

View File

@ -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 }}):

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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.
"""
...

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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 <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, "(")
else:
# apply operator to left operand (<operator> left_operand)

View File

@ -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}"