base.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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.__fields__["library"].default
  105. component.alias = "RadixThemes" + (component.tag or type(component).__name__)
  106. return component
  107. @staticmethod
  108. def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
  109. return {
  110. (45, "RadixThemesColorModeProvider"): RadixThemesColorModeProvider.create(),
  111. }
  112. class RadixThemesTriggerComponent(RadixThemesComponent):
  113. """Base class for Trigger, Close, Cancel, and Accept components.
  114. These components trigger some action in an overlay component that depends on the
  115. on_click event, and thus if a child is provided and has on_click specified, it
  116. will overtake the internal action, unless it is wrapped in some inert component,
  117. in this case, a Flex.
  118. """
  119. @classmethod
  120. def create(cls, *children: Any, **props: Any) -> Component:
  121. """Create a new RadixThemesTriggerComponent instance.
  122. Args:
  123. children: The children of the component.
  124. props: The properties of the component.
  125. Returns:
  126. The new RadixThemesTriggerComponent instance.
  127. """
  128. from .layout.flex import Flex
  129. for child in children:
  130. if "on_click" in getattr(child, "event_triggers", {}):
  131. children = (Flex.create(*children),)
  132. break
  133. return super().create(*children, **props)
  134. class Theme(RadixThemesComponent):
  135. """A theme provider for radix components.
  136. This should be applied as `App.theme` to apply the theme to all radix
  137. components in the app with the given settings.
  138. It can also be used in a normal page to apply specified properties to all
  139. child elements as an override of the main theme.
  140. """
  141. tag = "Theme"
  142. # Whether to apply the themes background color to the theme node. Defaults to True.
  143. has_background: Var[bool]
  144. # Override light or dark mode theme: "inherit" | "light" | "dark". Defaults to "inherit".
  145. appearance: Var[LiteralAppearance]
  146. # The color used for default buttons, typography, backgrounds, etc
  147. accent_color: Var[LiteralAccentColor]
  148. # The shade of gray, defaults to "auto".
  149. gray_color: Var[LiteralGrayColor]
  150. # Whether panel backgrounds are translucent: "solid" | "translucent" (default)
  151. panel_background: Var[LiteralPanelBackground]
  152. # Element border radius: "none" | "small" | "medium" | "large" | "full". Defaults to "medium".
  153. radius: Var[LiteralRadius]
  154. # Scale of all theme items: "90%" | "95%" | "100%" | "105%" | "110%". Defaults to "100%"
  155. scaling: Var[LiteralScaling]
  156. @classmethod
  157. def create(
  158. cls,
  159. *children,
  160. color_mode: LiteralAppearance | None = None,
  161. theme_panel: bool = False,
  162. **props,
  163. ) -> Component:
  164. """Create a new Radix Theme specification.
  165. Args:
  166. *children: Child components.
  167. color_mode: Map to appearance prop.
  168. theme_panel: Whether to include a panel for editing the theme.
  169. **props: Component properties.
  170. Returns:
  171. A new component instance.
  172. """
  173. if color_mode is not None:
  174. props["appearance"] = color_mode
  175. if theme_panel:
  176. children = [ThemePanel.create(), *children]
  177. return super().create(*children, **props)
  178. def add_imports(self) -> ImportDict | list[ImportDict]:
  179. """Add imports for the Theme component.
  180. Returns:
  181. The import dict.
  182. """
  183. return {
  184. "$/utils/theme.js": [ImportVar(tag="theme", is_default=True)],
  185. }
  186. def _render(self, props: dict[str, Any] | None = None) -> Tag:
  187. tag = super()._render(props)
  188. tag.add_props(
  189. css=Var(
  190. _js_expr="{...theme.styles.global[':root'], ...theme.styles.global.body}"
  191. ),
  192. )
  193. tag.remove_props("appearance")
  194. return tag
  195. class ThemePanel(RadixThemesComponent):
  196. """Visual editor for creating and editing themes.
  197. Include as a child component of Theme to use in your app.
  198. """
  199. tag = "ThemePanel"
  200. # Whether the panel is open. Defaults to False.
  201. default_open: Var[bool]
  202. def add_imports(self) -> dict[str, str]:
  203. """Add imports for the ThemePanel component.
  204. Returns:
  205. The import dict.
  206. """
  207. return {"react": "useEffect"}
  208. class RadixThemesColorModeProvider(Component):
  209. """Next-themes integration for radix themes components."""
  210. library = "$/components/reflex/radix_themes_color_mode_provider.js"
  211. tag = "RadixThemesColorModeProvider"
  212. is_default = True
  213. theme = Theme.create
  214. theme_panel = ThemePanel.create