base.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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.tags import Tag
  6. from reflex.config import get_config
  7. from reflex.utils.imports import ImportVar
  8. from reflex.vars 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"
  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 RadixLoadingProp(Component):
  63. """Base class for components that can be in a loading state."""
  64. # If set, show an rx.spinner instead of the component children.
  65. loading: Var[bool]
  66. class RadixThemesComponent(Component):
  67. """Base class for all @radix-ui/themes components."""
  68. library = "@radix-ui/themes@^3.0.0"
  69. # "Fake" prop color_scheme is used to avoid shadowing CSS prop "color".
  70. _rename_props: Dict[str, str] = {"colorScheme": "color"}
  71. @classmethod
  72. def create(
  73. cls,
  74. *children,
  75. **props,
  76. ) -> Component:
  77. """Create a new component instance.
  78. Will prepend "RadixThemes" to the component tag to avoid conflicts with
  79. other UI libraries for common names, like Text and Button.
  80. Args:
  81. *children: Child components.
  82. **props: Component properties.
  83. Returns:
  84. A new component instance.
  85. """
  86. component = super().create(*children, **props)
  87. if component.library is None:
  88. component.library = RadixThemesComponent.__fields__["library"].default
  89. component.alias = "RadixThemes" + (
  90. component.tag or component.__class__.__name__
  91. )
  92. return component
  93. @staticmethod
  94. def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
  95. return {
  96. (45, "RadixThemesColorModeProvider"): RadixThemesColorModeProvider.create(),
  97. }
  98. class RadixThemesTriggerComponent(RadixThemesComponent):
  99. """Base class for Trigger, Close, Cancel, and Accept components.
  100. These components trigger some action in an overlay component that depends on the
  101. on_click event, and thus if a child is provided and has on_click specified, it
  102. will overtake the internal action, unless it is wrapped in some inert component,
  103. in this case, a Flex.
  104. """
  105. @classmethod
  106. def create(cls, *children: Any, **props: Any) -> Component:
  107. """Create a new RadixThemesTriggerComponent instance.
  108. Args:
  109. children: The children of the component.
  110. props: The properties of the component.
  111. Returns:
  112. The new RadixThemesTriggerComponent instance.
  113. """
  114. from .layout.flex import Flex
  115. for child in children:
  116. if "on_click" in getattr(child, "event_triggers", {}):
  117. children = (Flex.create(*children),)
  118. break
  119. return super().create(*children, **props)
  120. class Theme(RadixThemesComponent):
  121. """A theme provider for radix components.
  122. This should be applied as `App.theme` to apply the theme to all radix
  123. components in the app with the given settings.
  124. It can also be used in a normal page to apply specified properties to all
  125. child elements as an override of the main theme.
  126. """
  127. tag = "Theme"
  128. # Whether to apply the themes background color to the theme node. Defaults to True.
  129. has_background: Var[bool]
  130. # Override light or dark mode theme: "inherit" | "light" | "dark". Defaults to "inherit".
  131. appearance: Var[LiteralAppearance]
  132. # The color used for default buttons, typography, backgrounds, etc
  133. accent_color: Var[LiteralAccentColor]
  134. # The shade of gray, defaults to "auto".
  135. gray_color: Var[LiteralGrayColor]
  136. # Whether panel backgrounds are translucent: "solid" | "translucent" (default)
  137. panel_background: Var[LiteralPanelBackground]
  138. # Element border radius: "none" | "small" | "medium" | "large" | "full". Defaults to "medium".
  139. radius: Var[LiteralRadius]
  140. # Scale of all theme items: "90%" | "95%" | "100%" | "105%" | "110%". Defaults to "100%"
  141. scaling: Var[LiteralScaling]
  142. @classmethod
  143. def create(
  144. cls,
  145. *children,
  146. color_mode: LiteralAppearance | None = None,
  147. theme_panel: bool = False,
  148. **props,
  149. ) -> Component:
  150. """Create a new Radix Theme specification.
  151. Args:
  152. *children: Child components.
  153. color_mode: Map to appearance prop.
  154. theme_panel: Whether to include a panel for editing the theme.
  155. **props: Component properties.
  156. Returns:
  157. A new component instance.
  158. """
  159. if color_mode is not None:
  160. props["appearance"] = color_mode
  161. if theme_panel:
  162. children = [ThemePanel.create(), *children]
  163. return super().create(*children, **props)
  164. def add_imports(self) -> dict[str, list[ImportVar] | ImportVar]:
  165. """Add imports for the Theme component.
  166. Returns:
  167. The import dict.
  168. """
  169. _imports: dict[str, list[ImportVar] | ImportVar] = {
  170. "/utils/theme.js": [ImportVar(tag="theme", is_default=True)],
  171. }
  172. if get_config().tailwind is None:
  173. # When tailwind is disabled, import the radix-ui styles directly because they will
  174. # not be included in the tailwind.css file.
  175. _imports[""] = ImportVar(
  176. tag="@radix-ui/themes/styles.css",
  177. install=False,
  178. )
  179. return _imports
  180. def _render(self, props: dict[str, Any] | None = None) -> Tag:
  181. tag = super()._render(props)
  182. tag.add_props(
  183. css=Var.create(
  184. "{{...theme.styles.global[':root'], ...theme.styles.global.body}}",
  185. _var_is_local=False,
  186. ),
  187. )
  188. return tag
  189. class ThemePanel(RadixThemesComponent):
  190. """Visual editor for creating and editing themes.
  191. Include as a child component of Theme to use in your app.
  192. """
  193. tag = "ThemePanel"
  194. # Whether the panel is open. Defaults to False.
  195. default_open: Var[bool]
  196. def add_imports(self) -> dict[str, str]:
  197. """Add imports for the ThemePanel component.
  198. Returns:
  199. The import dict.
  200. """
  201. return {"react": "useEffect"}
  202. def add_hooks(self) -> list[str]:
  203. """Add a hook on the ThemePanel to clear chakra-ui-color-mode.
  204. Returns:
  205. The hooks to render.
  206. """
  207. # The panel freezes the tab if the user color preference differs from the
  208. # theme "appearance", so clear it out when theme panel is used.
  209. return [
  210. """
  211. useEffect(() => {
  212. if (typeof window !== 'undefined') {
  213. window.onbeforeunload = () => {
  214. localStorage.removeItem('chakra-ui-color-mode');
  215. }
  216. window.onbeforeunload();
  217. }
  218. }, [])"""
  219. ]
  220. class RadixThemesColorModeProvider(Component):
  221. """Next-themes integration for radix themes components."""
  222. library = "/components/reflex/radix_themes_color_mode_provider.js"
  223. tag = "RadixThemesColorModeProvider"
  224. is_default = True
  225. theme = Theme.create
  226. theme_panel = ThemePanel.create