Compare commits
12 Commits
main
...
reflex-0.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
25c1406e7f | ||
![]() |
7d4609ba60 | ||
![]() |
52e0507c40 | ||
![]() |
5e1fe07e0b | ||
![]() |
d520b34762 | ||
![]() |
7727c14758 | ||
![]() |
7e6fd44732 | ||
![]() |
69d5c02cc5 | ||
![]() |
09dfd031d7 | ||
![]() |
4e58716378 | ||
![]() |
4f7ccb57bc | ||
![]() |
601391fca9 |
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "reflex"
|
name = "reflex"
|
||||||
version = "0.4.9"
|
version = "0.5.0.post1"
|
||||||
description = "Web apps in pure Python."
|
description = "Web apps in pure Python."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
authors = [
|
authors = [
|
||||||
|
@ -44,7 +44,7 @@ def template(main_content: Callable[[], rx.Component]) -> rx.Component:
|
|||||||
),
|
),
|
||||||
rx.chakra.menu_item(
|
rx.chakra.menu_item(
|
||||||
rx.chakra.link(
|
rx.chakra.link(
|
||||||
"Contact", href="mailto:founders@=reflex.dev", width="100%"
|
"Contact", href="mailto:founders@reflex.dev", width="100%"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -17,4 +17,16 @@ module.exports = {
|
|||||||
{% if darkMode is defined %}
|
{% if darkMode is defined %}
|
||||||
darkMode: {{darkMode|json_dumps}},
|
darkMode: {{darkMode|json_dumps}},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if corePlugins is defined %}
|
||||||
|
corePlugins: {{corePlugins|json_dumps}},
|
||||||
|
{% endif %}
|
||||||
|
{% if important is defined %}
|
||||||
|
important: {{important|json_dumps}},
|
||||||
|
{% endif %}
|
||||||
|
{% if prefix is defined %}
|
||||||
|
prefix: {{prefix|json_dumps}},
|
||||||
|
{% endif %}
|
||||||
|
{% if separator is defined %}
|
||||||
|
separator: {{separator|json_dumps}},
|
||||||
|
{% endif %}
|
||||||
};
|
};
|
||||||
|
@ -781,7 +781,7 @@ class Component(BaseComponent, ABC):
|
|||||||
|
|
||||||
return cls(children=children, **props)
|
return cls(children=children, **props)
|
||||||
|
|
||||||
def add_style(self) -> Style | None:
|
def add_style(self) -> dict[str, Any] | None:
|
||||||
"""Add style to the component.
|
"""Add style to the component.
|
||||||
|
|
||||||
Downstream components can override this method to return a style dict
|
Downstream components can override this method to return a style dict
|
||||||
@ -801,20 +801,16 @@ class Component(BaseComponent, ABC):
|
|||||||
The style to add.
|
The style to add.
|
||||||
"""
|
"""
|
||||||
styles = []
|
styles = []
|
||||||
vars = []
|
|
||||||
|
|
||||||
# Walk the MRO to call all `add_style` methods.
|
# Walk the MRO to call all `add_style` methods.
|
||||||
for base in self._iter_parent_classes_with_method("add_style"):
|
for base in self._iter_parent_classes_with_method("add_style"):
|
||||||
s = base.add_style(self) # type: ignore
|
s = base.add_style(self) # type: ignore
|
||||||
if s is not None:
|
if s is not None:
|
||||||
styles.append(s)
|
styles.append(s)
|
||||||
vars.append(s._var_data)
|
|
||||||
|
|
||||||
_style = Style()
|
_style = Style()
|
||||||
for s in reversed(styles):
|
for s in reversed(styles):
|
||||||
_style.update(s)
|
_style.update(s)
|
||||||
|
|
||||||
_style._var_data = VarData.merge(*vars)
|
|
||||||
return _style
|
return _style
|
||||||
|
|
||||||
def _get_component_style(self, styles: ComponentStyle) -> Style | None:
|
def _get_component_style(self, styles: ComponentStyle) -> Style | None:
|
||||||
|
@ -2,16 +2,24 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from hashlib import md5
|
|
||||||
from typing import Any, Callable, Iterable
|
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
|
||||||
from reflex.components.tags import IterTag
|
from reflex.components.tags import IterTag
|
||||||
from reflex.constants import MemoizationMode
|
from reflex.constants import MemoizationMode
|
||||||
|
from reflex.utils import console
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
|
|
||||||
|
class ForeachVarError(TypeError):
|
||||||
|
"""Raised when the iterable type is Any."""
|
||||||
|
|
||||||
|
|
||||||
|
class ForeachRenderError(TypeError):
|
||||||
|
"""Raised when there is an error with the foreach render function."""
|
||||||
|
|
||||||
|
|
||||||
class Foreach(Component):
|
class Foreach(Component):
|
||||||
"""A component that takes in an iterable and a render function and renders a list of components."""
|
"""A component that takes in an iterable and a render function and renders a list of components."""
|
||||||
|
|
||||||
@ -24,56 +32,84 @@ class Foreach(Component):
|
|||||||
render_fn: Callable = Fragment.create
|
render_fn: Callable = Fragment.create
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach:
|
def create(
|
||||||
|
cls,
|
||||||
|
iterable: Var[Iterable] | Iterable,
|
||||||
|
render_fn: Callable,
|
||||||
|
**props,
|
||||||
|
) -> Foreach:
|
||||||
"""Create a foreach component.
|
"""Create a foreach component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
iterable: The iterable to create components from.
|
iterable: The iterable to create components from.
|
||||||
render_fn: A function from the render args to the component.
|
render_fn: A function from the render args to the component.
|
||||||
**props: The attributes to pass to each child component.
|
**props: The attributes to pass to each child component (deprecated).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The foreach component.
|
The foreach component.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: If the iterable is of type Any.
|
ForeachVarError: If the iterable is of type Any.
|
||||||
"""
|
"""
|
||||||
iterable = Var.create(iterable) # type: ignore
|
if props:
|
||||||
|
console.deprecate(
|
||||||
|
feature_name="Passing props to rx.foreach",
|
||||||
|
reason="it does not have the intended effect and may be confusing",
|
||||||
|
deprecation_version="0.5.0",
|
||||||
|
removal_version="0.6.0",
|
||||||
|
)
|
||||||
|
iterable = Var.create_safe(iterable)
|
||||||
if iterable._var_type == Any:
|
if iterable._var_type == Any:
|
||||||
raise TypeError(
|
raise ForeachVarError(
|
||||||
f"Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)"
|
f"Could not foreach over var `{iterable._var_full_name}` of type Any. "
|
||||||
|
"(If you are trying to foreach over a state var, add a type annotation to the var). "
|
||||||
|
"See https://reflex.dev/docs/library/layout/foreach/"
|
||||||
)
|
)
|
||||||
component = cls(
|
component = cls(
|
||||||
iterable=iterable,
|
iterable=iterable,
|
||||||
render_fn=render_fn,
|
render_fn=render_fn,
|
||||||
**props,
|
|
||||||
)
|
)
|
||||||
# Keep a ref to a rendered component to determine correct imports.
|
# Keep a ref to a rendered component to determine correct imports/hooks/styles.
|
||||||
component.children = [
|
component.children = [component._render().render_component()]
|
||||||
component._render(props=dict(index_var_name="i")).render_component()
|
|
||||||
]
|
|
||||||
return component
|
return component
|
||||||
|
|
||||||
def _render(self, props: dict[str, Any] | None = None) -> IterTag:
|
def _render(self) -> IterTag:
|
||||||
props = {} if props is None else props.copy()
|
props = {}
|
||||||
|
|
||||||
# Determine the arg var name based on the params accepted by render_fn.
|
|
||||||
render_sig = inspect.signature(self.render_fn)
|
render_sig = inspect.signature(self.render_fn)
|
||||||
params = list(render_sig.parameters.values())
|
params = list(render_sig.parameters.values())
|
||||||
if len(params) >= 1:
|
|
||||||
props.setdefault("arg_var_name", params[0].name)
|
|
||||||
|
|
||||||
if len(params) >= 2:
|
# Validate the render function signature.
|
||||||
|
if len(params) == 0 or len(params) > 2:
|
||||||
|
raise ForeachRenderError(
|
||||||
|
"Expected 1 or 2 parameters in foreach render function, got "
|
||||||
|
f"{[p.name for p in params]}. See https://reflex.dev/docs/library/layout/foreach/"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(params) >= 1:
|
||||||
|
# Determine the arg var name based on the params accepted by render_fn.
|
||||||
|
props["arg_var_name"] = params[0].name
|
||||||
|
|
||||||
|
if len(params) == 2:
|
||||||
# Determine the index var name based on the params accepted by render_fn.
|
# Determine the index var name based on the params accepted by render_fn.
|
||||||
props.setdefault("index_var_name", params[1].name)
|
props["index_var_name"] = params[1].name
|
||||||
elif "index_var_name" not in props:
|
else:
|
||||||
# Otherwise, use a deterministic index, based on the rendered code.
|
# Otherwise, use a deterministic index, based on the render function bytecode.
|
||||||
code_hash = md5(str(self.children[0].render()).encode("utf-8")).hexdigest()
|
code_hash = (
|
||||||
props.setdefault("index_var_name", f"index_{code_hash}")
|
hash(self.render_fn.__code__)
|
||||||
|
.to_bytes(
|
||||||
|
length=8,
|
||||||
|
byteorder="big",
|
||||||
|
signed=True,
|
||||||
|
)
|
||||||
|
.hex()
|
||||||
|
)
|
||||||
|
props["index_var_name"] = f"index_{code_hash}"
|
||||||
|
|
||||||
return IterTag(
|
return IterTag(
|
||||||
iterable=self.iterable,
|
iterable=self.iterable,
|
||||||
render_fn=self.render_fn,
|
render_fn=self.render_fn,
|
||||||
|
children=self.children,
|
||||||
**props,
|
**props,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,19 +120,9 @@ class Foreach(Component):
|
|||||||
The dictionary for template of component.
|
The dictionary for template of component.
|
||||||
"""
|
"""
|
||||||
tag = self._render()
|
tag = self._render()
|
||||||
component = tag.render_component()
|
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
tag.add_props(
|
tag,
|
||||||
**self.event_triggers,
|
|
||||||
key=self.key,
|
|
||||||
sx=self.style,
|
|
||||||
id=self.id,
|
|
||||||
class_name=self.class_name,
|
|
||||||
).set(
|
|
||||||
children=[component.render()],
|
|
||||||
props=tag.format_props(),
|
|
||||||
),
|
|
||||||
iterable_state=tag.iterable._var_full_name,
|
iterable_state=tag.iterable._var_full_name,
|
||||||
arg_name=tag.arg_var_name,
|
arg_name=tag.arg_var_name,
|
||||||
arg_index=tag.get_index_var_arg(),
|
arg_index=tag.get_index_var_arg(),
|
||||||
|
@ -6,9 +6,10 @@ 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.colors import color
|
from reflex.components.core.colors import color
|
||||||
|
from reflex.components.core.cond import cond
|
||||||
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, LiteralRadius
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils import imports
|
from reflex.utils import imports
|
||||||
from reflex.vars import Var, get_uuid_string_var
|
from reflex.vars import Var, get_uuid_string_var
|
||||||
@ -19,6 +20,32 @@ LiteralAccordionOrientation = Literal["vertical", "horizontal"]
|
|||||||
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
|
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
|
||||||
|
|
||||||
DEFAULT_ANIMATION_DURATION = 250
|
DEFAULT_ANIMATION_DURATION = 250
|
||||||
|
DEFAULT_ANIMATION_EASING = "cubic-bezier(0.87, 0, 0.13, 1)"
|
||||||
|
|
||||||
|
|
||||||
|
def _inherited_variant_selector(
|
||||||
|
variant: Var[LiteralAccordionVariant] | LiteralAccordionVariant,
|
||||||
|
*selectors: str,
|
||||||
|
) -> str:
|
||||||
|
"""Create a multi CSS selector for targeting variant against the given selectors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
variant: The variant to target.
|
||||||
|
selectors: The selectors to apply the variant to (default &)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A CSS selector that is more specific on elements that directly set the variant.
|
||||||
|
"""
|
||||||
|
if not selectors:
|
||||||
|
selectors = ("&",)
|
||||||
|
# Prefer the `data-variant` that is set directly on the selector,
|
||||||
|
# but also inherit the `data-variant` from any parent element.
|
||||||
|
return ", ".join(
|
||||||
|
[
|
||||||
|
f"{selector}[data-variant='{variant}'], *:where([data-variant='{variant}']) {selector}"
|
||||||
|
for selector in selectors
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccordionComponent(RadixPrimitiveComponent):
|
class AccordionComponent(RadixPrimitiveComponent):
|
||||||
@ -30,14 +57,14 @@ class AccordionComponent(RadixPrimitiveComponent):
|
|||||||
color_scheme: Var[LiteralAccentColor]
|
color_scheme: Var[LiteralAccentColor]
|
||||||
|
|
||||||
# The variant of the component.
|
# The variant of the component.
|
||||||
variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
|
variant: Var[LiteralAccordionVariant]
|
||||||
|
|
||||||
def add_style(self) -> Style | None:
|
def add_style(self) -> Style | None:
|
||||||
"""Add style to the component."""
|
"""Add style to 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
|
||||||
|
if self.variant is not None:
|
||||||
self.custom_attrs["data-variant"] = self.variant
|
self.custom_attrs["data-variant"] = self.variant
|
||||||
|
|
||||||
def _exclude_props(self) -> list[str]:
|
def _exclude_props(self) -> list[str]:
|
||||||
return ["color_scheme", "variant"]
|
return ["color_scheme", "variant"]
|
||||||
@ -71,28 +98,27 @@ class AccordionRoot(AccordionComponent):
|
|||||||
# The orientation of the accordion.
|
# The orientation of the accordion.
|
||||||
orientation: Var[LiteralAccordionOrientation]
|
orientation: Var[LiteralAccordionOrientation]
|
||||||
|
|
||||||
# The variant of the accordion.
|
# The radius of the accordion corners.
|
||||||
variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
|
radius: Var[LiteralRadius]
|
||||||
|
|
||||||
|
# The time in milliseconds to animate open and close
|
||||||
|
duration: Var[int] = Var.create_safe(DEFAULT_ANIMATION_DURATION)
|
||||||
|
|
||||||
|
# The easing function to use for the animation.
|
||||||
|
easing: Var[str] = Var.create_safe(DEFAULT_ANIMATION_EASING)
|
||||||
|
|
||||||
|
# Whether to show divider lines between items.
|
||||||
|
show_dividers: Var[bool]
|
||||||
|
|
||||||
_valid_children: List[str] = ["AccordionItem"]
|
_valid_children: List[str] = ["AccordionItem"]
|
||||||
|
|
||||||
@classmethod
|
def _exclude_props(self) -> list[str]:
|
||||||
def create(cls, *children, **props) -> Component:
|
return super()._exclude_props() + [
|
||||||
"""Create the Accordion root component.
|
"radius",
|
||||||
|
"duration",
|
||||||
Args:
|
"easing",
|
||||||
*children: The children of the component.
|
"show_dividers",
|
||||||
**props: The properties of the component.
|
]
|
||||||
|
|
||||||
Returns:
|
|
||||||
The Accordion root Component.
|
|
||||||
"""
|
|
||||||
for child in children:
|
|
||||||
if isinstance(child, AccordionItem):
|
|
||||||
child.color_scheme = props.get("color_scheme") # type: ignore
|
|
||||||
child.variant = props.get("variant") # type: ignore
|
|
||||||
|
|
||||||
return super().create(*children, **props)
|
|
||||||
|
|
||||||
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.
|
||||||
@ -111,30 +137,42 @@ class AccordionRoot(AccordionComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
return Style(
|
if self.radius is not None:
|
||||||
{
|
self.custom_attrs["data-radius"] = self.radius
|
||||||
"border_radius": "6px",
|
if self.variant is None:
|
||||||
"box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}",
|
# The default variant is classic
|
||||||
"&[data-variant='classic']": {
|
self.custom_attrs["data-variant"] = "classic"
|
||||||
"background_color": color("accent", 9),
|
|
||||||
"box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}",
|
style = {
|
||||||
},
|
"border_radius": "var(--radius-4)",
|
||||||
"&[data-variant='soft']": {
|
"box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}",
|
||||||
"background_color": color("accent", 3),
|
"&[data-variant='classic']": {
|
||||||
},
|
"background_color": color("accent", 9),
|
||||||
"&[data-variant='outline']": {
|
"box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}",
|
||||||
"border": f"1px solid {color('accent', 6)}",
|
},
|
||||||
},
|
"&[data-variant='soft']": {
|
||||||
"&[data-variant='surface']": {
|
"background_color": color("accent", 3),
|
||||||
"border": f"1px solid {color('accent', 6)}",
|
},
|
||||||
"background_color": color("accent", 3),
|
"&[data-variant='outline']": {
|
||||||
},
|
"border": f"1px solid {color('accent', 6)}",
|
||||||
"&[data-variant='ghost']": {
|
},
|
||||||
"background_color": "none",
|
"&[data-variant='surface']": {
|
||||||
"box_shadow": "None",
|
"border": f"1px solid {color('accent', 6)}",
|
||||||
},
|
"background_color": "var(--accent-surface)",
|
||||||
}
|
},
|
||||||
)
|
"&[data-variant='ghost']": {
|
||||||
|
"background_color": "none",
|
||||||
|
"box_shadow": "None",
|
||||||
|
},
|
||||||
|
"--animation-duration": f"{self.duration}ms",
|
||||||
|
"--animation-easing": self.easing,
|
||||||
|
}
|
||||||
|
if self.show_dividers is not None:
|
||||||
|
style["--divider-px"] = cond(self.show_dividers, "1px", "0")
|
||||||
|
else:
|
||||||
|
style["&[data-variant='outline']"]["--divider-px"] = "1px"
|
||||||
|
style["&[data-variant='surface']"]["--divider-px"] = "1px"
|
||||||
|
return Style(style)
|
||||||
|
|
||||||
|
|
||||||
class AccordionItem(AccordionComponent):
|
class AccordionItem(AccordionComponent):
|
||||||
@ -185,23 +223,28 @@ class AccordionItem(AccordionComponent):
|
|||||||
):
|
):
|
||||||
cls_name = f"{cls_name} AccordionItem"
|
cls_name = f"{cls_name} AccordionItem"
|
||||||
|
|
||||||
|
color_scheme = props.get("color_scheme")
|
||||||
|
variant = props.get("variant")
|
||||||
|
|
||||||
if (header is not None) and (content is not None):
|
if (header is not None) and (content is not None):
|
||||||
children = [
|
children = [
|
||||||
AccordionHeader.create(
|
AccordionHeader.create(
|
||||||
AccordionTrigger.create(
|
AccordionTrigger.create(
|
||||||
header,
|
header,
|
||||||
AccordionIcon.create(
|
AccordionIcon.create(
|
||||||
color_scheme=props.get("color_scheme"),
|
color_scheme=color_scheme,
|
||||||
variant=props.get("variant"),
|
variant=variant,
|
||||||
),
|
),
|
||||||
color_scheme=props.get("color_scheme"),
|
color_scheme=color_scheme,
|
||||||
variant=props.get("variant"),
|
variant=variant,
|
||||||
),
|
),
|
||||||
color_scheme=props.get("color_scheme"),
|
color_scheme=color_scheme,
|
||||||
variant=props.get("variant"),
|
variant=variant,
|
||||||
),
|
),
|
||||||
AccordionContent.create(
|
AccordionContent.create(
|
||||||
content, color_scheme=props.get("color_scheme")
|
content,
|
||||||
|
color_scheme=color_scheme,
|
||||||
|
variant=variant,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -213,29 +256,35 @@ class AccordionItem(AccordionComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
for child in self.children:
|
divider_style = f"var(--divider-px) solid {color('gray', 6, alpha=True)}"
|
||||||
if isinstance(child, (AccordionHeader, AccordionContent)):
|
|
||||||
child.color_scheme = self.color_scheme
|
|
||||||
child.variant = self.variant
|
|
||||||
|
|
||||||
return Style(
|
return Style(
|
||||||
{
|
{
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"margin_top": "1px",
|
"margin_top": "1px",
|
||||||
|
"border_top": divider_style,
|
||||||
"&:first-child": {
|
"&:first-child": {
|
||||||
"margin_top": 0,
|
"margin_top": 0,
|
||||||
"border_top_left_radius": "4px",
|
"border_top": 0,
|
||||||
"border_top_right_radius": "4px",
|
"border_top_left_radius": "var(--radius-4)",
|
||||||
|
"border_top_right_radius": "var(--radius-4)",
|
||||||
},
|
},
|
||||||
"&:last-child": {
|
"&:last-child": {
|
||||||
"border_bottom_left_radius": "4px",
|
"border_bottom_left_radius": "var(--radius-4)",
|
||||||
"border_bottom_right_radius": "4px",
|
"border_bottom_right_radius": "var(--radius-4)",
|
||||||
},
|
},
|
||||||
"&:focus-within": {
|
"&:focus-within": {
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
"z_index": 1,
|
"z_index": 1,
|
||||||
},
|
},
|
||||||
|
_inherited_variant_selector("ghost", "&:first-child"): {
|
||||||
|
"border_radius": 0,
|
||||||
|
"border_top": divider_style,
|
||||||
|
},
|
||||||
|
_inherited_variant_selector("ghost", "&:last-child"): {
|
||||||
|
"border_radius": 0,
|
||||||
|
"border_bottom": divider_style,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -271,17 +320,9 @@ class AccordionHeader(AccordionComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
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"})
|
return Style({"display": "flex"})
|
||||||
|
|
||||||
|
|
||||||
cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
|
|
||||||
|
|
||||||
|
|
||||||
class AccordionTrigger(AccordionComponent):
|
class AccordionTrigger(AccordionComponent):
|
||||||
"""An accordion component."""
|
"""An accordion component."""
|
||||||
|
|
||||||
@ -313,24 +354,18 @@ class AccordionTrigger(AccordionComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
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(
|
return Style(
|
||||||
{
|
{
|
||||||
"color": color("accent", 11),
|
"color": color("accent", 11),
|
||||||
|
"font_size": "1.1em",
|
||||||
"line_height": 1,
|
"line_height": 1,
|
||||||
"font_size": "15px",
|
|
||||||
"justify_content": "space-between",
|
"justify_content": "space-between",
|
||||||
"align_items": "center",
|
"align_items": "center",
|
||||||
"flex": 1,
|
"flex": 1,
|
||||||
"display": "flex",
|
"display": "flex",
|
||||||
"padding": "0 20px",
|
"padding": "var(--space-3) var(--space-4)",
|
||||||
"height": "45px",
|
|
||||||
"font_family": "inherit",
|
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
|
"box_shadow": f"0 var(--divider-px) 0 {color('gray', 6, alpha=True)}",
|
||||||
"&[data-state='open'] > .AccordionChevron": {
|
"&[data-state='open'] > .AccordionChevron": {
|
||||||
"transform": "rotate(180deg)",
|
"transform": "rotate(180deg)",
|
||||||
},
|
},
|
||||||
@ -338,17 +373,15 @@ class AccordionTrigger(AccordionComponent):
|
|||||||
"background_color": color("accent", 4),
|
"background_color": color("accent", 4),
|
||||||
},
|
},
|
||||||
"& > .AccordionChevron": {
|
"& > .AccordionChevron": {
|
||||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
|
"transition": f"transform var(--animation-duration) var(--animation-easing)",
|
||||||
},
|
},
|
||||||
"&[data-variant='classic']": {
|
_inherited_variant_selector("classic"): {
|
||||||
"color": color("accent", 12),
|
"color": "var(--accent-contrast)",
|
||||||
"box_shadow": color("accent", 11),
|
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
"background_color": color("accent", 10),
|
"background_color": color("accent", 10),
|
||||||
},
|
},
|
||||||
"& > .AccordionChevron": {
|
"& > .AccordionChevron": {
|
||||||
"color": color("accent", 12),
|
"color": "var(--accent-contrast)",
|
||||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -444,30 +477,31 @@ to {
|
|||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
slideDown = Var.create(
|
slideDown = Var.create(
|
||||||
f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
|
f"${{slideDown}} var(--animation-duration) var(--animation-easing)",
|
||||||
_var_is_string=True,
|
_var_is_string=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
slideUp = Var.create(
|
slideUp = Var.create(
|
||||||
f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
|
f"${{slideUp}} var(--animation-duration) var(--animation-easing)",
|
||||||
_var_is_string=True,
|
_var_is_string=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Style(
|
return Style(
|
||||||
{
|
{
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
"font_size": "10px",
|
|
||||||
"color": color("accent", 11),
|
"color": color("accent", 11),
|
||||||
"background_color": color("accent", 3),
|
"padding_x": "var(--space-4)",
|
||||||
"padding": "0 15px",
|
# Apply before and after content to avoid height animation jank.
|
||||||
|
"&:before, &:after": {
|
||||||
|
"content": "' '",
|
||||||
|
"display": "block",
|
||||||
|
"height": "var(--space-3)",
|
||||||
|
},
|
||||||
"&[data-state='open']": {"animation": slideDown},
|
"&[data-state='open']": {"animation": slideDown},
|
||||||
"&[data-state='closed']": {"animation": slideUp},
|
"&[data-state='closed']": {"animation": slideUp},
|
||||||
"&[data-variant='classic']": {
|
_inherited_variant_selector("classic"): {
|
||||||
"color": color("accent", 12),
|
"color": "var(--accent-contrast)",
|
||||||
"background_color": color("accent", 9),
|
|
||||||
},
|
},
|
||||||
"&[data-variant='outline']": {"background_color": "transparent"},
|
|
||||||
"&[data-variant='ghost']": {"background_color": "transparent"},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,9 +10,10 @@ 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.colors import color
|
from reflex.components.core.colors import color
|
||||||
|
from reflex.components.core.cond import cond
|
||||||
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, LiteralRadius
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils import imports
|
from reflex.utils import imports
|
||||||
from reflex.vars import Var, get_uuid_string_var
|
from reflex.vars import Var, get_uuid_string_var
|
||||||
@ -22,6 +23,7 @@ LiteralAccordionDir = Literal["ltr", "rtl"]
|
|||||||
LiteralAccordionOrientation = Literal["vertical", "horizontal"]
|
LiteralAccordionOrientation = Literal["vertical", "horizontal"]
|
||||||
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
|
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
|
||||||
DEFAULT_ANIMATION_DURATION = 250
|
DEFAULT_ANIMATION_DURATION = 250
|
||||||
|
DEFAULT_ANIMATION_EASING = "cubic-bezier(0.87, 0, 0.13, 1)"
|
||||||
|
|
||||||
class AccordionComponent(RadixPrimitiveComponent):
|
class AccordionComponent(RadixPrimitiveComponent):
|
||||||
def add_style(self) -> Style | None: ...
|
def add_style(self) -> Style | None: ...
|
||||||
@ -173,6 +175,8 @@ class AccordionComponent(RadixPrimitiveComponent):
|
|||||||
...
|
...
|
||||||
|
|
||||||
class AccordionRoot(AccordionComponent):
|
class AccordionRoot(AccordionComponent):
|
||||||
|
def get_event_triggers(self) -> Dict[str, Any]: ...
|
||||||
|
def add_style(self): ...
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def create( # type: ignore
|
def create( # type: ignore
|
||||||
@ -196,12 +200,15 @@ class AccordionRoot(AccordionComponent):
|
|||||||
Literal["vertical", "horizontal"],
|
Literal["vertical", "horizontal"],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
variant: Optional[
|
radius: Optional[
|
||||||
Union[
|
Union[
|
||||||
Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
|
Var[Literal["none", "small", "medium", "large", "full"]],
|
||||||
Literal["classic", "soft", "surface", "outline", "ghost"],
|
Literal["none", "small", "medium", "large", "full"],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
duration: Optional[Union[Var[int], int]] = None,
|
||||||
|
easing: Optional[Union[Var[str], str]] = None,
|
||||||
|
show_dividers: Optional[Union[Var[bool], bool]] = None,
|
||||||
color_scheme: Optional[
|
color_scheme: Optional[
|
||||||
Union[
|
Union[
|
||||||
Var[
|
Var[
|
||||||
@ -264,6 +271,12 @@ class AccordionRoot(AccordionComponent):
|
|||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = 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,
|
||||||
@ -321,7 +334,7 @@ class AccordionRoot(AccordionComponent):
|
|||||||
] = None,
|
] = None,
|
||||||
**props
|
**props
|
||||||
) -> "AccordionRoot":
|
) -> "AccordionRoot":
|
||||||
"""Create the Accordion root component.
|
"""Create the component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*children: The children of the component.
|
*children: The children of the component.
|
||||||
@ -332,8 +345,12 @@ 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 component.
|
radius: The radius of the accordion corners.
|
||||||
|
duration: The time in milliseconds to animate open and close
|
||||||
|
easing: The easing function to use for the animation.
|
||||||
|
show_dividers: Whether to show divider lines between items.
|
||||||
color_scheme: The color scheme 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.
|
||||||
@ -341,14 +358,12 @@ class AccordionRoot(AccordionComponent):
|
|||||||
class_name: The class name for the component.
|
class_name: The class name for the component.
|
||||||
autofocus: Whether the component should take the focus once the page is loaded
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
custom_attrs: custom attribute
|
custom_attrs: custom attribute
|
||||||
**props: The properties of the component.
|
**props: The props of the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The Accordion root Component.
|
The component.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
def get_event_triggers(self) -> Dict[str, Any]: ...
|
|
||||||
def add_style(self): ...
|
|
||||||
|
|
||||||
class AccordionItem(AccordionComponent):
|
class AccordionItem(AccordionComponent):
|
||||||
@overload
|
@overload
|
||||||
@ -656,8 +671,6 @@ class AccordionHeader(AccordionComponent):
|
|||||||
...
|
...
|
||||||
def add_style(self) -> Style | None: ...
|
def add_style(self) -> Style | None: ...
|
||||||
|
|
||||||
cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
|
|
||||||
|
|
||||||
class AccordionTrigger(AccordionComponent):
|
class AccordionTrigger(AccordionComponent):
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -44,7 +44,7 @@ class FormRoot(FormComponent, HTMLForm):
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
return Style({"width": "260px"})
|
return Style({"width": "100%"})
|
||||||
|
|
||||||
|
|
||||||
class FormField(FormComponent):
|
class FormField(FormComponent):
|
||||||
|
@ -23,7 +23,8 @@ from typing import Literal, get_args
|
|||||||
from reflex.components.component import BaseComponent
|
from reflex.components.component import BaseComponent
|
||||||
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
from reflex.style import color_mode, toggle_color_mode
|
from reflex.components.radix.themes.components.switch import Switch
|
||||||
|
from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
from reflex.vars import BaseVar, Var
|
from reflex.vars import BaseVar, Var
|
||||||
|
|
||||||
@ -143,11 +144,34 @@ class ColorModeIconButton(IconButton):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ColorModeSwitch(Switch):
|
||||||
|
"""Switch for toggling light / dark mode via toggle_color_mode."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *children, **props):
|
||||||
|
"""Create a switch component bound to color_mode.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
**props: The props to pass to the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The switch component.
|
||||||
|
"""
|
||||||
|
return Switch.create(
|
||||||
|
*children,
|
||||||
|
checked=color_mode != LIGHT_COLOR_MODE,
|
||||||
|
on_change=toggle_color_mode,
|
||||||
|
**props,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ColorModeNamespace(BaseVar):
|
class ColorModeNamespace(BaseVar):
|
||||||
"""Namespace for color mode components."""
|
"""Namespace for color mode components."""
|
||||||
|
|
||||||
icon = staticmethod(ColorModeIcon.create)
|
icon = staticmethod(ColorModeIcon.create)
|
||||||
button = staticmethod(ColorModeIconButton.create)
|
button = staticmethod(ColorModeIconButton.create)
|
||||||
|
switch = staticmethod(ColorModeSwitch.create)
|
||||||
|
|
||||||
|
|
||||||
color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode))
|
color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode))
|
||||||
|
@ -12,7 +12,8 @@ from typing import Literal, get_args
|
|||||||
from reflex.components.component import BaseComponent
|
from reflex.components.component import BaseComponent
|
||||||
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
from reflex.style import color_mode, toggle_color_mode
|
from reflex.components.radix.themes.components.switch import Switch
|
||||||
|
from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
from reflex.vars import BaseVar, Var
|
from reflex.vars import BaseVar, Var
|
||||||
from .components.icon_button import IconButton
|
from .components.icon_button import IconButton
|
||||||
@ -362,8 +363,184 @@ class ColorModeIconButton(IconButton):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class ColorModeSwitch(Switch):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
default_checked: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
checked: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
required: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
name: Optional[Union[Var[str], str]] = None,
|
||||||
|
value: Optional[Union[Var[str], str]] = None,
|
||||||
|
size: Optional[
|
||||||
|
Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]]
|
||||||
|
] = None,
|
||||||
|
variant: Optional[
|
||||||
|
Union[
|
||||||
|
Var[Literal["classic", "surface", "soft"]],
|
||||||
|
Literal["classic", "surface", "soft"],
|
||||||
|
]
|
||||||
|
] = 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,
|
||||||
|
high_contrast: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
radius: Optional[
|
||||||
|
Union[
|
||||||
|
Var[Literal["none", "small", "full"]], Literal["none", "small", "full"]
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
style: Optional[Style] = None,
|
||||||
|
key: Optional[Any] = None,
|
||||||
|
id: Optional[Any] = None,
|
||||||
|
class_name: Optional[Any] = None,
|
||||||
|
autofocus: Optional[bool] = None,
|
||||||
|
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||||
|
on_blur: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_change: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_click: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_context_menu: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_double_click: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_focus: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mount: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_down: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_enter: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_leave: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_move: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_out: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_over: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_mouse_up: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_scroll: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
on_unmount: Optional[
|
||||||
|
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||||
|
] = None,
|
||||||
|
**props
|
||||||
|
) -> "ColorModeSwitch":
|
||||||
|
"""Create a switch component bound to color_mode.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.
|
||||||
|
default_checked: Whether the switch is checked by default
|
||||||
|
checked: Whether the switch is checked
|
||||||
|
disabled: If true, prevent the user from interacting with the switch
|
||||||
|
required: If true, the user must interact with the switch to submit the form
|
||||||
|
name: The name of the switch (when submitting a form)
|
||||||
|
value: The value associated with the "on" position
|
||||||
|
size: Switch size "1" - "4"
|
||||||
|
variant: Variant of switch: "classic" | "surface" | "soft"
|
||||||
|
color_scheme: Override theme color for switch
|
||||||
|
high_contrast: Whether to render the switch with higher contrast color against background
|
||||||
|
radius: Override theme radius for switch: "none" | "small" | "full"
|
||||||
|
style: The style of the component.
|
||||||
|
key: A unique key for the component.
|
||||||
|
id: The id for the component.
|
||||||
|
class_name: The class name for the component.
|
||||||
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
|
custom_attrs: custom attribute
|
||||||
|
**props: The props to pass to the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The switch component.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
class ColorModeNamespace(BaseVar):
|
class ColorModeNamespace(BaseVar):
|
||||||
icon = staticmethod(ColorModeIcon.create)
|
icon = staticmethod(ColorModeIcon.create)
|
||||||
button = staticmethod(ColorModeIconButton.create)
|
button = staticmethod(ColorModeIconButton.create)
|
||||||
|
switch = staticmethod(ColorModeSwitch.create)
|
||||||
|
|
||||||
color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode))
|
color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode))
|
||||||
|
@ -20,7 +20,6 @@ from reflex import constants
|
|||||||
from reflex.config import get_config
|
from reflex.config import get_config
|
||||||
from reflex.constants import CustomComponents
|
from reflex.constants import CustomComponents
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
from reflex.utils.pyi_generator import PyiGenerator
|
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
custom_components_cli = typer.Typer()
|
custom_components_cli = typer.Typer()
|
||||||
@ -438,6 +437,8 @@ def _run_commands_in_subprocess(cmds: list[str]) -> bool:
|
|||||||
|
|
||||||
def _make_pyi_files():
|
def _make_pyi_files():
|
||||||
"""Create pyi files for the custom component."""
|
"""Create pyi files for the custom component."""
|
||||||
|
from reflex.utils.pyi_generator import PyiGenerator
|
||||||
|
|
||||||
package_name = _get_package_config()["project"]["name"]
|
package_name = _get_package_config()["project"]["name"]
|
||||||
|
|
||||||
for dir, _, _ in os.walk(f"./{package_name}"):
|
for dir, _, _ in os.walk(f"./{package_name}"):
|
||||||
|
@ -180,12 +180,15 @@ class Style(dict):
|
|||||||
style_dict: The style dictionary.
|
style_dict: The style dictionary.
|
||||||
kwargs: Other key value pairs to apply to the dict update.
|
kwargs: Other key value pairs to apply to the dict update.
|
||||||
"""
|
"""
|
||||||
if kwargs:
|
|
||||||
style_dict = {**(style_dict or {}), **kwargs}
|
|
||||||
if not isinstance(style_dict, Style):
|
if not isinstance(style_dict, Style):
|
||||||
converted_dict = type(self)(style_dict)
|
converted_dict = type(self)(style_dict)
|
||||||
else:
|
else:
|
||||||
converted_dict = style_dict
|
converted_dict = style_dict
|
||||||
|
if kwargs:
|
||||||
|
if converted_dict is None:
|
||||||
|
converted_dict = type(self)(kwargs)
|
||||||
|
else:
|
||||||
|
converted_dict.update(kwargs)
|
||||||
# Combine our VarData with that of any Vars in the style_dict that was passed.
|
# Combine our VarData with that of any Vars in the style_dict that was passed.
|
||||||
self._var_data = VarData.merge(self._var_data, converted_dict._var_data)
|
self._var_data = VarData.merge(self._var_data, converted_dict._var_data)
|
||||||
super().update(converted_dict)
|
super().update(converted_dict)
|
||||||
|
@ -181,7 +181,12 @@ def get_install_package_manager() -> str | None:
|
|||||||
Returns:
|
Returns:
|
||||||
The path to the package manager.
|
The path to the package manager.
|
||||||
"""
|
"""
|
||||||
if constants.IS_WINDOWS and not is_windows_bun_supported():
|
if (
|
||||||
|
constants.IS_WINDOWS
|
||||||
|
and not is_windows_bun_supported()
|
||||||
|
or windows_check_onedrive_in_path()
|
||||||
|
or windows_npm_escape_hatch()
|
||||||
|
):
|
||||||
return get_package_manager()
|
return get_package_manager()
|
||||||
return get_config().bun_path
|
return get_config().bun_path
|
||||||
|
|
||||||
@ -199,6 +204,24 @@ def get_package_manager() -> str | None:
|
|||||||
return npm_path
|
return npm_path
|
||||||
|
|
||||||
|
|
||||||
|
def windows_check_onedrive_in_path() -> bool:
|
||||||
|
"""For windows, check if oneDrive is present in the project dir path.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
If oneDrive is in the path of the project directory.
|
||||||
|
"""
|
||||||
|
return "onedrive" in str(Path.cwd()).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def windows_npm_escape_hatch() -> bool:
|
||||||
|
"""For windows, if the user sets REFLEX_USE_NPM, use npm instead of bun.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
If the user has set REFLEX_USE_NPM.
|
||||||
|
"""
|
||||||
|
return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"]
|
||||||
|
|
||||||
|
|
||||||
def get_app(reload: bool = False) -> ModuleType:
|
def get_app(reload: bool = False) -> ModuleType:
|
||||||
"""Get the app module based on the default config.
|
"""Get the app module based on the default config.
|
||||||
|
|
||||||
@ -737,10 +760,17 @@ def install_bun():
|
|||||||
Raises:
|
Raises:
|
||||||
FileNotFoundError: If required packages are not found.
|
FileNotFoundError: If required packages are not found.
|
||||||
"""
|
"""
|
||||||
if constants.IS_WINDOWS and not is_windows_bun_supported():
|
win_supported = is_windows_bun_supported()
|
||||||
console.warn(
|
one_drive_in_path = windows_check_onedrive_in_path()
|
||||||
"Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm."
|
if constants.IS_WINDOWS and not win_supported or one_drive_in_path:
|
||||||
)
|
if not win_supported:
|
||||||
|
console.warn(
|
||||||
|
"Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm."
|
||||||
|
)
|
||||||
|
if one_drive_in_path:
|
||||||
|
console.warn(
|
||||||
|
"Creating project directories in OneDrive is not recommended for bun usage on windows. This will fallback to npm."
|
||||||
|
)
|
||||||
|
|
||||||
# Skip if bun is already installed.
|
# Skip if bun is already installed.
|
||||||
if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse(
|
if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse(
|
||||||
@ -843,6 +873,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|||||||
if not constants.IS_WINDOWS
|
if not constants.IS_WINDOWS
|
||||||
or constants.IS_WINDOWS
|
or constants.IS_WINDOWS
|
||||||
and is_windows_bun_supported()
|
and is_windows_bun_supported()
|
||||||
|
and not windows_check_onedrive_in_path()
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
processes.run_process_with_fallback(
|
processes.run_process_with_fallback(
|
||||||
@ -929,6 +960,11 @@ def needs_reinit(frontend: bool = True) -> bool:
|
|||||||
console.warn(
|
console.warn(
|
||||||
f"""On Python < 3.12, `uvicorn==0.20.0` is recommended for improved hot reload times. Found {uvi_ver} instead."""
|
f"""On Python < 3.12, `uvicorn==0.20.0` is recommended for improved hot reload times. Found {uvi_ver} instead."""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if windows_check_onedrive_in_path():
|
||||||
|
console.warn(
|
||||||
|
"Creating project directories in OneDrive may lead to performance issues. For optimal performance, It is recommended to avoid using OneDrive for your reflex app."
|
||||||
|
)
|
||||||
# No need to reinitialize if the app is already initialized.
|
# No need to reinitialize if the app is already initialized.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -2,17 +2,11 @@ from typing import Dict, List, Set, Tuple, Union
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from reflex.components import box, foreach, text
|
from reflex.components import box, el, foreach, text
|
||||||
from reflex.components.core import Foreach
|
from reflex.components.core.foreach import Foreach, ForeachRenderError, ForeachVarError
|
||||||
from reflex.state import BaseState
|
from reflex.state import BaseState
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
try:
|
|
||||||
# When pydantic v2 is installed
|
|
||||||
from pydantic.v1 import ValidationError # type: ignore
|
|
||||||
except ImportError:
|
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
|
|
||||||
class ForEachState(BaseState):
|
class ForEachState(BaseState):
|
||||||
"""A state for testing the ForEach component."""
|
"""A state for testing the ForEach component."""
|
||||||
@ -84,12 +78,12 @@ def display_nested_color_with_shades_v2(color):
|
|||||||
|
|
||||||
def display_color_tuple(color):
|
def display_color_tuple(color):
|
||||||
assert color._var_type == str
|
assert color._var_type == str
|
||||||
return box(text(color, "tuple"))
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
def display_colors_set(color):
|
def display_colors_set(color):
|
||||||
assert color._var_type == str
|
assert color._var_type == str
|
||||||
return box(text(color, "set"))
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
def display_nested_list_element(element: Var[str], index: Var[int]):
|
def display_nested_list_element(element: Var[str], index: Var[int]):
|
||||||
@ -100,7 +94,7 @@ def display_nested_list_element(element: Var[str], index: Var[int]):
|
|||||||
|
|
||||||
def display_color_index_tuple(color):
|
def display_color_index_tuple(color):
|
||||||
assert color._var_type == Union[int, str]
|
assert color._var_type == Union[int, str]
|
||||||
return box(text(color, "index_tuple"))
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
seen_index_vars = set()
|
seen_index_vars = set()
|
||||||
@ -215,24 +209,46 @@ def test_foreach_render(state_var, render_fn, render_dict):
|
|||||||
|
|
||||||
# Make sure the index vars are unique.
|
# Make sure the index vars are unique.
|
||||||
arg_index = rend["arg_index"]
|
arg_index = rend["arg_index"]
|
||||||
|
assert isinstance(arg_index, Var)
|
||||||
assert arg_index._var_name not in seen_index_vars
|
assert arg_index._var_name not in seen_index_vars
|
||||||
assert arg_index._var_type == int
|
assert arg_index._var_type == int
|
||||||
seen_index_vars.add(arg_index._var_name)
|
seen_index_vars.add(arg_index._var_name)
|
||||||
|
|
||||||
|
|
||||||
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 ForeachVarError if the iterable is of type Any."""
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(ForeachVarError):
|
||||||
Foreach.create(
|
Foreach.create(
|
||||||
ForEachState.bad_annotation_list, # type: ignore
|
ForEachState.bad_annotation_list,
|
||||||
lambda sublist: Foreach.create(sublist, lambda color: text(color)),
|
lambda sublist: Foreach.create(sublist, lambda color: text(color)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_foreach_no_param_in_signature():
|
def test_foreach_no_param_in_signature():
|
||||||
"""Test that the foreach component raises a TypeError if no parameters are passed."""
|
"""Test that the foreach component raises a ForeachRenderError if no parameters are passed."""
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ForeachRenderError):
|
||||||
Foreach.create(
|
Foreach.create(
|
||||||
ForEachState.colors_list, # type: ignore
|
ForEachState.colors_list,
|
||||||
lambda: text("color"),
|
lambda: text("color"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_foreach_too_many_params_in_signature():
|
||||||
|
"""Test that the foreach component raises a ForeachRenderError if too many parameters are passed."""
|
||||||
|
with pytest.raises(ForeachRenderError):
|
||||||
|
Foreach.create(
|
||||||
|
ForEachState.colors_list,
|
||||||
|
lambda color, index, extra: text(color),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_foreach_component_styles():
|
||||||
|
"""Test that the foreach component works with global component styles."""
|
||||||
|
component = el.div(
|
||||||
|
foreach(
|
||||||
|
ForEachState.colors_list,
|
||||||
|
display_color,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
component._add_style_recursive({box: {"color": "red"}})
|
||||||
|
assert 'css={{"color": "red"}}' in str(component)
|
||||||
|
@ -1951,3 +1951,73 @@ def test_component_add_custom_code():
|
|||||||
"const custom_code5 = 46",
|
"const custom_code5 = 46",
|
||||||
"const custom_code6 = 47",
|
"const custom_code6 = 47",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_style_embedded_vars(test_state: BaseState):
|
||||||
|
"""Test that add_style works with embedded vars when returning a plain dict.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
test_state: A test state.
|
||||||
|
"""
|
||||||
|
v0 = Var.create_safe("parent")._replace(
|
||||||
|
merge_var_data=VarData(hooks={"useParent": None}), # type: ignore
|
||||||
|
)
|
||||||
|
v1 = rx.color("plum", 10)
|
||||||
|
v2 = Var.create_safe("text")._replace(
|
||||||
|
merge_var_data=VarData(hooks={"useText": None}), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
class ParentComponent(Component):
|
||||||
|
def add_style(self):
|
||||||
|
return Style(
|
||||||
|
{
|
||||||
|
"fake_parent": v0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class StyledComponent(ParentComponent):
|
||||||
|
tag = "StyledComponent"
|
||||||
|
|
||||||
|
def add_style(self):
|
||||||
|
return {
|
||||||
|
"color": v1,
|
||||||
|
"fake": v2,
|
||||||
|
"margin": f"{test_state.num}%",
|
||||||
|
}
|
||||||
|
|
||||||
|
page = rx.vstack(StyledComponent.create())
|
||||||
|
page._add_style_recursive(Style())
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"const test_state = useContext(StateContexts.test_state)"
|
||||||
|
in page._get_all_hooks_internal()
|
||||||
|
)
|
||||||
|
assert "useText" in page._get_all_hooks_internal()
|
||||||
|
assert "useParent" in page._get_all_hooks_internal()
|
||||||
|
assert (
|
||||||
|
str(page).count(
|
||||||
|
'css={{"fakeParent": "parent", "color": "var(--plum-10)", "fake": "text", "margin": `${test_state.num}%`}}'
|
||||||
|
)
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_style_foreach():
|
||||||
|
class StyledComponent(Component):
|
||||||
|
tag = "StyledComponent"
|
||||||
|
ix: Var[int]
|
||||||
|
|
||||||
|
def add_style(self):
|
||||||
|
return Style({"color": "red"})
|
||||||
|
|
||||||
|
page = rx.vstack(rx.foreach(Var.range(3), lambda i: StyledComponent.create(i)))
|
||||||
|
page._add_style_recursive(Style())
|
||||||
|
|
||||||
|
# Expect only a single child of the foreach on the python side
|
||||||
|
assert len(page.children[0].children) == 1
|
||||||
|
|
||||||
|
# Expect the style to be added to the child of the foreach
|
||||||
|
assert 'css={{"color": "red"}}' in str(page.children[0].children[0])
|
||||||
|
|
||||||
|
# Expect only one instance of this CSS dict in the rendered page
|
||||||
|
assert str(page).count('css={{"color": "red"}}') == 1
|
||||||
|
@ -8,7 +8,7 @@ import reflex as rx
|
|||||||
from reflex import style
|
from reflex import style
|
||||||
from reflex.components.component import evaluate_style_namespaces
|
from reflex.components.component import evaluate_style_namespaces
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var, VarData
|
||||||
|
|
||||||
test_style = [
|
test_style = [
|
||||||
({"a": 1}, {"a": 1}),
|
({"a": 1}, {"a": 1}),
|
||||||
@ -503,3 +503,25 @@ def test_evaluate_style_namespaces():
|
|||||||
assert rx.text.__call__ not in style_dict
|
assert rx.text.__call__ not in style_dict
|
||||||
style_dict = evaluate_style_namespaces(style_dict) # type: ignore
|
style_dict = evaluate_style_namespaces(style_dict) # type: ignore
|
||||||
assert rx.text.__call__ in style_dict
|
assert rx.text.__call__ in style_dict
|
||||||
|
|
||||||
|
|
||||||
|
def test_style_update_with_var_data():
|
||||||
|
"""Test that .update with a Style containing VarData works."""
|
||||||
|
red_var = Var.create_safe("red")._replace(
|
||||||
|
merge_var_data=VarData(hooks={"const red = true": None}), # type: ignore
|
||||||
|
)
|
||||||
|
blue_var = Var.create_safe("blue", _var_is_local=False)._replace(
|
||||||
|
merge_var_data=VarData(hooks={"const blue = true": None}), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
s1 = Style(
|
||||||
|
{
|
||||||
|
"color": red_var,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
s2 = Style()
|
||||||
|
s2.update(s1, background_color=f"{blue_var}ish")
|
||||||
|
assert s2 == {"color": "red", "backgroundColor": "`${blue}ish`"}
|
||||||
|
assert s2._var_data is not None
|
||||||
|
assert "const red = true" in s2._var_data.hooks
|
||||||
|
assert "const blue = true" in s2._var_data.hooks
|
||||||
|
Loading…
Reference in New Issue
Block a user