From 92cdc158966c2a1f241b4fcd34cadc5f0204c6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Fri, 26 Apr 2024 21:08:09 +0200 Subject: [PATCH] IconButton for color_mode with nice default and a position props to control it (#3165) --- reflex/components/lucide/icon.py | 10 +- reflex/components/lucide/icon.pyi | 1 - reflex/components/radix/themes/color_mode.py | 114 +++++++--- reflex/components/radix/themes/color_mode.pyi | 209 ++---------------- .../radix/themes/components/icon_button.py | 5 +- .../radix/themes/components/icon_button.pyi | 1 - 6 files changed, 104 insertions(+), 236 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 051b83912..5c4ef3dab 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -1,7 +1,6 @@ """Lucide Icon component.""" from reflex.components.component import Component -from reflex.style import Style from reflex.utils import console, format from reflex.vars import Var @@ -73,16 +72,9 @@ class Icon(LucideIconComponent): props["tag"] = format.to_title_case(format.to_snake_case(props["tag"])) + "Icon" props["alias"] = f"Lucide{props['tag']}" + props.setdefault("color", f"var(--current-color)") return super().create(*children, **props) - def _apply_theme(self, theme: Component): - self.style = Style( - { - "color": f"var(--current-color)", - **self.style, - } - ) - RENAMED_ICONS_05 = { "activity_square": "square_activity", diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index b13c1a0e2..f370185fa 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -8,7 +8,6 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from reflex.components.component import Component -from reflex.style import Style from reflex.utils import console, format from reflex.vars import Var diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index ed1522ddc..8a29d3b87 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -14,18 +14,20 @@ rx.text( ) ``` """ + from __future__ import annotations import dataclasses +from typing import Literal, get_args from reflex.components.component import BaseComponent -from reflex.components.core.cond import Cond, color_mode_cond +from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.lucide.icon import Icon -from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode -from reflex.vars import BaseVar +from reflex.style import color_mode, toggle_color_mode +from reflex.utils import console +from reflex.vars import BaseVar, Var -from .components.button import Button -from .components.switch import Switch +from .components.icon_button import IconButton DEFAULT_LIGHT_ICON: Icon = Icon.create(tag="sun") DEFAULT_DARK_ICON: Icon = Icon.create(tag="moon") @@ -55,44 +57,87 @@ class ColorModeIcon(Cond): ) -class ColorModeSwitch(Switch): - """Switch for toggling light / dark mode via toggle_color_mode.""" +LiteralPosition = Literal["top-left", "top-right", "bottom-left", "bottom-right"] + +position_values = get_args(LiteralPosition) + +position_map = { + "position": position_values, + "left": ["top-left", "bottom-left"], + "right": ["top-right", "bottom-right"], + "top": ["top-left", "top-right"], + "bottom": ["bottom-left", "bottom-right"], +} + + +# needed to inverse contains for find +def _find(const, var): + return Var.create_safe(const).contains(var) + + +def _set_var_default(props, position, prop, default1, default2=""): + props.setdefault( + prop, cond(_find(position_map[prop], position), default1, default2) + ) + + +def _set_static_default(props, position, prop, default): + if prop in position: + props.setdefault(prop, default) + + +class ColorModeIconButton(IconButton): + """Icon Button for toggling light / dark mode via toggle_color_mode.""" @classmethod - def create(cls, *children, **props): - """Create a switch component bound to color_mode. - - Args: - *children: The children of the component. - **props: The props to pass to the component. - - Returns: - The switch component. - """ - return Switch.create( - *children, - checked=color_mode != LIGHT_COLOR_MODE, - on_change=toggle_color_mode, - **props, - ) - - -class ColorModeButton(Button): - """Button for toggling chakra light / dark mode via toggle_color_mode.""" - - @classmethod - def create(cls, *children, **props): - """Create a button component that calls toggle_color_mode on click. + def create( + cls, + *children, + position: LiteralPosition | None = None, + **props, + ): + """Create a icon button component that calls toggle_color_mode on click. Args: *children: The children of the component. + position: The position of the icon button. Follow document flow if None. **props: The props to pass to the component. Returns: The button component. """ - return Button.create( - *children, + if children: + console.deprecate( + feature_name="passing children to color_mode.button", + reason=", use color_mode_cond and toggle_color_mode instead to build a custom color_mode component", + deprecation_version="0.5.0", + removal_version="0.6.0", + ) + + # position is used to set nice defaults for positioning the icon button + if isinstance(position, Var): + _set_var_default(props, position, "position", "fixed", position) + _set_var_default(props, position, "bottom", "2rem") + _set_var_default(props, position, "top", "2rem") + _set_var_default(props, position, "left", "2rem") + _set_var_default(props, position, "right", "2rem") + elif position is not None: + if position in position_values: + props.setdefault("position", "fixed") + _set_static_default(props, position, "bottom", "2rem") + _set_static_default(props, position, "top", "2rem") + _set_static_default(props, position, "left", "2rem") + _set_static_default(props, position, "right", "2rem") + else: + props["position"] = position + + props.setdefault("background", "transparent") + props.setdefault("color", "inherit") + props.setdefault("z_index", "20") + props.setdefault(":hover", {"cursor": "pointer"}) + + return super().create( + ColorModeIcon.create(), on_click=toggle_color_mode, **props, ) @@ -102,8 +147,7 @@ class ColorModeNamespace(BaseVar): """Namespace for color mode components.""" icon = staticmethod(ColorModeIcon.create) - switch = staticmethod(ColorModeSwitch.create) - button = staticmethod(ColorModeButton.create) + button = staticmethod(ColorModeIconButton.create) color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode)) diff --git a/reflex/components/radix/themes/color_mode.pyi b/reflex/components/radix/themes/color_mode.pyi index 270140cd5..51a208212 100644 --- a/reflex/components/radix/themes/color_mode.pyi +++ b/reflex/components/radix/themes/color_mode.pyi @@ -8,13 +8,14 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style import dataclasses +from typing import Literal, get_args from reflex.components.component import BaseComponent -from reflex.components.core.cond import Cond, color_mode_cond +from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.lucide.icon import Icon -from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode -from reflex.vars import BaseVar -from .components.button import Button -from .components.switch import Switch +from reflex.style import color_mode, toggle_color_mode +from reflex.utils import console +from reflex.vars import BaseVar, Var +from .components.icon_button import IconButton DEFAULT_LIGHT_ICON: Icon DEFAULT_DARK_ICON: Icon @@ -92,187 +93,23 @@ class ColorModeIcon(Cond): """ ... -class ColorModeSwitch(Switch): - @overload - @classmethod - def create( # type: ignore - cls, - *children, - as_child: Optional[Union[Var[bool], bool]] = None, - default_checked: Optional[Union[Var[bool], bool]] = None, - checked: Optional[Union[Var[bool], bool]] = None, - disabled: Optional[Union[Var[bool], bool]] = None, - required: Optional[Union[Var[bool], bool]] = None, - name: Optional[Union[Var[str], str]] = None, - value: Optional[Union[Var[str], str]] = None, - size: Optional[ - Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]] - ] = None, - variant: Optional[ - Union[ - Var[Literal["classic", "surface", "soft"]], - Literal["classic", "surface", "soft"], - ] - ] = None, - color_scheme: Optional[ - Union[ - Var[ - 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", - ] - ], - 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", - ], - ] - ] = None, - high_contrast: Optional[Union[Var[bool], bool]] = None, - radius: Optional[ - Union[ - Var[Literal["none", "small", "full"]], Literal["none", "small", "full"] - ] - ] = None, - style: Optional[Style] = None, - key: Optional[Any] = None, - id: Optional[Any] = None, - class_name: Optional[Any] = None, - autofocus: Optional[bool] = None, - custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, - on_blur: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_change: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_click: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_context_menu: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_double_click: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_focus: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mount: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_down: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_enter: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_leave: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_move: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_out: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_over: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_mouse_up: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_scroll: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - on_unmount: Optional[ - Union[EventHandler, EventSpec, list, function, BaseVar] - ] = None, - **props - ) -> "ColorModeSwitch": - """Create a switch component bound to color_mode. - - Args: - *children: The children of the component. - as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. - default_checked: Whether the switch is checked by default - checked: Whether the switch is checked - disabled: If true, prevent the user from interacting with the switch - required: If true, the user must interact with the switch to submit the form - name: The name of the switch (when submitting a form) - value: The value associated with the "on" position - size: Switch size "1" - "4" - variant: Variant of switch: "classic" | "surface" | "soft" - color_scheme: Override theme color for switch - high_contrast: Whether to render the switch with higher contrast color against background - radius: Override theme radius for switch: "none" | "small" | "full" - style: The style of the component. - key: A unique key for the component. - id: The id for the component. - class_name: The class name for the component. - autofocus: Whether the component should take the focus once the page is loaded - custom_attrs: custom attribute - **props: The props to pass to the component. - - Returns: - The switch component. - """ - ... - -class ColorModeButton(Button): +LiteralPosition = Literal["top-left", "top-right", "bottom-left", "bottom-right"] +position_values = get_args(LiteralPosition) +position_map = { + "position": position_values, + "left": ["top-left", "bottom-left"], + "right": ["top-right", "bottom-right"], + "top": ["top-left", "top-right"], + "bottom": ["bottom-left", "bottom-right"], +} + +class ColorModeIconButton(IconButton): @overload @classmethod def create( # type: ignore cls, *children, + position: Optional[LiteralPosition | None] = None, as_child: Optional[Union[Var[bool], bool]] = None, size: Optional[ Union[Var[Literal["1", "2", "3", "4"]], Literal["1", "2", "3", "4"]] @@ -470,14 +307,15 @@ class ColorModeButton(Button): Union[EventHandler, EventSpec, list, function, BaseVar] ] = None, **props - ) -> "ColorModeButton": - """Create a button component that calls toggle_color_mode on click. + ) -> "ColorModeIconButton": + """Create a icon button component that calls toggle_color_mode on click. Args: *children: The children of the component. + position: The position of the icon button. Follow document flow if None. as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. size: Button size "1" - "4" - variant: Variant of button: "solid" | "soft" | "outline" | "ghost" + variant: Variant of button: "classic" | "solid" | "soft" | "surface" | "outline" | "ghost" color_scheme: Override theme color for button high_contrast: Whether to render the button with higher contrast color against background radius: Override theme radius for button: "none" | "small" | "medium" | "large" | "full" @@ -524,7 +362,6 @@ class ColorModeButton(Button): class ColorModeNamespace(BaseVar): icon = staticmethod(ColorModeIcon.create) - switch = staticmethod(ColorModeSwitch.create) - button = staticmethod(ColorModeButton.create) + button = staticmethod(ColorModeIconButton.create) color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode)) diff --git a/reflex/components/radix/themes/components/icon_button.py b/reflex/components/radix/themes/components/icon_button.py index 010e8099e..684ffc1e6 100644 --- a/reflex/components/radix/themes/components/icon_button.py +++ b/reflex/components/radix/themes/components/icon_button.py @@ -6,7 +6,6 @@ from reflex import el from reflex.components.component import Component from reflex.components.core.match import Match from reflex.components.lucide import Icon -from reflex.style import Style from reflex.vars import Var from ..base import ( @@ -86,10 +85,8 @@ class IconButton(el.Button, RadixLoadingProp, RadixThemesComponent): ("4", "48px"), "12px", ) + props.setdefault("padding", "6px") return super().create(*children, **props) - def _apply_theme(self, theme: Component): - self.style = Style({"padding": "6px", **self.style}) - icon_button = IconButton.create diff --git a/reflex/components/radix/themes/components/icon_button.pyi b/reflex/components/radix/themes/components/icon_button.pyi index 1bea5fc6d..a883f6595 100644 --- a/reflex/components/radix/themes/components/icon_button.pyi +++ b/reflex/components/radix/themes/components/icon_button.pyi @@ -12,7 +12,6 @@ from reflex import el from reflex.components.component import Component from reflex.components.core.match import Match from reflex.components.lucide import Icon -from reflex.style import Style from reflex.vars import Var from ..base import ( LiteralAccentColor,