cond.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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.constants.colors import Color
  9. from reflex.style import LIGHT_COLOR_MODE, color_mode
  10. from reflex.utils import format, imports
  11. from reflex.vars import BaseVar, Var, VarData
  12. _IS_TRUE_IMPORT = {
  13. f"/{Dirs.STATE_PATH}": {imports.ImportVar(tag="isTrue")},
  14. }
  15. class Cond(MemoizationLeaf):
  16. """Render one of two components based on a condition."""
  17. # The cond to determine which component to render.
  18. cond: Var[Any]
  19. # The component to render if the cond is true.
  20. comp1: BaseComponent = None # type: ignore
  21. # The component to render if the cond is false.
  22. comp2: BaseComponent = None # type: ignore
  23. @classmethod
  24. def create(
  25. cls,
  26. cond: Var,
  27. comp1: BaseComponent,
  28. comp2: Optional[BaseComponent] = None,
  29. ) -> Component:
  30. """Create a conditional component.
  31. Args:
  32. cond: The cond to determine which component to render.
  33. comp1: The component to render if the cond is true.
  34. comp2: The component to render if the cond is false.
  35. Returns:
  36. The conditional component.
  37. """
  38. # Wrap everything in fragments.
  39. if comp1.__class__.__name__ != "Fragment":
  40. comp1 = Fragment.create(comp1)
  41. if comp2 is None or comp2.__class__.__name__ != "Fragment":
  42. comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
  43. return Fragment.create(
  44. cls(
  45. cond=cond,
  46. comp1=comp1,
  47. comp2=comp2,
  48. children=[comp1, comp2],
  49. )
  50. )
  51. def _get_props_imports(self):
  52. """Get the imports needed for component's props.
  53. Returns:
  54. The imports for the component's props of the component.
  55. """
  56. return []
  57. def _render(self) -> Tag:
  58. return CondTag(
  59. cond=self.cond,
  60. true_value=self.comp1.render(),
  61. false_value=self.comp2.render(),
  62. )
  63. def render(self) -> Dict:
  64. """Render the component.
  65. Returns:
  66. The dictionary for template of component.
  67. """
  68. tag = self._render()
  69. return dict(
  70. tag.add_props(
  71. **self.event_triggers,
  72. key=self.key,
  73. sx=self.style,
  74. id=self.id,
  75. class_name=self.class_name,
  76. ).set(
  77. props=tag.format_props(),
  78. ),
  79. cond_state=f"isTrue({self.cond._var_full_name})",
  80. )
  81. def _get_imports(self) -> imports.ImportDict:
  82. return imports.merge_imports(
  83. super()._get_imports(),
  84. getattr(self.cond._var_data, "imports", {}),
  85. _IS_TRUE_IMPORT,
  86. )
  87. def _apply_theme(self, theme: Component):
  88. """Apply the theme to this component.
  89. Args:
  90. theme: The theme to apply.
  91. """
  92. self.comp1.apply_theme(theme) # type: ignore
  93. self.comp2.apply_theme(theme) # type: ignore
  94. @overload
  95. def cond(condition: Any, c1: Component, c2: Any) -> Component:
  96. ...
  97. @overload
  98. def cond(condition: Any, c1: Component) -> Component:
  99. ...
  100. @overload
  101. def cond(condition: Any, c1: Any, c2: Any) -> Var:
  102. ...
  103. def cond(condition: Any, c1: Any, c2: Any = None):
  104. """Create a conditional component or Prop.
  105. Args:
  106. condition: The cond to determine which component to render.
  107. c1: The component or prop to render if the cond_var is true.
  108. c2: The component or prop to render if the cond_var is false.
  109. Returns:
  110. The conditional component.
  111. Raises:
  112. ValueError: If the arguments are invalid.
  113. """
  114. var_datas: list[VarData | None] = [
  115. VarData( # type: ignore
  116. imports=_IS_TRUE_IMPORT,
  117. ),
  118. ]
  119. # Convert the condition to a Var.
  120. cond_var = Var.create(condition)
  121. assert cond_var is not None, "The condition must be set."
  122. # If the first component is a component, create a Cond component.
  123. if isinstance(c1, BaseComponent):
  124. assert c2 is None or isinstance(
  125. c2, BaseComponent
  126. ), "Both arguments must be components."
  127. return Cond.create(cond_var, c1, c2)
  128. if isinstance(c1, Var):
  129. var_datas.append(c1._var_data)
  130. # Otherwise, create a conditional Var.
  131. # Check that the second argument is valid.
  132. if isinstance(c2, BaseComponent):
  133. raise ValueError("Both arguments must be props.")
  134. if c2 is None:
  135. raise ValueError("For conditional vars, the second argument must be set.")
  136. if isinstance(c2, Var):
  137. var_datas.append(c2._var_data)
  138. def create_var(cond_part):
  139. return Var.create_safe(
  140. cond_part,
  141. _var_is_string=type(cond_part) is str or isinstance(cond_part, Color),
  142. )
  143. # convert the truth and false cond parts into vars so the _var_data can be obtained.
  144. c1 = create_var(c1)
  145. c2 = create_var(c2)
  146. var_datas.extend([c1._var_data, c2._var_data])
  147. # Create the conditional var.
  148. return cond_var._replace(
  149. _var_name=format.format_cond(
  150. cond=cond_var._var_full_name,
  151. true_value=c1,
  152. false_value=c2,
  153. is_prop=True,
  154. ),
  155. _var_type=c1._var_type if isinstance(c1, BaseVar) else type(c1),
  156. _var_is_local=False,
  157. _var_full_name_needs_state_prefix=False,
  158. merge_var_data=VarData.merge(*var_datas),
  159. )
  160. def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
  161. """Create a component or Prop based on color_mode.
  162. Args:
  163. light: The component or prop to render if color_mode is default
  164. dark: The component or prop to render if color_mode is non-default
  165. Returns:
  166. The conditional component or prop.
  167. """
  168. return cond(
  169. color_mode == LIGHT_COLOR_MODE,
  170. light,
  171. dark,
  172. )