cond.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. """Create a list of components from an iterable."""
  2. from __future__ import annotations
  3. from typing import Any, Dict, Optional, overload
  4. from reflex.components.base.fragment import Fragment
  5. from reflex.components.component import BaseComponent, Component, MemoizationLeaf
  6. from reflex.components.tags import CondTag, Tag
  7. from reflex.constants import Dirs
  8. from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
  9. from reflex.utils.imports import ImportDict, ImportVar
  10. from reflex.utils.types import infallible_issubclass
  11. from reflex.vars import VarData
  12. from reflex.vars.base import LiteralVar, ReflexCallable, Var
  13. from reflex.vars.function import ArgsFunctionOperation
  14. from reflex.vars.number import ternary_operation
  15. _IS_TRUE_IMPORT: ImportDict = {
  16. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
  17. }
  18. class Cond(MemoizationLeaf):
  19. """Render one of two components based on a condition."""
  20. # The cond to determine which component to render.
  21. cond: Var[Any]
  22. # The component to render if the cond is true.
  23. comp1: BaseComponent = None # type: ignore
  24. # The component to render if the cond is false.
  25. comp2: BaseComponent = None # type: ignore
  26. @classmethod
  27. def create(
  28. cls,
  29. cond: Var,
  30. comp1: BaseComponent,
  31. comp2: Optional[BaseComponent] = None,
  32. ) -> Component:
  33. """Create a conditional component.
  34. Args:
  35. cond: The cond to determine which component to render.
  36. comp1: The component to render if the cond is true.
  37. comp2: The component to render if the cond is false.
  38. Returns:
  39. The conditional component.
  40. """
  41. # Wrap everything in fragments.
  42. if type(comp1).__name__ != "Fragment":
  43. comp1 = Fragment.create(comp1)
  44. if comp2 is None or type(comp2).__name__ != "Fragment":
  45. comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
  46. return Fragment.create(
  47. cls(
  48. cond=cond,
  49. comp1=comp1,
  50. comp2=comp2,
  51. children=[comp1, comp2],
  52. )
  53. )
  54. def _get_props_imports(self):
  55. """Get the imports needed for component's props.
  56. Returns:
  57. The imports for the component's props of the component.
  58. """
  59. return []
  60. def _render(self) -> Tag:
  61. return CondTag(
  62. cond=self.cond,
  63. true_value=self.comp1.render(),
  64. false_value=self.comp2.render(),
  65. )
  66. def render(self) -> Dict:
  67. """Render the component.
  68. Returns:
  69. The dictionary for template of component.
  70. """
  71. tag = self._render()
  72. return dict(
  73. tag.add_props(
  74. **self.event_triggers,
  75. key=self.key,
  76. sx=self.style,
  77. id=self.id,
  78. class_name=self.class_name,
  79. ).set(
  80. props=tag.format_props(),
  81. ),
  82. cond_state=f"isTrue({self.cond!s})",
  83. )
  84. def add_imports(self) -> ImportDict:
  85. """Add imports for the Cond component.
  86. Returns:
  87. The import dict for the component.
  88. """
  89. var_data = VarData.merge(self.cond._get_all_var_data())
  90. imports = var_data.old_school_imports() if var_data else {}
  91. return {**imports, **_IS_TRUE_IMPORT}
  92. @overload
  93. def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
  94. @overload
  95. def cond(condition: Any, c1: Component) -> Component: ...
  96. @overload
  97. def cond(condition: Any, c1: Any, c2: Any) -> Var: ...
  98. def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
  99. """Create a conditional component or Prop.
  100. Args:
  101. condition: The cond to determine which component to render.
  102. c1: The component or prop to render if the cond_var is true.
  103. c2: The component or prop to render if the cond_var is false.
  104. Returns:
  105. The conditional component.
  106. Raises:
  107. ValueError: If the arguments are invalid.
  108. """
  109. # Convert the condition to a Var.
  110. cond_var = LiteralVar.create(condition)
  111. # If the first component is a component, create a Fragment if the second component is not set.
  112. if isinstance(c1, BaseComponent) or (
  113. isinstance(c1, Var) and infallible_issubclass(c1._var_type, BaseComponent)
  114. ):
  115. c2 = c2 if c2 is not None else Fragment.create()
  116. # Check that the second argument is valid.
  117. if c2 is None:
  118. raise ValueError("For conditional vars, the second argument must be set.")
  119. c1 = Var.create(c1)
  120. c2 = Var.create(c2)
  121. # Create the conditional var.
  122. return ternary_operation(
  123. cond_var.bool(),
  124. ArgsFunctionOperation.create(
  125. (),
  126. c1,
  127. _var_type=ReflexCallable[[], c1._var_type],
  128. ),
  129. ArgsFunctionOperation.create(
  130. (),
  131. c2,
  132. _var_type=ReflexCallable[[], c2._var_type],
  133. ),
  134. ).call()
  135. @overload
  136. def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore
  137. @overload
  138. def color_mode_cond(light: Any, dark: Any = None) -> Var: ...
  139. def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
  140. """Create a component or Prop based on color_mode.
  141. Args:
  142. light: The component or prop to render if color_mode is default
  143. dark: The component or prop to render if color_mode is non-default
  144. Returns:
  145. The conditional component or prop.
  146. """
  147. return cond(
  148. resolved_color_mode == LiteralVar.create(LIGHT_COLOR_MODE),
  149. light,
  150. dark,
  151. )