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():
# Merge the component style with the app style.
component._add_style_recursive(self.style)
component.apply_theme(self.theme)
component._add_style_recursive(self.style, self.theme)
# Add component._get_all_imports() to 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.
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(
{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())
# 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]):
"""Apply the theme to this component.
Deprecated. Use add_style instead.
Args:
theme: The theme to apply.
"""
@ -779,44 +781,119 @@ class Component(BaseComponent, ABC):
return cls(children=children, **props)
def _add_style(self, style: dict):
"""Add additional style to the component.
def add_style(self) -> Style | None:
"""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:
style: A style dict to apply.
"""
self.style.update(style)
styles: The style to apply.
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.
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:
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:
The component with the additional style.
"""
component_style = None
if type(self) in style:
# Extract the style for this component.
component_style = Style(style[type(self)])
if self.create in style:
component_style = Style(style[self.create])
if component_style is not None:
# 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
}
# 1. Default style from `_add_style`/`add_style`.
if type(self)._add_style != Component._add_style:
raise UserWarning(
"Do not override _add_style directly. Use add_style instead."
)
new_style = self._add_style()
style_vars = [new_style._var_data]
# Add the style to the component.
self._add_style(component_style)
# 2. User-defined style from `App.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.
for child in self.children:
# Skip BaseComponent and StatefulComponent children.
if not isinstance(child, Component):
continue
child._add_style_recursive(style)
child._add_style_recursive(style, theme)
return self
def _get_style(self) -> dict:

View File

@ -102,15 +102,6 @@ class Cond(MemoizationLeaf):
_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
def cond(condition: Any, c1: Component, c2: Any) -> Component:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import inspect
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.component import Component
@ -23,17 +23,6 @@ class Foreach(Component):
# A function from the render args to the component.
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
def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach:
"""Create a foreach component.
@ -97,10 +86,6 @@ class Foreach(Component):
tag = self._render()
component = tag.render_component()
# Apply the theme to the children.
if self.theme is not None:
component.apply_theme(self.theme)
return dict(
tag.add_props(
**self.event_triggers,

View File

@ -273,18 +273,3 @@ class Match(MemoizationLeaf):
super()._get_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."""
from __future__ import annotations
import re
from typing import Dict, Literal, Optional, Union
@ -491,8 +493,9 @@ class CodeBlock(Component):
else:
return code_block
def _add_style(self, style):
self.custom_style.update(style) # type: ignore
def add_style(self) -> Style | None:
"""Add style to the component."""
self.custom_style.update(self.style)
def _render(self):
out = super()._render()

View File

