base.py 8.7 KB

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