add_style api (#3202)

This commit is contained in:
Thomas Brandého 2024-05-07 01:59:11 +02:00 committed by GitHub
parent d68792f7b3
commit 57476966f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 885 additions and 618 deletions

View File

@ -833,9 +833,7 @@ class App(Base):
for _route, component in self.pages.items(): for _route, component in self.pages.items():
# Merge the component style with the app style. # Merge the component style with the app style.
component._add_style_recursive(self.style) component._add_style_recursive(self.style, self.theme)
component.apply_theme(self.theme)
# Add component._get_all_imports() to all_imports. # Add component._get_all_imports() to all_imports.
all_imports.update(component._get_all_imports()) all_imports.update(component._get_all_imports())

View File

@ -263,9 +263,18 @@ def _compile_stateful_components(
# Reset this flag to render the actual component. # Reset this flag to render the actual component.
component.rendered_as_shared = False component.rendered_as_shared = False
# Include dynamic imports in the shared component.
if dynamic_imports := component._get_all_dynamic_imports():
rendered_components.update(
{dynamic_import: None for dynamic_import in dynamic_imports}
)
# Include custom code in the shared component.
rendered_components.update( rendered_components.update(
{code: None for code in component._get_all_custom_code()}, {code: None for code in component._get_all_custom_code()},
) )
# Include all imports in the shared component.
all_import_dicts.append(component._get_all_imports()) all_import_dicts.append(component._get_all_imports())
# Indicate that this component now imports from the shared file. # Indicate that this component now imports from the shared file.

View File

@ -608,6 +608,8 @@ class Component(BaseComponent, ABC):
def _apply_theme(self, theme: Optional[Component]): def _apply_theme(self, theme: Optional[Component]):
"""Apply the theme to this component. """Apply the theme to this component.
Deprecated. Use add_style instead.
Args: Args:
theme: The theme to apply. theme: The theme to apply.
""" """
@ -779,44 +781,119 @@ class Component(BaseComponent, ABC):
return cls(children=children, **props) return cls(children=children, **props)
def _add_style(self, style: dict): def add_style(self) -> Style | None:
"""Add additional style to the component. """Add style to the component.
Downstream components can override this method to return a style dict
that will be applied to the component.
Returns:
The style to add.
"""
return None
def _add_style(self) -> Style:
"""Call add_style for all bases in the MRO.
Downstream components should NOT override. Use add_style instead.
Returns:
The style to add.
"""
styles = []
vars = []
# Walk the MRO to call all `add_style` methods.
for base in self._iter_parent_classes_with_method("add_style"):
s = base.add_style(self) # type: ignore
if s is not None:
styles.append(s)
vars.append(s._var_data)
_style = Style()
for s in reversed(styles):
_style.update(s)
_style._var_data = VarData.merge(*vars)
return _style
def _get_component_style(self, styles: ComponentStyle) -> Style | None:
"""Get the style to the component from `App.style`.
Args: Args:
style: A style dict to apply. styles: The style to apply.
"""
self.style.update(style)
def _add_style_recursive(self, style: ComponentStyle) -> Component: Returns:
The style of the component.
"""
component_style = None
if type(self) in styles:
component_style = Style(styles[type(self)])
if self.create in styles:
component_style = Style(styles[self.create])
return component_style
def _add_style_recursive(
self, style: ComponentStyle, theme: Optional[Component] = None
) -> Component:
"""Add additional style to the component and its children. """Add additional style to the component and its children.
Apply order is as follows (with the latest overriding the earliest):
1. Default style from `_add_style`/`add_style`.
2. User-defined style from `App.style`.
3. User-defined style from `Component.style`.
4. style dict and css props passed to the component instance.
Args: Args:
style: A dict from component to styling. style: A dict from component to styling.
theme: The theme to apply. (for retro-compatibility with deprecated _apply_theme API)
Raises:
UserWarning: If `_add_style` has been overridden.
Returns: Returns:
The component with the additional style. The component with the additional style.
""" """
component_style = None # 1. Default style from `_add_style`/`add_style`.
if type(self) in style: if type(self)._add_style != Component._add_style:
# Extract the style for this component. raise UserWarning(
component_style = Style(style[type(self)]) "Do not override _add_style directly. Use add_style instead."
if self.create in style: )
component_style = Style(style[self.create]) new_style = self._add_style()
if component_style is not None: style_vars = [new_style._var_data]
# Only add style props that are not overridden.
component_style = {
k: v for k, v in component_style.items() if k not in self.style
}
# Add the style to the component. # 2. User-defined style from `App.style`.
self._add_style(component_style) component_style = self._get_component_style(style)
if component_style:
new_style.update(component_style)
style_vars.append(component_style._var_data)
# 3. User-defined style from `Component.style`.
# Apply theme for retro-compatibility with deprecated _apply_theme API
if type(self)._apply_theme != Component._apply_theme:
console.deprecate(
f"{self.__class__.__name__}._apply_theme",
reason="use add_style instead",
deprecation_version="0.5.0",
removal_version="0.6.0",
)
self._apply_theme(theme)
# 4. style dict and css props passed to the component instance.
new_style.update(self.style)
style_vars.append(self.style._var_data)
new_style._var_data = VarData.merge(*style_vars)
# Assign the new style
self.style = new_style
# Recursively add style to the children. # Recursively add style to the children.
for child in self.children: for child in self.children:
# Skip BaseComponent and StatefulComponent children. # Skip BaseComponent and StatefulComponent children.
if not isinstance(child, Component): if not isinstance(child, Component):
continue continue
child._add_style_recursive(style) child._add_style_recursive(style, theme)
return self return self
def _get_style(self) -> dict: def _get_style(self) -> dict:

View File

@ -102,15 +102,6 @@ class Cond(MemoizationLeaf):
_IS_TRUE_IMPORT, _IS_TRUE_IMPORT,
) )
def _apply_theme(self, theme: Component):
"""Apply the theme to this component.
Args:
theme: The theme to apply.
"""
self.comp1.apply_theme(theme) # type: ignore
self.comp2.apply_theme(theme) # type: ignore
@overload @overload
def cond(condition: Any, c1: Component, c2: Any) -> Component: def cond(condition: Any, c1: Component, c2: Any) -> Component:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import inspect import inspect
from hashlib import md5 from hashlib import md5
from typing import Any, Callable, Iterable, Optional from typing import Any, Callable, Iterable
from reflex.components.base.fragment import Fragment from reflex.components.base.fragment import Fragment
from reflex.components.component import Component from reflex.components.component import Component
@ -23,17 +23,6 @@ class Foreach(Component):
# A function from the render args to the component. # A function from the render args to the component.
render_fn: Callable = Fragment.create render_fn: Callable = Fragment.create
# The theme if set.
theme: Optional[Component] = None
def _apply_theme(self, theme: Component):
"""Apply the theme to this component.
Args:
theme: The theme to apply.
"""
self.theme = theme
@classmethod @classmethod
def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach: def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach:
"""Create a foreach component. """Create a foreach component.
@ -97,10 +86,6 @@ class Foreach(Component):
tag = self._render() tag = self._render()
component = tag.render_component() component = tag.render_component()
# Apply the theme to the children.
if self.theme is not None:
component.apply_theme(self.theme)
return dict( return dict(
tag.add_props( tag.add_props(
**self.event_triggers, **self.event_triggers,

View File

@ -273,18 +273,3 @@ class Match(MemoizationLeaf):
super()._get_imports(), super()._get_imports(),
getattr(self.cond._var_data, "imports", {}), getattr(self.cond._var_data, "imports", {}),
) )
def _apply_theme(self, theme: Component):
"""Apply the theme to this component.
Args:
theme: The theme to apply.
"""
# apply theme to return components.
for match_case in self.match_cases:
if isinstance(match_case[-1], Component):
match_case[-1].apply_theme(theme)
# apply theme to default component
if isinstance(self.default, Component):
self.default.apply_theme(theme)

View File

@ -1,4 +1,6 @@
"""A code component.""" """A code component."""
from __future__ import annotations
import re import re
from typing import Dict, Literal, Optional, Union from typing import Dict, Literal, Optional, Union
@ -491,8 +493,9 @@ class CodeBlock(Component):
else: else:
return code_block return code_block
def _add_style(self, style): def add_style(self) -> Style | None:
self.custom_style.update(style) # type: ignore """Add style to the component."""
self.custom_style.update(self.style)
def _render(self): def _render(self):
out = super()._render() out = super()._render()

View File

@ -1111,5 +1111,6 @@ class CodeBlock(Component):
The text component. The text component.
""" """
... ...
def add_style(self) -> Style | None: ...
@staticmethod @staticmethod
def convert_theme_name(theme) -> str: ... def convert_theme_name(theme) -> str: ...

View File

@ -18,7 +18,6 @@ from reflex.components.radix.themes.typography.heading import Heading
from reflex.components.radix.themes.typography.link import Link from reflex.components.radix.themes.typography.link import Link
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
from reflex.components.tags.tag import Tag from reflex.components.tags.tag import Tag
from reflex.style import Style
from reflex.utils import console, imports, types from reflex.utils import console, imports, types
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import Var from reflex.vars import Var
@ -230,7 +229,8 @@ class Markdown(Component):
component = self.component_map[tag](*children, **props).set( component = self.component_map[tag](*children, **props).set(
special_props=special_props special_props=special_props
) )
component._add_style(Style(self.custom_styles.get(tag, {}))) component.style.update(self.custom_styles.get(tag, {}))
return component return component
def format_component(self, tag: str, **props) -> str: def format_component(self, tag: str, **props) -> str:

View File

@ -22,7 +22,6 @@ from reflex.components.radix.themes.typography.heading import Heading
from reflex.components.radix.themes.typography.link import Link from reflex.components.radix.themes.typography.link import Link
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
from reflex.components.tags.tag import Tag from reflex.components.tags.tag import Tag
from reflex.style import Style
from reflex.utils import console, imports, types from reflex.utils import console, imports, types
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import Var from reflex.vars import Var

View File

@ -5,303 +5,43 @@ from __future__ import annotations
from typing import Any, Dict, List, Literal, Optional, Union from typing import Any, Dict, List, Literal, Optional, Union
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import Component, ComponentNamespace
from reflex.components.core.match import Match from reflex.components.core.colors import color
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor from reflex.components.radix.themes.base import LiteralAccentColor
from reflex.style import ( from reflex.style import Style
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 BaseVar, Var, VarData, get_uuid_string_var from reflex.vars import Var, get_uuid_string_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"] LiteralAccordionVariant = 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.create( # type: ignore
variant,
(
"soft",
convert_dict_to_style_and_format_emotion(
{
"border_radius": "6px",
"background_color": f"var(--{color_scheme}-3)",
"box_shadow": "0 2px 10px var(--black-a1)",
}
),
),
(
"outline",
convert_dict_to_style_and_format_emotion(
{
"border_radius": "6px",
"border": f"1px solid var(--{color_scheme}-6)",
"box_shadow": "0 2px 10px var(--black-a1)",
}
),
),
(
"surface",
convert_dict_to_style_and_format_emotion(
{
"border_radius": "6px",
"border": f"1px solid var(--{color_scheme}-6)",
"background_color": f"var(--{color_scheme}-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": f"var(--{color_scheme}-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",
"&: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.create( # type: ignore
variant,
(
"soft",
convert_dict_to_style_and_format_emotion(
{
"color": f"var(--{color_scheme}-11)",
"&:hover": {
"background_color": f"var(--{color_scheme}-4)",
},
"& > .AccordionChevron": {
"color": f"var(--{color_scheme}-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",
"line_height": 1,
}
),
),
(
"outline",
"surface",
"ghost",
convert_dict_to_style_and_format_emotion(
{
"color": f"var(--{color_scheme}-11)",
"&:hover": {
"background_color": f"var(--{color_scheme}-4)",
},
"& > .AccordionChevron": {
"color": f"var(--{color_scheme}-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",
"line_height": 1,
}
),
),
# defaults to classic
convert_dict_to_style_and_format_emotion(
{
"color": f"var(--{color_scheme}-9-contrast)",
"box_shadow": f"var(--{color_scheme}-11)",
"&:hover": {
"background_color": f"var(--{color_scheme}-10)",
},
"& > .AccordionChevron": {
"color": f"var(--{color_scheme}-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.create( # type: ignore
variant,
(
"outline",
"ghost",
convert_dict_to_style_and_format_emotion(
{
"overflow": "hidden",
"font_size": "10px",
"color": f"var(--{color_scheme}-11)",
"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": Match.create(
variant,
("classic", f"var(--{color_scheme}-9-contrast)"),
f"var(--{color_scheme}-11)",
),
"background_color": Match.create(
variant,
("classic", f"var(--{color_scheme}-9)"),
f"var(--{color_scheme}-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."""
library = "@radix-ui/react-accordion@^1.1.2" library = "@radix-ui/react-accordion@^1.1.2"
# The color scheme of the component.
color_scheme: Var[LiteralAccentColor]
# The variant of the component.
variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
def add_style(self) -> Style | None:
"""Add style to the component."""
if self.color_scheme is not None:
self.custom_attrs["data-accent-color"] = self.color_scheme
self.custom_attrs["data-variant"] = self.variant
def _exclude_props(self) -> list[str]:
return ["color_scheme", "variant"]
class AccordionRoot(AccordionComponent): class AccordionRoot(AccordionComponent):
"""An accordion component.""" """An accordion component."""
@ -332,16 +72,7 @@ class AccordionRoot(AccordionComponent):
orientation: Var[LiteralAccordionOrientation] orientation: Var[LiteralAccordionOrientation]
# The variant of the accordion. # The variant of the accordion.
variant: Var[LiteralAccordionRootVariant] = "classic" # type: ignore variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
# The color scheme of the accordion.
color_scheme: Var[LiteralAccentColor] # type: ignore
# dynamic themes of the accordion generated at compile time.
_dynamic_themes: Var[dict] = Var.create({}) # type: ignore
# The var_data associated with the component.
_var_data: VarData = VarData() # type: ignore
_valid_children: List[str] = ["AccordionItem"] _valid_children: List[str] = ["AccordionItem"]
@ -356,81 +87,12 @@ class AccordionRoot(AccordionComponent):
Returns: Returns:
The Accordion root Component. The Accordion root Component.
""" """
comp = super().create(*children, **props) for child in children:
if isinstance(child, AccordionItem):
child.color_scheme = props.get("color_scheme") # type: ignore
child.variant = props.get("variant") # type: ignore
if comp.color_scheme is not None and not comp.color_scheme._var_state: # type: ignore return super().create(*children, **props)
# 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 comp.variant is not None and 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
return comp
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):
global_color_scheme = getattr(theme, "accent_color", None)
if global_color_scheme is None and self.color_scheme is None:
raise ValueError(
"`color_scheme` cannot be None. Either set the `color_scheme` prop on the accordion "
"component or set the `accent_color` prop in your global theme."
)
# prepare the color_scheme var to be used in an f-string(strip off the wrapping curly brace)
color_scheme = Var.create(
self.color_scheme if self.color_scheme is not None else global_color_scheme
)._replace( # type: ignore
_var_is_string=False
)
accordion_theme_root = get_theme_accordion_root(
variant=self.variant, color_scheme=color_scheme
)
accordion_theme_content = get_theme_accordion_content(
variant=self.variant, color_scheme=color_scheme
)
accordion_theme_trigger = get_theme_accordion_trigger(
variant=self.variant, color_scheme=color_scheme
)
# extract var_data from dynamic themes.
self._var_data = (
self._var_data.merge( # type: ignore
accordion_theme_trigger._var_data,
accordion_theme_content._var_data,
accordion_theme_root._var_data,
)
or self._var_data
)
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": accordion_theme_trigger,
"& .AccordionContent": accordion_theme_content,
}
)
)._merge( # type: ignore
accordion_theme_root
)
def _get_imports(self):
return imports.merge_imports(
super()._get_imports(),
self._var_data.imports if self._var_data else {},
{"@emotion/react": [imports.ImportVar(tag="keyframes")]},
)
def get_event_triggers(self) -> Dict[str, Any]: def get_event_triggers(self) -> Dict[str, Any]:
"""Get the events triggers signatures for the component. """Get the events triggers signatures for the component.
@ -443,28 +105,36 @@ class AccordionRoot(AccordionComponent):
"on_value_change": lambda e0: [e0], "on_value_change": lambda e0: [e0],
} }
def _get_custom_code(self) -> str: def add_style(self):
return """ """Add style to the component.
const slideDown = keyframes`
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
`
const slideUp = keyframes`
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
`
"""
def _exclude_props(self) -> list[str]: Returns:
return ["color_scheme", "variant"] The style of the component.
"""
return Style(
{
"border_radius": "6px",
"box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}",
"&[data-variant='classic']": {
"background_color": color("accent", 9),
"box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}",
},
"&[data-variant='soft']": {
"background_color": color("accent", 3),
},
"&[data-variant='outline']": {
"border": f"1px solid {color('accent', 6)}",
},
"&[data-variant='surface']": {
"border": f"1px solid {color('accent', 6)}",
"background_color": color("accent", 3),
},
"&[data-variant='ghost']": {
"background_color": "none",
"box_shadow": "None",
},
}
)
class AccordionItem(AccordionComponent): class AccordionItem(AccordionComponent):
@ -488,13 +158,6 @@ class AccordionItem(AccordionComponent):
_valid_parents: List[str] = ["AccordionRoot"] _valid_parents: List[str] = ["AccordionRoot"]
def _apply_theme(self, theme: Component):
self.style = Style(
{
**self.style,
}
)
@classmethod @classmethod
def create( def create(
cls, cls,
@ -506,9 +169,9 @@ class AccordionItem(AccordionComponent):
"""Create an accordion item. """Create an accordion item.
Args: Args:
*children: The list of children to use if header and content are not provided.
header: The header of the accordion item. header: The header of the accordion item.
content: The content of the accordion item. content: The content of the accordion item.
*children: The list of children to use if header and content are not provided.
**props: Additional properties to apply to the accordion item. **props: Additional properties to apply to the accordion item.
Returns: Returns:
@ -527,14 +190,55 @@ class AccordionItem(AccordionComponent):
AccordionHeader.create( AccordionHeader.create(
AccordionTrigger.create( AccordionTrigger.create(
header, header,
AccordionIcon.create(), AccordionIcon.create(
color_scheme=props.get("color_scheme"),
variant=props.get("variant"),
),
color_scheme=props.get("color_scheme"),
variant=props.get("variant"),
), ),
color_scheme=props.get("color_scheme"),
variant=props.get("variant"),
),
AccordionContent.create(
content, color_scheme=props.get("color_scheme")
), ),
AccordionContent.create(content),
] ]
return super().create(*children, value=value, **props, class_name=cls_name) return super().create(*children, value=value, **props, class_name=cls_name)
def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
for child in self.children:
if isinstance(child, (AccordionHeader, AccordionContent)):
child.color_scheme = self.color_scheme
child.variant = self.variant
return Style(
{
"overflow": "hidden",
"width": "100%",
"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,
},
}
)
class AccordionHeader(AccordionComponent): class AccordionHeader(AccordionComponent):
"""An accordion component.""" """An accordion component."""
@ -561,8 +265,21 @@ class AccordionHeader(AccordionComponent):
return super().create(*children, class_name=cls_name, **props) return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style({**self.style}) """Add style to the component.
Returns:
The style of the component.
"""
for child in self.children:
if isinstance(child, AccordionTrigger):
child.color_scheme = self.color_scheme
child.variant = self.variant
return Style({"display": "flex"})
cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
class AccordionTrigger(AccordionComponent): class AccordionTrigger(AccordionComponent):
@ -590,8 +307,52 @@ class AccordionTrigger(AccordionComponent):
return super().create(*children, class_name=cls_name, **props) return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style({**self.style}) """Add style to the component.
Returns:
The style of the component.
"""
for child in self.children:
if isinstance(child, AccordionIcon):
child.color_scheme = self.color_scheme
child.variant = self.variant
return Style(
{
"color": color("accent", 11),
"line_height": 1,
"font_size": "15px",
"justify_content": "space-between",
"align_items": "center",
"flex": 1,
"display": "flex",
"padding": "0 20px",
"height": "45px",
"font_family": "inherit",
"width": "100%",
"&[data-state='open'] > .AccordionChevron": {
"transform": "rotate(180deg)",
},
"&:hover": {
"background_color": color("accent", 4),
},
"& > .AccordionChevron": {
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
},
"&[data-variant='classic']": {
"color": color("accent", 12),
"box_shadow": color("accent", 11),
"&:hover": {
"background_color": color("accent", 10),
},
"& > .AccordionChevron": {
"color": color("accent", 12),
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
},
},
}
)
class AccordionIcon(Icon): class AccordionIcon(Icon):
@ -623,6 +384,14 @@ class AccordionContent(AccordionComponent):
alias = "RadixAccordionContent" alias = "RadixAccordionContent"
def add_imports(self) -> imports.ImportDict:
"""Add imports to the component.
Returns:
The imports of the component.
"""
return {"@emotion/react": [imports.ImportVar(tag="keyframes")]}
@classmethod @classmethod
def create(cls, *children, **props) -> Component: def create(cls, *children, **props) -> Component:
"""Create the Accordion content component. """Create the Accordion content component.
@ -641,14 +410,66 @@ class AccordionContent(AccordionComponent):
return super().create(*children, class_name=cls_name, **props) return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component): def add_custom_code(self) -> list[str]:
self.style = Style({**self.style}) """Add custom code to the component.
# def _get_imports(self): Returns:
# return { The custom code of the component.
# **super()._get_imports(), """
# "@emotion/react": [imports.ImportVar(tag="keyframes")], return [
# } """
const slideDown = keyframes`
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
`
const slideUp = keyframes`
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
`
"""
]
def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
slideDown = Var.create(
f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
_var_is_string=True,
)
slideUp = Var.create(
f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
_var_is_string=True,
)
return Style(
{
"overflow": "hidden",
"font_size": "10px",
"color": color("accent", 11),
"background_color": color("accent", 3),
"padding": "0 15px",
"&[data-state='open']": {"animation": slideDown},
"&[data-state='closed']": {"animation": slideUp},
"&[data-variant='classic']": {
"color": color("accent", 12),
"background_color": color("accent", 9),
},
"&[data-variant='outline']": {"background_color": "transparent"},
"&[data-variant='ghost']": {"background_color": "transparent"},
}
)
class Accordion(ComponentNamespace): class Accordion(ComponentNamespace):

View File

@ -9,41 +9,95 @@ from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style from reflex.style import Style
from typing import Any, Dict, List, Literal, Optional, Union from typing import Any, Dict, List, Literal, Optional, Union
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import Component, ComponentNamespace
from reflex.components.core.match import Match from reflex.components.core.colors import color
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor from reflex.components.radix.themes.base import LiteralAccentColor
from reflex.style import ( from reflex.style import Style
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 BaseVar, Var, VarData, get_uuid_string_var from reflex.vars import Var, get_uuid_string_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"] LiteralAccordionVariant = 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):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
cls, cls,
*children, *children,
color_scheme: Optional[
Union[
Var[
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
]
],
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
],
]
] = None,
variant: Optional[
Union[
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
Literal["classic", "soft", "surface", "outline", "ghost"],
]
] = 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,
@ -102,6 +156,8 @@ class AccordionComponent(RadixPrimitiveComponent):
Args: Args:
*children: The children of the component. *children: The children of the component.
color_scheme: The color scheme of the component.
variant: The variant of the component.
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.
@ -208,8 +264,6 @@ class AccordionRoot(AccordionComponent):
], ],
] ]
] = None, ] = None,
_dynamic_themes: Optional[Union[Var[dict], dict]] = None,
_var_data: Optional[VarData] = 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,
@ -278,10 +332,8 @@ 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. variant: The variant of the component.
color_scheme: The color scheme of the accordion. color_scheme: The color scheme of the component.
_dynamic_themes: dynamic themes of the accordion generated at compile time.
_var_data: The var_data associated with the component.
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.
@ -296,6 +348,7 @@ class AccordionRoot(AccordionComponent):
""" """
... ...
def get_event_triggers(self) -> Dict[str, Any]: ... def get_event_triggers(self) -> Dict[str, Any]: ...
def add_style(self): ...
class AccordionItem(AccordionComponent): class AccordionItem(AccordionComponent):
@overload @overload
@ -307,6 +360,74 @@ class AccordionItem(AccordionComponent):
content: Optional[Union[Component, Var]] = None, content: Optional[Union[Component, Var]] = None,
value: Optional[Union[Var[str], str]] = None, value: Optional[Union[Var[str], str]] = None,
disabled: Optional[Union[Var[bool], bool]] = None, disabled: Optional[Union[Var[bool], bool]] = None,
color_scheme: Optional[
Union[
Var[
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
]
],
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
],
]
] = None,
variant: Optional[
Union[
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
Literal["classic", "soft", "surface", "outline", "ghost"],
]
] = 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,
@ -364,11 +485,13 @@ class AccordionItem(AccordionComponent):
"""Create an accordion item. """Create an accordion item.
Args: Args:
*children: The list of children to use if header and content are not provided.
header: The header of the accordion item. header: The header of the accordion item.
content: The content of the accordion item. content: The content of the accordion item.
*children: The list of children to use if header and content are not provided.
value: A unique identifier for the item. value: A unique identifier for the item.
disabled: When true, prevents the user from interacting with the item. disabled: When true, prevents the user from interacting with the item.
color_scheme: The color scheme of the component.
variant: The variant of the component.
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.
@ -382,6 +505,7 @@ class AccordionItem(AccordionComponent):
The accordion item. The accordion item.
""" """
... ...
def add_style(self) -> Style | None: ...
class AccordionHeader(AccordionComponent): class AccordionHeader(AccordionComponent):
@overload @overload
@ -389,6 +513,74 @@ class AccordionHeader(AccordionComponent):
def create( # type: ignore def create( # type: ignore
cls, cls,
*children, *children,
color_scheme: Optional[
Union[
Var[
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
]
],
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
],
]
] = None,
variant: Optional[
Union[
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
Literal["classic", "soft", "surface", "outline", "ghost"],
]
] = 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,
@ -447,6 +639,8 @@ class AccordionHeader(AccordionComponent):
Args: Args:
*children: The children of the component. *children: The children of the component.
color_scheme: The color scheme of the component.
variant: The variant of the component.
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.
@ -460,6 +654,9 @@ class AccordionHeader(AccordionComponent):
The Accordion header Component. The Accordion header Component.
""" """
... ...
def add_style(self) -> Style | None: ...
cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
class AccordionTrigger(AccordionComponent): class AccordionTrigger(AccordionComponent):
@overload @overload
@ -467,6 +664,74 @@ class AccordionTrigger(AccordionComponent):
def create( # type: ignore def create( # type: ignore
cls, cls,
*children, *children,
color_scheme: Optional[
Union[
Var[
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
]
],
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
],
]
] = None,
variant: Optional[
Union[
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
Literal["classic", "soft", "surface", "outline", "ghost"],
]
] = 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,
@ -525,6 +790,8 @@ class AccordionTrigger(AccordionComponent):
Args: Args:
*children: The children of the component. *children: The children of the component.
color_scheme: The color scheme of the component.
variant: The variant of the component.
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.
@ -538,6 +805,7 @@ class AccordionTrigger(AccordionComponent):
The Accordion trigger Component. The Accordion trigger Component.
""" """
... ...
def add_style(self) -> Style | None: ...
class AccordionIcon(Icon): class AccordionIcon(Icon):
@overload @overload
@ -618,11 +886,80 @@ class AccordionIcon(Icon):
... ...
class AccordionContent(AccordionComponent): class AccordionContent(AccordionComponent):
def add_imports(self) -> imports.ImportDict: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
cls, cls,
*children, *children,
color_scheme: Optional[
Union[
Var[
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
]
],
Literal[
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"brown",
"orange",
"sky",
"mint",
"lime",
"yellow",
"amber",
"gold",
"bronze",
"gray",
],
]
] = None,
variant: Optional[
Union[
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
Literal["classic", "soft", "surface", "outline", "ghost"],
]
] = 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,
@ -681,6 +1018,8 @@ class AccordionContent(AccordionComponent):
Args: Args:
*children: The children of the component. *children: The children of the component.
color_scheme: The color scheme of the component.
variant: The variant of the component.
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.
@ -694,6 +1033,8 @@ class AccordionContent(AccordionComponent):
The Accordion content Component. The Accordion content Component.
""" """
... ...
def add_custom_code(self) -> list[str]: ...
def add_style(self) -> Style | None: ...
class Accordion(ComponentNamespace): class Accordion(ComponentNamespace):
content = staticmethod(AccordionContent.create) content = staticmethod(AccordionContent.create)

View File

@ -4,10 +4,11 @@ from __future__ import annotations
from typing import Any, Dict, Literal from typing import Any, Dict, Literal
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import ComponentNamespace
from reflex.components.el.elements.forms import Form as HTMLForm from reflex.components.el.elements.forms import Form as HTMLForm
from reflex.components.radix.themes.components.text_field import TextFieldRoot from reflex.components.radix.themes.components.text_field import TextFieldRoot
from reflex.constants.event import EventTriggers from reflex.constants.event import EventTriggers
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
from .base import RadixPrimitiveComponentWithClassName from .base import RadixPrimitiveComponentWithClassName
@ -37,11 +38,13 @@ class FormRoot(FormComponent, HTMLForm):
EventTriggers.ON_CLEAR_SERVER_ERRORS: lambda: [], EventTriggers.ON_CLEAR_SERVER_ERRORS: lambda: [],
} }
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
return { """Add style to the component.
"width": "260px",
**self.style, Returns:
} The style of the component.
"""
return Style({"width": "260px"})
class FormField(FormComponent): class FormField(FormComponent):
@ -57,12 +60,13 @@ class FormField(FormComponent):
# Flag to mark the form field as invalid, for server side validation. # Flag to mark the form field as invalid, for server side validation.
server_invalid: Var[bool] server_invalid: Var[bool]
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
return { """Add style to the component.
"display": "grid",
"margin_bottom": "10px", Returns:
**self.style, The style of the component.
} """
return Style({"display": "grid", "margin_bottom": "10px"})
class FormLabel(FormComponent): class FormLabel(FormComponent):
@ -72,13 +76,13 @@ class FormLabel(FormComponent):
alias = "RadixFormLabel" alias = "RadixFormLabel"
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
return { """Add style to the component.
"font_size": "15px",
"font_weight": "500", Returns:
"line_height": "35px", The style of the component.
**self.style, """
} return Style({"font_size": "15px", "font_weight": "500", "line_height": "35px"})
class FormControl(FormComponent): class FormControl(FormComponent):
@ -145,13 +149,13 @@ class FormMessage(FormComponent):
# Forces the message to be shown. This is useful when using server-side validation. # Forces the message to be shown. This is useful when using server-side validation.
force_match: Var[bool] force_match: Var[bool]
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
return { """Add style to the component.
"font_size": "13px",
"opacity": "0.8", Returns:
"color": "white", The style of the component.
**self.style, """
} return Style({"font_size": "13px", "opacity": "0.8", "color": "white"})
class FormValidityState(FormComponent): class FormValidityState(FormComponent):

View File

@ -8,10 +8,11 @@ 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 Any, Dict, Literal from typing import Any, Dict, Literal
from reflex.components.component import Component, ComponentNamespace from reflex.components.component import ComponentNamespace
from reflex.components.el.elements.forms import Form as HTMLForm from reflex.components.el.elements.forms import Form as HTMLForm
from reflex.components.radix.themes.components.text_field import TextFieldRoot from reflex.components.radix.themes.components.text_field import TextFieldRoot
from reflex.constants.event import EventTriggers from reflex.constants.event import EventTriggers
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
from .base import RadixPrimitiveComponentWithClassName from .base import RadixPrimitiveComponentWithClassName
@ -95,6 +96,7 @@ class FormComponent(RadixPrimitiveComponentWithClassName):
class FormRoot(FormComponent, HTMLForm): class FormRoot(FormComponent, HTMLForm):
def get_event_triggers(self) -> Dict[str, Any]: ... def get_event_triggers(self) -> Dict[str, Any]: ...
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -273,6 +275,7 @@ class FormRoot(FormComponent, HTMLForm):
... ...
class FormField(FormComponent): class FormField(FormComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -355,6 +358,7 @@ class FormField(FormComponent):
... ...
class FormLabel(FormComponent): class FormLabel(FormComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -528,6 +532,7 @@ LiteralMatcher = Literal[
] ]
class FormMessage(FormComponent): class FormMessage(FormComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore

View File

@ -28,20 +28,24 @@ class ProgressRoot(ProgressComponent):
# Override theme radius for progress bar: "none" | "small" | "medium" | "large" | "full" # Override theme radius for progress bar: "none" | "small" | "medium" | "large" | "full"
radius: Var[LiteralRadius] radius: Var[LiteralRadius]
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
if self.radius is not None: if self.radius is not None:
self.custom_attrs["data-radius"] = self.radius self.custom_attrs["data-radius"] = self.radius
self.style = Style( return Style(
{ {
"position": "relative", "position": "relative",
"overflow": "hidden", "overflow": "hidden",
"background": "var(--gray-a3)", "background": color("gray", 3, alpha=True),
"border_radius": "max(var(--radius-2), var(--radius-full))", "border_radius": "max(var(--radius-2), var(--radius-full))",
"width": "100%", "width": "100%",
"height": "20px", "height": "20px",
"boxShadow": "inset 0 0 0 1px var(--gray-a5)", "boxShadow": f"inset 0 0 0 1px {color('gray', 5, alpha=True)}",
**self.style,
} }
) )
@ -65,22 +69,26 @@ class ProgressIndicator(ProgressComponent):
# The color scheme of the progress indicator. # The color scheme of the progress indicator.
color_scheme: Var[LiteralAccentColor] color_scheme: Var[LiteralAccentColor]
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
if self.color_scheme is not None: if self.color_scheme is not None:
self.custom_attrs["data-accent-color"] = self.color_scheme self.custom_attrs["data-accent-color"] = self.color_scheme
self.style = Style( return Style(
{ {
"background_color": color("accent", 9), "background_color": color("accent", 9),
"width": "100%", "width": "100%",
"height": "100%", "height": "100%",
f"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear", "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
"&[data_state='loading']": { "&[data_state='loading']": {
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear", "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
}, },
"transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", # type: ignore "transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", # type: ignore
"boxShadow": "inset 0 0 0 1px var(--gray-a5)", "boxShadow": "inset 0 0 0 1px var(--gray-a5)",
**self.style,
} }
) )

View File

@ -95,6 +95,7 @@ class ProgressComponent(RadixPrimitiveComponentWithClassName):
... ...
class ProgressRoot(ProgressComponent): class ProgressRoot(ProgressComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -180,6 +181,7 @@ class ProgressRoot(ProgressComponent):
... ...
class ProgressIndicator(ProgressComponent): class ProgressIndicator(ProgressComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore

View File

@ -59,8 +59,13 @@ class SliderRoot(SliderComponent):
"on_value_commit": lambda e0: [e0], # trigger when thumb is released "on_value_commit": lambda e0: [e0], # trigger when thumb is released
} }
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"position": "relative", "position": "relative",
"display": "flex", "display": "flex",
@ -74,7 +79,6 @@ class SliderRoot(SliderComponent):
"width": "20px", "width": "20px",
"height": "100px", "height": "100px",
}, },
**self.style,
} }
) )
@ -85,18 +89,20 @@ class SliderTrack(SliderComponent):
tag = "Track" tag = "Track"
alias = "RadixSliderTrack" alias = "RadixSliderTrack"
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"position": "relative", "position": "relative",
"flex_grow": "1", "flex_grow": "1",
"background_color": "black", "background_color": "black",
"border_radius": "9999px", "border_radius": "9999px",
"height": "3px", "height": "3px",
"&[data-orientation='vertical']": { "&[data-orientation='vertical']": {"width": "3px"},
"width": "3px",
},
**self.style,
} }
) )
@ -107,16 +113,18 @@ class SliderRange(SliderComponent):
tag = "Range" tag = "Range"
alias = "RadixSliderRange" alias = "RadixSliderRange"
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"position": "absolute", "position": "absolute",
"background_color": "white", "background_color": "white",
"height": "100%", "height": "100%",
"&[data-orientation='vertical']": { "&[data-orientation='vertical']": {"width": "100%"},
"width": "100%",
},
**self.style,
} }
) )
@ -127,8 +135,13 @@ class SliderThumb(SliderComponent):
tag = "Thumb" tag = "Thumb"
alias = "RadixSliderThumb" alias = "RadixSliderThumb"
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"display": "block", "display": "block",
"width": "20px", "width": "20px",
@ -143,7 +156,6 @@ class SliderThumb(SliderComponent):
"outline": "none", "outline": "none",
"box_shadow": "0 0 0 4px gray", "box_shadow": "0 0 0 4px gray",
}, },
**self.style,
} }
) )

View File

@ -96,6 +96,7 @@ class SliderComponent(RadixPrimitiveComponentWithClassName):
class SliderRoot(SliderComponent): class SliderRoot(SliderComponent):
def get_event_triggers(self) -> Dict[str, Any]: ... def get_event_triggers(self) -> Dict[str, Any]: ...
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -196,6 +197,7 @@ class SliderRoot(SliderComponent):
... ...
class SliderTrack(SliderComponent): class SliderTrack(SliderComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -274,6 +276,7 @@ class SliderTrack(SliderComponent):
... ...
class SliderRange(SliderComponent): class SliderRange(SliderComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
@ -352,6 +355,7 @@ class SliderRange(SliderComponent):
... ...
class SliderThumb(SliderComponent): class SliderThumb(SliderComponent):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore

View File

@ -1,4 +1,5 @@
"""Interactive components provided by @radix-ui/themes.""" """Interactive components provided by @radix-ui/themes."""
from __future__ import annotations
from typing import Literal from typing import Literal
@ -6,6 +7,7 @@ from reflex import el
from reflex.components.component import Component from reflex.components.component import Component
from reflex.components.core.match import Match from reflex.components.core.match import Match
from reflex.components.lucide import Icon from reflex.components.lucide import Icon
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
from ..base import ( from ..base import (
@ -68,25 +70,25 @@ class IconButton(el.Button, RadixLoadingProp, RadixThemesComponent):
"IconButton requires a child icon. Pass a string as the first child or a rx.icon." "IconButton requires a child icon. Pass a string as the first child or a rx.icon."
) )
if "size" in props: if "size" in props:
RADIX_TO_LUCIDE_SIZE = {"1": "12px", "2": "24px", "3": "36px", "4": "48px"}
if isinstance(props["size"], str): if isinstance(props["size"], str):
RADIX_TO_LUCIDE_SIZE = {
"1": "12px",
"2": "24px",
"3": "36px",
"4": "48px",
}
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]] children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
else: else:
children[0].size = Match.create( children[0].size = Match.create(
props["size"], props["size"],
("1", "12px"), *[(size, px) for size, px in RADIX_TO_LUCIDE_SIZE.items()],
("2", "24px"),
("3", "36px"),
("4", "48px"),
"12px", "12px",
) )
props.setdefault("padding", "6px")
return super().create(*children, **props) return super().create(*children, **props)
def add_style(self):
"""Add style to the component.
Returns:
The style of the component.
"""
return Style({"padding": "6px"})
icon_button = IconButton.create icon_button = IconButton.create

View File

@ -12,6 +12,7 @@ from reflex import el
from reflex.components.component import Component from reflex.components.component import Component
from reflex.components.core.match import Match from reflex.components.core.match import Match
from reflex.components.lucide import Icon from reflex.components.lucide import Icon
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
from ..base import ( from ..base import (
LiteralAccentColor, LiteralAccentColor,
@ -280,5 +281,6 @@ class IconButton(el.Button, RadixLoadingProp, RadixThemesComponent):
The IconButton component. The IconButton component.
""" """
... ...
def add_style(self): ...
icon_button = IconButton.create icon_button = IconButton.create

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from reflex.components.component import Component from reflex.style import Style
from .flex import Flex from .flex import Flex
@ -10,8 +10,13 @@ from .flex import Flex
class Center(Flex): class Center(Flex):
"""A center component.""" """A center component."""
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style.update( """Add style that center the content.
Returns:
The style of the component.
"""
return Style(
{ {
"display": "flex", "display": "flex",
"align_items": "center", "align_items": "center",

View File

@ -7,10 +7,11 @@ 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 reflex.components.component import Component from reflex.style import Style
from .flex import Flex from .flex import Flex
class Center(Flex): class Center(Flex):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore

View File

@ -1,4 +1,5 @@
"""List components.""" """List components."""
from __future__ import annotations
from typing import Iterable, Literal, Optional, Union from typing import Iterable, Literal, Optional, Union
@ -77,12 +78,16 @@ class BaseList(Component):
style["gap"] = props["gap"] style["gap"] = props["gap"]
return super().create(*children, **props) return super().create(*children, **props)
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style = Style( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"direction": "column", "direction": "column",
"list_style_position": "inside", "list_style_position": "inside",
**self.style,
} }
) )

View File

@ -157,6 +157,7 @@ class BaseList(Component):
""" """
... ...
def add_style(self) -> Style | None: ...
class UnorderedList(BaseList, Ul): class UnorderedList(BaseList, Ul):
@overload @overload
@ -165,7 +166,7 @@ class UnorderedList(BaseList, Ul):
cls, cls,
*children, *children,
items: Optional[Union[Var[Iterable], Iterable]] = None, items: Optional[Union[Var[Iterable], Iterable]] = None,
list_style_type: Optional[Literal["none", "disc", "circle", "square"]] = "disc", list_style_type: Optional[LiteralListStyleTypeUnordered] = "disc",
access_key: Optional[ access_key: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]] Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None, ] = None,
@ -302,24 +303,7 @@ class OrderedList(BaseList, Ol):
cls, cls,
*children, *children,
items: Optional[Union[Var[Iterable], Iterable]] = None, items: Optional[Union[Var[Iterable], Iterable]] = None,
list_style_type: Optional[ list_style_type: Optional[LiteralListStyleTypeOrdered] = "decimal",
Literal[
"none",
"decimal",
"decimal-leading-zero",
"lower-roman",
"upper-roman",
"lower-greek",
"lower-latin",
"upper-latin",
"armenian",
"georgian",
"lower-alpha",
"upper-alpha",
"hiragana",
"katakana",
]
] = "decimal",
reversed: Optional[ reversed: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]] Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None, ] = None,

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from reflex.components.component import Component from reflex.style import Style
from .flex import Flex from .flex import Flex
@ -10,8 +10,13 @@ from .flex import Flex
class Spacer(Flex): class Spacer(Flex):
"""A spacer component.""" """A spacer component."""
def _apply_theme(self, theme: Component): def add_style(self) -> Style | None:
self.style.update( """Add style to the component.
Returns:
The style of the component.
"""
return Style(
{ {
"flex": 1, "flex": 1,
"justify_self": "stretch", "justify_self": "stretch",

View File

@ -7,10 +7,11 @@ 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 reflex.components.component import Component from reflex.style import Style
from .flex import Flex from .flex import Flex
class Spacer(Flex): class Spacer(Flex):
def add_style(self) -> Style | None: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore

View File

@ -37,6 +37,8 @@ ColorType = Literal[
"bronze", "bronze",
"gray", "gray",
"accent", "accent",
"black",
"white",
] ]
ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

View File

@ -1,5 +1,7 @@
"""To experiment with layout component, move them to reflex/components later.""" """To experiment with layout component, move them to reflex/components later."""
from __future__ import annotations
from reflex import color, cond from reflex import color, cond
from reflex.components.base.fragment import Fragment from reflex.components.base.fragment import Fragment
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
@ -9,6 +11,7 @@ from reflex.components.radix.themes.layout import Box, Container, HStack
from reflex.event import call_script from reflex.event import call_script
from reflex.experimental import hooks from reflex.experimental import hooks
from reflex.state import ComponentState from reflex.state import ComponentState
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
@ -26,23 +29,38 @@ class Sidebar(Box, MemoizationLeaf):
Returns: Returns:
The sidebar component. The sidebar component.
""" """
props.setdefault("border_right", f"1px solid {color('accent', 12)}") # props.setdefault("border_right", f"1px solid {color('accent', 12)}")
props.setdefault("background_color", color("accent", 1)) # props.setdefault("background_color", color("accent", 1))
props.setdefault("width", "20vw") # props.setdefault("width", "20vw")
props.setdefault("height", "100vh") # props.setdefault("height", "100vh")
props.setdefault("position", "fixed") # props.setdefault("position", "fixed")
return super().create( return super().create(
Box.create(*children, **props), # sidebar for content Box.create(*children, **props), # sidebar for content
Box.create(width=props.get("width")), # spacer for layout Box.create(width=props.get("width")), # spacer for layout
) )
def _apply_theme(self, theme: Component | None): def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
sidebar: Component = self.children[-2] # type: ignore sidebar: Component = self.children[-2] # type: ignore
spacer: Component = self.children[-1] # type: ignore spacer: Component = self.children[-1] # type: ignore
open = self.State.open if self.State else Var.create("open") # type: ignore open = self.State.open if self.State else Var.create("open") # type: ignore
sidebar.style["display"] = spacer.style["display"] = cond(open, "block", "none") sidebar.style["display"] = spacer.style["display"] = cond(open, "block", "none")
return Style(
{
"position": "fixed",
"border_right": f"1px solid {color('accent', 12)}",
"background_color": color("accent", 1),
"width": "20vw",
"height": "100vh",
}
)
def _get_hooks(self) -> Var | None: def _get_hooks(self) -> Var | None:
return hooks.useState("open", "true") if not self.State else None return hooks.useState("open", "true") if not self.State else None

View File

@ -159,12 +159,17 @@ def format_style_key(key: str) -> Tuple[str, ...]:
class Style(dict): class Style(dict):
"""A style dictionary.""" """A style dictionary."""
def __init__(self, style_dict: dict | None = None): def __init__(self, style_dict: dict | None = None, **kwargs):
"""Initialize the style. """Initialize the style.
Args: Args:
style_dict: The style dictionary. style_dict: The style dictionary.
kwargs: Other key value pairs to apply to the dict update.
""" """
if style_dict:
style_dict.update(kwargs)
else:
style_dict = kwargs
style_dict, self._var_data = convert(style_dict or {}) style_dict, self._var_data = convert(style_dict or {})
super().__init__(style_dict) super().__init__(style_dict)

View File

@ -2,7 +2,7 @@ from typing import Dict, List, Set, Tuple, Union
import pytest import pytest
from reflex.components import box, foreach, text, theme from reflex.components import box, foreach, text
from reflex.components.core import Foreach from reflex.components.core import Foreach
from reflex.state import BaseState from reflex.state import BaseState
from reflex.vars import Var from reflex.vars import Var
@ -220,15 +220,6 @@ def test_foreach_render(state_var, render_fn, render_dict):
seen_index_vars.add(arg_index._var_name) seen_index_vars.add(arg_index._var_name)
def test_foreach_apply_theme():
"""Test that the foreach component applies the theme."""
tag = Foreach.create(ForEachState.colors_list, display_color) # type: ignore
_theme = theme()
tag.apply_theme(_theme)
assert tag.theme == _theme
tag.render()
def test_foreach_bad_annotations(): def test_foreach_bad_annotations():
"""Test that the foreach component raises a TypeError if the iterable is of type Any.""" """Test that the foreach component raises a TypeError if the iterable is of type Any."""
with pytest.raises(TypeError): with pytest.raises(TypeError):

View File

@ -1,7 +1,6 @@
import pytest import pytest
from reflex.components.lucide.icon import LUCIDE_ICON_LIST, RENAMED_ICONS_05, Icon from reflex.components.lucide.icon import LUCIDE_ICON_LIST, RENAMED_ICONS_05, Icon
from reflex.components.radix.themes.base import Theme
from reflex.utils import format from reflex.utils import format
@ -17,7 +16,7 @@ RENAMED_TAGS = [tag for tag in RENAMED_ICONS_05.items()]
@pytest.mark.parametrize("tag, new_tag", RENAMED_TAGS) @pytest.mark.parametrize("tag, new_tag", RENAMED_TAGS)
def test_icon_renamed_tags(tag, new_tag): def test_icon_renamed_tags(tag, new_tag):
Icon.create(tag) Icon.create(tag)
# need a PR so we can pass the following test. Currently it fails and uses the old tag as the import. # TODO: need a PR so we can pass the following test. Currently it fails and uses the old tag as the import.
# assert icon.alias == f"Lucide{format.to_title_case(new_tag)}Icon" # assert icon.alias == f"Lucide{format.to_title_case(new_tag)}Icon"
@ -36,6 +35,6 @@ def test_icon_multiple_children():
_ = Icon.create("activity", "child1", "child2") _ = Icon.create("activity", "child1", "child2")
def test_icon_apply_theme(): def test_icon_add_style():
ic = Icon.create("activity") ic = Icon.create("activity")
ic._apply_theme(Theme()) assert ic.add_style() is None

View File

@ -1,19 +1,21 @@
import pytest import pytest
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.base import Theme
from reflex.components.radix.themes.components.icon_button import IconButton from reflex.components.radix.themes.components.icon_button import IconButton
from reflex.style import Style
from reflex.vars import Var from reflex.vars import Var
def test_icon_button(): def test_icon_button():
ib1 = IconButton.create("activity") ib1 = IconButton.create("activity")
ib1._apply_theme(Theme.create())
assert isinstance(ib1, IconButton) assert isinstance(ib1, IconButton)
ib2 = IconButton.create(Icon.create("activity")) ib2 = IconButton.create(Icon.create("activity"))
assert isinstance(ib2, IconButton) assert isinstance(ib2, IconButton)
assert isinstance(ib1.add_style(), Style)
assert isinstance(ib2.add_style(), Style)
def test_icon_button_no_child(): def test_icon_button_no_child():
with pytest.raises(ValueError): with pytest.raises(ValueError):