base.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. """Base classes for radix-themes components."""
  2. from __future__ import annotations
  3. from typing import Any, ClassVar, Literal
  4. from reflex.components import Component
  5. from reflex.components.core.breakpoints import Responsive
  6. from reflex.components.tags import Tag
  7. from reflex.utils.imports import ImportDict, ImportVar
  8. from reflex.vars.base import Var
  9. LiteralAlign = Literal["start", "center", "end", "baseline", "stretch"]
  10. LiteralJustify = Literal["start", "center", "end", "between"]
  11. LiteralSpacing = Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  12. LiteralVariant = Literal["classic", "solid", "soft", "surface", "outline", "ghost"]
  13. LiteralAppearance = Literal["inherit", "light", "dark"]
  14. LiteralGrayColor = Literal["gray", "mauve", "slate", "sage", "olive", "sand", "auto"]
  15. LiteralPanelBackground = Literal["solid", "translucent"]
  16. LiteralRadius = Literal["none", "small", "medium", "large", "full"]
  17. LiteralScaling = Literal["90%", "95%", "100%", "105%", "110%"]
  18. LiteralAccentColor = Literal[
  19. "tomato",
  20. "red",
  21. "ruby",
  22. "crimson",
  23. "pink",
  24. "plum",
  25. "purple",
  26. "violet",
  27. "iris",
  28. "indigo",
  29. "blue",
  30. "cyan",
  31. "teal",
  32. "jade",
  33. "green",
  34. "grass",
  35. "brown",
  36. "orange",
  37. "sky",
  38. "mint",
  39. "lime",
  40. "yellow",
  41. "amber",
  42. "gold",
  43. "bronze",
  44. "gray",
  45. ]
  46. class CommonMarginProps(Component):
  47. """Many radix-themes elements accept shorthand margin props."""
  48. # Margin: "0" - "9" # noqa: ERA001
  49. m: Var[LiteralSpacing]
  50. # Margin horizontal: "0" - "9"
  51. mx: Var[LiteralSpacing]
  52. # Margin vertical: "0" - "9"
  53. my: Var[LiteralSpacing]
  54. # Margin top: "0" - "9"
  55. mt: Var[LiteralSpacing]
  56. # Margin right: "0" - "9"
  57. mr: Var[LiteralSpacing]
  58. # Margin bottom: "0" - "9"
  59. mb: Var[LiteralSpacing]
  60. # Margin left: "0" - "9"
  61. ml: Var[LiteralSpacing]
  62. class CommonPaddingProps(Component):
  63. """Many radix-themes elements accept shorthand padding props."""
  64. # Padding: "0" - "9" # noqa: ERA001
  65. p: Var[Responsive[LiteralSpacing]]
  66. # Padding horizontal: "0" - "9"
  67. px: Var[Responsive[LiteralSpacing]]
  68. # Padding vertical: "0" - "9"
  69. py: Var[Responsive[LiteralSpacing]]
  70. # Padding top: "0" - "9"
  71. pt: Var[Responsive[LiteralSpacing]]
  72. # Padding right: "0" - "9"
  73. pr: Var[Responsive[LiteralSpacing]]
  74. # Padding bottom: "0" - "9"
  75. pb: Var[Responsive[LiteralSpacing]]
  76. # Padding left: "0" - "9"
  77. pl: Var[Responsive[LiteralSpacing]]
  78. class RadixLoadingProp(Component):
  79. """Base class for components that can be in a loading state."""
  80. # If set, show an rx.spinner instead of the component children.
  81. loading: Var[bool]
  82. class RadixThemesComponent(Component):
  83. """Base class for all @radix-ui/themes components."""
  84. library = "@radix-ui/themes@3.2.1"
  85. # "Fake" prop color_scheme is used to avoid shadowing CSS prop "color".
  86. _rename_props: ClassVar[dict[str, str]] = {"colorScheme": "color"}
  87. @classmethod
  88. def create(
  89. cls,
  90. *children,
  91. **props,
  92. ) -> Component:
  93. """Create a new component instance.
  94. Will prepend "RadixThemes" to the component tag to avoid conflicts with
  95. other UI libraries for common names, like Text and Button.
  96. Args:
  97. *children: Child components.
  98. **props: Component properties.
  99. Returns:
  100. A new component instance.
  101. """
  102. component = super().create(*children, **props)
  103. if component.library is None:
  104. component.library = RadixThemesComponent.get_fields()[
  105. "library"
  106. ].default_value()
  107. component.alias = "RadixThemes" + (component.tag or type(component).__name__)
  108. return component
  109. @staticmethod
  110. def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
  111. return {
  112. (45, "RadixThemesColorModeProvider"): RadixThemesColorModeProvider.create(),
  113. }
  114. class RadixThemesTriggerComponent(RadixThemesComponent):
  115. """Base class for Trigger, Close, Cancel, and Accept components.
  116. These components trigger some action in an overlay component that depends on the
  117. on_click event, and thus if a child is provided and has on_click specified, it
  118. will overtake the internal action, unless it is wrapped in some inert component,
  119. in this case, a Flex.
  120. """
  121. @classmethod
  122. def create(cls, *children: Any, **props: Any) -> Component:
  123. """Create a new RadixThemesTriggerComponent instance.
  124. Args:
  125. children: The children of the component.
  126. props: The properties of the component.
  127. Returns:
  128. The new RadixThemesTriggerComponent instance.
  129. """
  130. from .layout.flex import Flex
  131. for child in children:
  132. if "on_click" in getattr(child, "event_triggers", {}):
  133. children = (Flex.create(*children),)
  134. break
  135. return super().create(*children, **props)
  136. class Theme(RadixThemesComponent):
  137. """A theme provider for radix components.
  138. This should be applied as `App.theme` to apply the theme to all radix
  139. components in the app with the given settings.
  140. It can also be used in a normal page to apply specified properties to all
  141. child elements as an override of the main theme.
  142. """
  143. tag = "Theme"
  144. # Whether to apply the themes background color to the theme node. Defaults to True.
  145. has_background: Var[bool]
  146. # Override light or dark mode theme: "inherit" | "light" | "dark". Defaults to "inherit".
  147. appearance: Var[LiteralAppearance]
  148. # The color used for default buttons, typography, backgrounds, etc
  149. accent_color: Var[LiteralAccentColor]
  150. # The shade of gray, defaults to "auto".
  151. gray_color: Var[LiteralGrayColor]
  152. # Whether panel backgrounds are translucent: "solid" | "translucent" (default)
  153. panel_background: Var[LiteralPanelBackground]
  154. # Element border radius: "none" | "small" | "medium" | "large" | "full". Defaults to "medium".
  155. radius: Var[LiteralRadius]
  156. # Scale of all theme items: "90%" | "95%" | "100%" | "105%" | "110%". Defaults to "100%"
  157. scaling: Var[LiteralScaling]
  158. @classmethod
  159. def create(
  160. cls,
  161. *children,
  162. color_mode: LiteralAppearance | None = None,
  163. theme_panel: bool = False,
  164. **props,
  165. ) -> Component:
  166. """Create a new Radix Theme specification.
  167. Args:
  168. *children: Child components.
  169. color_mode: Map to appearance prop.
  170. theme_panel: Whether to include a panel for editing the theme.
  171. **props: Component properties.
  172. Returns:
  173. A new component instance.
  174. """
  175. if color_mode is not None:
  176. props["appearance"] = color_mode
  177. if theme_panel:
  178. children = [ThemePanel.create(), *children]
  179. return super().create(*children, **props)
  180. def add_imports(self) -> ImportDict | list[ImportDict]:
  181. """Add imports for the Theme component.
  182. Returns:
  183. The import dict.
  184. """
  185. return {
  186. "$/utils/theme.js": [ImportVar(tag="theme", is_default=True)],
  187. }
  188. def _render(self, props: dict[str, Any] | None = None) -> Tag:
  189. tag = super()._render(props)
  190. tag.add_props(
  191. css=Var(
  192. _js_expr="{...theme.styles.global[':root'], ...theme.styles.global.body}"
  193. ),
  194. )
  195. tag.remove_props("appearance")
  196. return tag
  197. class ThemePanel(RadixThemesComponent):
  198. """Visual editor for creating and editing themes.
  199. Include as a child component of Theme to use in your app.
  200. """
  201. tag = "ThemePanel"
  202. # Whether the panel is open. Defaults to False.
  203. default_open: Var[bool]
  204. def add_imports(self) -> dict[str, str]:
  205. """Add imports for the ThemePanel component.
  206. Returns:
  207. The import dict.
  208. """
  209. return {"react": "useEffect"}
  210. class RadixThemesColorModeProvider(Component):
  211. """Next-themes integration for radix themes components."""
  212. library = "$/components/reflex/radix_themes_color_mode_provider.js"
  213. tag = "RadixThemesColorModeProvider"
  214. is_default = True
  215. theme = Theme.create
  216. theme_panel = ThemePanel.create