"""Base classes for radix-themes components.""" from __future__ import annotations from typing import Any, ClassVar, Literal from reflex.components import Component from reflex.components.core.breakpoints import Responsive from reflex.components.tags import Tag from reflex.config import get_config from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var LiteralAlign = Literal["start", "center", "end", "baseline", "stretch"] LiteralJustify = Literal["start", "center", "end", "between"] LiteralSpacing = Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] LiteralVariant = Literal["classic", "solid", "soft", "surface", "outline", "ghost"] LiteralAppearance = Literal["inherit", "light", "dark"] LiteralGrayColor = Literal["gray", "mauve", "slate", "sage", "olive", "sand", "auto"] LiteralPanelBackground = Literal["solid", "translucent"] LiteralRadius = Literal["none", "small", "medium", "large", "full"] LiteralScaling = Literal["90%", "95%", "100%", "105%", "110%"] LiteralAccentColor = Literal[ "tomato", "red", "ruby", "crimson", "pink", "plum", "purple", "violet", "iris", "indigo", "blue", "cyan", "teal", "jade", "green", "grass", "brown", "orange", "sky", "mint", "lime", "yellow", "amber", "gold", "bronze", "gray", ] class CommonMarginProps(Component): """Many radix-themes elements accept shorthand margin props.""" # Margin: "0" - "9" # noqa: ERA001 m: Var[LiteralSpacing] # Margin horizontal: "0" - "9" mx: Var[LiteralSpacing] # Margin vertical: "0" - "9" my: Var[LiteralSpacing] # Margin top: "0" - "9" mt: Var[LiteralSpacing] # Margin right: "0" - "9" mr: Var[LiteralSpacing] # Margin bottom: "0" - "9" mb: Var[LiteralSpacing] # Margin left: "0" - "9" ml: Var[LiteralSpacing] class CommonPaddingProps(Component): """Many radix-themes elements accept shorthand padding props.""" # Padding: "0" - "9" # noqa: ERA001 p: Var[Responsive[LiteralSpacing]] # Padding horizontal: "0" - "9" px: Var[Responsive[LiteralSpacing]] # Padding vertical: "0" - "9" py: Var[Responsive[LiteralSpacing]] # Padding top: "0" - "9" pt: Var[Responsive[LiteralSpacing]] # Padding right: "0" - "9" pr: Var[Responsive[LiteralSpacing]] # Padding bottom: "0" - "9" pb: Var[Responsive[LiteralSpacing]] # Padding left: "0" - "9" pl: Var[Responsive[LiteralSpacing]] class RadixLoadingProp(Component): """Base class for components that can be in a loading state.""" # If set, show an rx.spinner instead of the component children. loading: Var[bool] class RadixThemesComponent(Component): """Base class for all @radix-ui/themes components.""" library = "@radix-ui/themes@^3.2.1" # "Fake" prop color_scheme is used to avoid shadowing CSS prop "color". _rename_props: ClassVar[dict[str, str]] = {"colorScheme": "color"} @classmethod def create( cls, *children, **props, ) -> Component: """Create a new component instance. Will prepend "RadixThemes" to the component tag to avoid conflicts with other UI libraries for common names, like Text and Button. Args: *children: Child components. **props: Component properties. Returns: A new component instance. """ component = super().create(*children, **props) if component.library is None: component.library = RadixThemesComponent.__fields__["library"].default component.alias = "RadixThemes" + (component.tag or type(component).__name__) return component @staticmethod def _get_app_wrap_components() -> dict[tuple[int, str], Component]: return { (45, "RadixThemesColorModeProvider"): RadixThemesColorModeProvider.create(), } class RadixThemesTriggerComponent(RadixThemesComponent): """Base class for Trigger, Close, Cancel, and Accept components. These components trigger some action in an overlay component that depends on the on_click event, and thus if a child is provided and has on_click specified, it will overtake the internal action, unless it is wrapped in some inert component, in this case, a Flex. """ @classmethod def create(cls, *children: Any, **props: Any) -> Component: """Create a new RadixThemesTriggerComponent instance. Args: children: The children of the component. props: The properties of the component. Returns: The new RadixThemesTriggerComponent instance. """ from .layout.flex import Flex for child in children: if "on_click" in getattr(child, "event_triggers", {}): children = (Flex.create(*children),) break return super().create(*children, **props) class Theme(RadixThemesComponent): """A theme provider for radix components. This should be applied as `App.theme` to apply the theme to all radix components in the app with the given settings. It can also be used in a normal page to apply specified properties to all child elements as an override of the main theme. """ tag = "Theme" # Whether to apply the themes background color to the theme node. Defaults to True. has_background: Var[bool] # Override light or dark mode theme: "inherit" | "light" | "dark". Defaults to "inherit". appearance: Var[LiteralAppearance] # The color used for default buttons, typography, backgrounds, etc accent_color: Var[LiteralAccentColor] # The shade of gray, defaults to "auto". gray_color: Var[LiteralGrayColor] # Whether panel backgrounds are translucent: "solid" | "translucent" (default) panel_background: Var[LiteralPanelBackground] # Element border radius: "none" | "small" | "medium" | "large" | "full". Defaults to "medium". radius: Var[LiteralRadius] # Scale of all theme items: "90%" | "95%" | "100%" | "105%" | "110%". Defaults to "100%" scaling: Var[LiteralScaling] @classmethod def create( cls, *children, color_mode: LiteralAppearance | None = None, theme_panel: bool = False, **props, ) -> Component: """Create a new Radix Theme specification. Args: *children: Child components. color_mode: Map to appearance prop. theme_panel: Whether to include a panel for editing the theme. **props: Component properties. Returns: A new component instance. """ if color_mode is not None: props["appearance"] = color_mode if theme_panel: children = [ThemePanel.create(), *children] return super().create(*children, **props) def add_imports(self) -> ImportDict | list[ImportDict]: """Add imports for the Theme component. Returns: The import dict. """ _imports: ImportDict = { "$/utils/theme.js": [ImportVar(tag="theme", is_default=True)], } if get_config().tailwind is None: # When tailwind is disabled, import the radix-ui styles directly because they will # not be included in the tailwind.css file. _imports[""] = ImportVar( tag="@radix-ui/themes/styles.css", install=False, ) return _imports def _render(self, props: dict[str, Any] | None = None) -> Tag: tag = super()._render(props) tag.add_props( css=Var( _js_expr="{...theme.styles.global[':root'], ...theme.styles.global.body}" ), ) tag.remove_props("appearance") return tag class ThemePanel(RadixThemesComponent): """Visual editor for creating and editing themes. Include as a child component of Theme to use in your app. """ tag = "ThemePanel" # Whether the panel is open. Defaults to False. default_open: Var[bool] def add_imports(self) -> dict[str, str]: """Add imports for the ThemePanel component. Returns: The import dict. """ return {"react": "useEffect"} class RadixThemesColorModeProvider(Component): """Next-themes integration for radix themes components.""" library = "$/components/reflex/radix_themes_color_mode_provider.js" tag = "RadixThemesColorModeProvider" is_default = True theme = Theme.create theme_panel = ThemePanel.create