@ -1111,5 +1111,6 @@ class CodeBlock(Component):
The text component.
"""
...
def add_style(self) -> Style | None: ...
@staticmethod
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.text import Text
from reflex.components.tags.tag import Tag
from reflex.style import Style
from reflex.utils import console, imports, types
from reflex.utils.imports import ImportVar
from reflex.vars import Var
@ -230,7 +229,8 @@ class Markdown(Component):
component = self.component_map[tag](*children, **props).set(
special_props=special_props
)
component._add_style(Style(self.custom_styles.get(tag, {})))
component.style.update(self.custom_styles.get(tag, {}))
return component
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.text import Text
from reflex.components.tags.tag import Tag
from reflex.style import Style
from reflex.utils import console, imports, types
from reflex.utils.imports import ImportVar
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 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.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor
from reflex.style import (
Style,
convert_dict_to_style_and_format_emotion,
format_as_emotion,
)
from reflex.style import Style
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"]
LiteralAccordionDir = Literal["ltr", "rtl"]
LiteralAccordionOrientation = Literal["vertical", "horizontal"]
LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
LiteralAccordionRootColorScheme = Literal["primary", "accent"]
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
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):
"""Base class for all @radix-ui/accordion components."""
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):
"""An accordion component."""
@ -332,16 +72,7 @@ class AccordionRoot(AccordionComponent):
orientation: Var[LiteralAccordionOrientation]
# The variant of the accordion.
variant: Var[LiteralAccordionRootVariant] = "classic" # type: ignore
# 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
variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
_valid_children: List[str] = ["AccordionItem"]
@ -356,81 +87,12 @@ class AccordionRoot(AccordionComponent):
Returns:
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
# 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")]},
)
return super().create(*children, **props)
def get_event_triggers(self) -> Dict[str, Any]:
"""Get the events triggers signatures for the component.
@ -443,28 +105,36 @@ class AccordionRoot(AccordionComponent):
"on_value_change": lambda e0: [e0],
}
def _get_custom_code(self) -> str:
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):
"""Add style to the component.
def _exclude_props(self) -> list[str]:
return ["color_scheme", "variant"]
Returns:
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):
@ -488,13 +158,6 @@ class AccordionItem(AccordionComponent):
_valid_parents: List[str] = ["AccordionRoot"]
def _apply_theme(self, theme: Component):
self.style = Style(
{
**self.style,
}
)
@classmethod
def create(
cls,
@ -506,9 +169,9 @@ class AccordionItem(AccordionComponent):
"""Create an accordion item.
Args:
*children: The list of children to use if header and content are not provided.
header: The header 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.
Returns:
@ -527,14 +190,55 @@ class AccordionItem(AccordionComponent):
AccordionHeader.create(
AccordionTrigger.create(
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)
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):
"""An accordion component."""
@ -561,8 +265,21 @@ class AccordionHeader(AccordionComponent):
return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component):
self.style = Style({**self.style})
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, 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):
@ -590,8 +307,52 @@ class AccordionTrigger(AccordionComponent):
return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component):
self.style = Style({**self.style})
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, 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):
@ -623,6 +384,14 @@ class AccordionContent(AccordionComponent):
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
def create(cls, *children, **props) -> Component:
"""Create the Accordion content component.
@ -641,14 +410,66 @@ class AccordionContent(AccordionComponent):
return super().create(*children, class_name=cls_name, **props)
def _apply_theme(self, theme: Component):
self.style = Style({**self.style})
def add_custom_code(self) -> list[str]:
"""Add custom code to the component.
# def _get_imports(self):
# return {
# **super()._get_imports(),
# "@emotion/react": [imports.ImportVar(tag="keyframes")],
# }
Returns:
The custom code of the component.
"""
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):

View File

@ -9,41 +9,95 @@ from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
from typing import Any, Dict, List, Literal, Optional, Union
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.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor
from reflex.style import (
Style,
convert_dict_to_style_and_format_emotion,
format_as_emotion,
)
from reflex.style import Style
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"]
LiteralAccordionDir = Literal["ltr", "rtl"]
LiteralAccordionOrientation = Literal["vertical", "horizontal"]
LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
LiteralAccordionRootColorScheme = Literal["primary", "accent"]
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
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):
def add_style(self) -> Style | None: ...
@overload
@classmethod
def create( # type: ignore
cls,
*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,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -102,6 +156,8 @@ class AccordionComponent(RadixPrimitiveComponent):
Args:
*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.
style: The style of the component.
key: A unique key for the component.
@ -208,8 +264,6 @@ class AccordionRoot(AccordionComponent):
],
]
] = None,
_dynamic_themes: Optional[Union[Var[dict], dict]] = None,
_var_data: Optional[VarData] = None,
as_child: Optional[Union[Var[bool], bool]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -278,10 +332,8 @@ 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.
_var_data: The var_data associated with the component.
variant: The variant of the component.
color_scheme: The color scheme 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.
@ -296,6 +348,7 @@ class AccordionRoot(AccordionComponent):
"""
...
def get_event_triggers(self) -> Dict[str, Any]: ...
def add_style(self): ...
class AccordionItem(AccordionComponent):
@overload
@ -307,6 +360,74 @@ class AccordionItem(AccordionComponent):
content: Optional[Union[Component, Var]] = None,
value: Optional[Union[Var[str], str]] = 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,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -364,11 +485,13 @@ class AccordionItem(AccordionComponent):
"""Create an accordion item.
Args:
*children: The list of children to use if header and content are not provided.
header: The header 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.
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.
style: The style of the component.
key: A unique key for the component.
@ -382,6 +505,7 @@ class AccordionItem(AccordionComponent):
The accordion item.
"""
...
def add_style(self) -> Style | None: ...
class AccordionHeader(AccordionComponent):
@overload
@ -389,6 +513,74 @@ class AccordionHeader(AccordionComponent):
def create( # type: ignore
cls,
*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,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -447,6 +639,8 @@ class AccordionHeader(AccordionComponent):
Args:
*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.
style: The style of the component.
key: A unique key for the component.
@ -460,6 +654,9 @@ class AccordionHeader(AccordionComponent):
The Accordion header Component.
"""
...
def add_style(self) -> Style | None: ...
cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
class AccordionTrigger(AccordionComponent):
@overload
@ -467,6 +664,74 @@ class AccordionTrigger(AccordionComponent):
def create( # type: ignore
cls,
*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,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -525,6 +790,8 @@ class AccordionTrigger(AccordionComponent):
Args:
*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.
style: The style of the component.
key: A unique key for the component.
@ -538,6 +805,7 @@ class AccordionTrigger(AccordionComponent):
The Accordion trigger Component.
"""
...
def add_style(self) -> Style | None: ...
class AccordionIcon(Icon):
@overload
@ -618,11 +886,80 @@ class AccordionIcon(Icon):
...
class AccordionContent(AccordionComponent):
def add_imports(self) -> imports.ImportDict: ...
@overload
@classmethod
def create( # type: ignore
cls,
*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,
style: Optional[Style] = None,
key: Optional[Any] = None,
@ -681,6 +1018,8 @@ class AccordionContent(AccordionComponent):
Args:
*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.
style: The style of the component.
key: A unique key for the component.
@ -694,6 +1033,8 @@ class AccordionContent(AccordionComponent):
The Accordion content Component.
"""
...
def add_custom_code(self) -> list[str]: ...
def add_style(self) -> Style | None: ...
class Accordion(ComponentNamespace):
content = staticmethod(AccordionContent.create)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
"""Interactive components provided by @radix-ui/themes."""
from __future__ import annotations
from typing import Literal
@ -6,6 +7,7 @@ from reflex import el
from reflex.components.component import Component
from reflex.components.core.match import Match
from reflex.components.lucide import Icon
from reflex.style import Style
from reflex.vars import Var
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."
)
if "size" in props:
RADIX_TO_LUCIDE_SIZE = {"1": "12px", "2": "24px", "3": "36px", "4": "48px"}
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"]]
else:
children[0].size = Match.create(
props["size"],
("1", "12px"),
("2", "24px"),
("3", "36px"),
("4", "48px"),
*[(size, px) for size, px in RADIX_TO_LUCIDE_SIZE.items()],
"12px",
)
props.setdefault("padding", "6px")
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

View File

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

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from reflex.components.component import Component
from reflex.style import Style
from .flex import Flex
@ -10,8 +10,13 @@ from .flex import Flex
class Center(Flex):
"""A center component."""
def _apply_theme(self, theme: Component):
self.style.update(
def add_style(self) -> Style | None:
"""Add style that center the content.
Returns:
The style of the component.
"""
return Style(
{
"display": "flex",
"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.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
from reflex.components.component import Component
from reflex.style import Style
from .flex import Flex
class Center(Flex):
def add_style(self) -> Style | None: ...
@overload
@classmethod
def create( # type: ignore

View File

@ -1,4 +1,5 @@
"""List components."""
from __future__ import annotations
from typing import Iterable, Literal, Optional, Union
@ -77,12 +78,16 @@ class BaseList(Component):
style["gap"] = props["gap"]
return super().create(*children, **props)
def _apply_theme(self, theme: Component):
self.style = Style(
def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
return Style(
{
"direction": "column",
"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):
@overload
@ -165,7 +166,7 @@ class UnorderedList(BaseList, Ul):
cls,
*children,
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[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None,
@ -302,24 +303,7 @@ class OrderedList(BaseList, Ol):
cls,
*children,
items: Optional[Union[Var[Iterable], Iterable]] = None,
list_style_type: Optional[
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",
list_style_type: Optional[LiteralListStyleTypeOrdered] = "decimal",
reversed: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None,

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from reflex.components.component import Component
from reflex.style import Style
from .flex import Flex
@ -10,8 +10,13 @@ from .flex import Flex
class Spacer(Flex):
"""A spacer component."""
def _apply_theme(self, theme: Component):
self.style.update(
def add_style(self) -> Style | None:
"""Add style to the component.
Returns:
The style of the component.
"""
return Style(
{
"flex": 1,
"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.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
from reflex.components.component import Component
from reflex.style import Style
from .flex import Flex
class Spacer(Flex):
def add_style(self) -> Style | None: ...
@overload
@classmethod
def create( # type: ignore

View File

@ -37,6 +37,8 @@ ColorType = Literal[
"bronze",
"gray",
"accent",
"black",
"white",
]
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."""
from __future__ import annotations
from reflex import color, cond
from reflex.components.base.fragment import Fragment
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.experimental import hooks
from reflex.state import ComponentState
from reflex.style import Style
from reflex.vars import Var
@ -26,23 +29,38 @@ class Sidebar(Box, MemoizationLeaf):
Returns:
The sidebar component.
"""
props.setdefault("border_right", f"1px solid {color('accent', 12)}")
props.setdefault("background_color", color("accent", 1))
props.setdefault("width", "20vw")
props.setdefault("height", "100vh")
props.setdefault("position", "fixed")
# props.setdefault("border_right", f"1px solid {color('accent', 12)}")
# props.setdefault("background_color", color("accent", 1))
# props.setdefault("width", "20vw")
# props.setdefault("height", "100vh")
# props.setdefault("position", "fixed")
return super().create(
Box.create(*children, **props), # sidebar for content
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
spacer: Component = self.children[-1] # 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")
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:
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):
"""A style dictionary."""
def __init__(self, style_dict: dict | None = None):
def __init__(self, style_dict: dict | None = None, **kwargs):
"""Initialize the style.
Args:
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 {})
super().__init__(style_dict)

View File

@ -2,7 +2,7 @@ from typing import Dict, List, Set, Tuple, Union
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.state import BaseState
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)
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():
"""Test that the foreach component raises a TypeError if the iterable is of type Any."""
with pytest.raises(TypeError):

View File

@ -1,7 +1,6 @@
import pytest
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
@ -17,7 +16,7 @@ RENAMED_TAGS = [tag for tag in RENAMED_ICONS_05.items()]
@pytest.mark.parametrize("tag, new_tag", RENAMED_TAGS)
def test_icon_renamed_tags(tag, new_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"
@ -36,6 +35,6 @@ def test_icon_multiple_children():
_ = Icon.create("activity", "child1", "child2")
def test_icon_apply_theme():
def test_icon_add_style():
ic = Icon.create("activity")
ic._apply_theme(Theme())
assert ic.add_style() is None

View File

@ -1,19 +1,21 @@
import pytest
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.style import Style
from reflex.vars import Var
def test_icon_button():
ib1 = IconButton.create("activity")
ib1._apply_theme(Theme.create())
assert isinstance(ib1, IconButton)
ib2 = IconButton.create(Icon.create("activity"))
assert isinstance(ib2, IconButton)
assert isinstance(ib1.add_style(), Style)
assert isinstance(ib2.add_style(), Style)
def test_icon_button_no_child():
with pytest.raises(ValueError):