add allow_system prop to colormode iconbutton, and clean up logic (#3507)

* add allow_system prop to colormode iconbutton, and clean up logic

* remove segmentedcontrol change from this PR

* make it work for chakraColorProvider too

* add comment to explain resolved_color_mode
This commit is contained in:
Thomas Brandého 2024-06-19 02:02:27 +02:00 committed by GitHub
parent af3c9be97c
commit d6d14b3f72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 115 additions and 36 deletions

View File

@ -1,21 +1,35 @@
import { useColorMode as chakraUseColorMode } from "@chakra-ui/react" import { useColorMode as chakraUseColorMode } from "@chakra-ui/react";
import { useTheme } from "next-themes" import { useTheme } from "next-themes";
import { useEffect } from "react" import { useEffect, useState } from "react";
import { ColorModeContext } from "/utils/context.js" import { ColorModeContext, defaultColorMode } from "/utils/context.js";
export default function ChakraColorModeProvider({ children }) { export default function ChakraColorModeProvider({ children }) {
const {colorMode, toggleColorMode} = chakraUseColorMode() const { theme, resolvedTheme, setTheme } = useTheme();
const {theme, setTheme} = useTheme() const { colorMode, toggleColorMode } = chakraUseColorMode();
const [resolvedColorMode, setResolvedColorMode] = useState(theme);
useEffect(() => { useEffect(() => {
if (colorMode != theme) { if (colorMode != resolvedTheme) {
toggleColorMode() toggleColorMode();
} }
}, [theme]) }, [theme, resolvedTheme]);
const rawColorMode = colorMode;
const setColorMode = (mode) => {
const allowedModes = ["light", "dark", "system"];
if (!allowedModes.includes(mode)) {
console.error(
`Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`
);
mode = defaultColorMode;
}
setTheme(mode);
};
return ( return (
<ColorModeContext.Provider value={[ colorMode, toggleColorMode ]}> <ColorModeContext.Provider
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
>
{children} {children}
</ColorModeContext.Provider> </ColorModeContext.Provider>
) );
} }

View File

@ -3,18 +3,32 @@ import { useEffect, useState } from "react";
import { ColorModeContext, defaultColorMode } from "/utils/context.js"; import { ColorModeContext, defaultColorMode } from "/utils/context.js";
export default function RadixThemesColorModeProvider({ children }) { export default function RadixThemesColorModeProvider({ children }) {
const { resolvedTheme, setTheme } = useTheme(); const { theme, resolvedTheme, setTheme } = useTheme();
const [colorMode, setColorMode] = useState(defaultColorMode); const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
const [resolvedColorMode, setResolvedColorMode] = useState(theme);
useEffect(() => { useEffect(() => {
setColorMode(resolvedTheme); setRawColorMode(theme);
}, [resolvedTheme]); setResolvedColorMode(resolvedTheme);
}, [theme, resolvedTheme]);
const toggleColorMode = () => { const toggleColorMode = () => {
setTheme(resolvedTheme === "light" ? "dark" : "light"); setTheme(resolvedTheme === "light" ? "dark" : "light");
}; };
const setColorMode = (mode) => {
const allowedModes = ["light", "dark", "system"];
if (!allowedModes.includes(mode)) {
console.error(
`Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`
);
mode = defaultColorMode;
}
setTheme(mode);
};
return ( return (
<ColorModeContext.Provider value={[colorMode, toggleColorMode]}> <ColorModeContext.Provider
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
>
{children} {children}
</ColorModeContext.Provider> </ColorModeContext.Provider>
); );

View File

@ -37,7 +37,9 @@ class ReflexJinjaEnvironment(Environment):
constants.CompileVars.PROCESSING: False, constants.CompileVars.PROCESSING: False,
}, },
"color_mode": constants.ColorMode.NAME, "color_mode": constants.ColorMode.NAME,
"resolved_color_mode": constants.ColorMode.RESOLVED_NAME,
"toggle_color_mode": constants.ColorMode.TOGGLE, "toggle_color_mode": constants.ColorMode.TOGGLE,
"set_color_mode": constants.ColorMode.SET,
"use_color_mode": constants.ColorMode.USE, "use_color_mode": constants.ColorMode.USE,
"hydrate": constants.CompileVars.HYDRATE, "hydrate": constants.CompileVars.HYDRATE,
"on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL, "on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL,

View File

@ -9,7 +9,7 @@ from reflex.components.component import BaseComponent, Component, MemoizationLea
from reflex.components.tags import CondTag, Tag from reflex.components.tags import CondTag, Tag
from reflex.constants import Dirs from reflex.constants import Dirs
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.style import LIGHT_COLOR_MODE, color_mode from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var, VarData from reflex.vars import Var, VarData
@ -208,7 +208,7 @@ def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
The conditional component or prop. The conditional component or prop.
""" """
return cond( return cond(
color_mode == Var.create(LIGHT_COLOR_MODE, _var_is_string=True), resolved_color_mode == Var.create(LIGHT_COLOR_MODE, _var_is_string=True),
light, light,
dark, dark,
) )

View File

@ -23,8 +23,10 @@ from typing import Literal, get_args
from reflex.components.component import BaseComponent from reflex.components.component import BaseComponent
from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.core.cond import Cond, color_mode_cond, cond
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.components.dropdown_menu import dropdown_menu
from reflex.components.radix.themes.components.switch import Switch from reflex.components.radix.themes.components.switch import Switch
from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode from reflex.event import EventChain
from reflex.style import LIGHT_COLOR_MODE, color_mode, set_color_mode, toggle_color_mode
from reflex.utils import console from reflex.utils import console
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
@ -95,6 +97,7 @@ class ColorModeIconButton(IconButton):
cls, cls,
*children, *children,
position: LiteralPosition | None = None, position: LiteralPosition | None = None,
allow_system: bool = False,
**props, **props,
): ):
"""Create a icon button component that calls toggle_color_mode on click. """Create a icon button component that calls toggle_color_mode on click.
@ -102,6 +105,7 @@ class ColorModeIconButton(IconButton):
Args: Args:
*children: The children of the component. *children: The children of the component.
position: The position of the icon button. Follow document flow if None. position: The position of the icon button. Follow document flow if None.
allow_system: Allow picking the "system" value for the color mode.
**props: The props to pass to the component. **props: The props to pass to the component.
Returns: Returns:
@ -137,6 +141,32 @@ class ColorModeIconButton(IconButton):
props.setdefault("z_index", "20") props.setdefault("z_index", "20")
props.setdefault(":hover", {"cursor": "pointer"}) props.setdefault(":hover", {"cursor": "pointer"})
if allow_system:
def color_mode_item(_color_mode):
setter = Var.create_safe(
f'() => {set_color_mode._var_name}("{_color_mode}")',
_var_is_string=False,
_var_is_local=True,
_var_data=set_color_mode._var_data,
)
setter._var_type = EventChain
return dropdown_menu.item(_color_mode.title(), on_click=setter) # type: ignore
return dropdown_menu.root(
dropdown_menu.trigger(
super().create(
ColorModeIcon.create(),
**props,
)
),
dropdown_menu.content(
color_mode_item("light"),
color_mode_item("dark"),
color_mode_item("system"),
),
)
return super().create( return super().create(
ColorModeIcon.create(), ColorModeIcon.create(),
on_click=toggle_color_mode, on_click=toggle_color_mode,

View File

@ -12,8 +12,10 @@ from typing import Literal, get_args
from reflex.components.component import BaseComponent from reflex.components.component import BaseComponent
from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.core.cond import Cond, color_mode_cond, cond
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.components.dropdown_menu import dropdown_menu
from reflex.components.radix.themes.components.switch import Switch from reflex.components.radix.themes.components.switch import Switch
from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode from reflex.event import EventChain
from reflex.style import LIGHT_COLOR_MODE, color_mode, set_color_mode, toggle_color_mode
from reflex.utils import console from reflex.utils import console
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
from .components.icon_button import IconButton from .components.icon_button import IconButton
@ -113,6 +115,7 @@ class ColorModeIconButton(IconButton):
position: Optional[ position: Optional[
Literal["top-left", "top-right", "bottom-left", "bottom-right"] Literal["top-left", "top-right", "bottom-left", "bottom-right"]
] = None, ] = None,
allow_system: Optional[bool] = False,
as_child: Optional[Union[Var[bool], bool]] = None, as_child: Optional[Union[Var[bool], bool]] = None,
size: Optional[ size: Optional[
Union[Var[Literal["1", "2", "3", "4"]], Literal["1", "2", "3", "4"]] Union[Var[Literal["1", "2", "3", "4"]], Literal["1", "2", "3", "4"]]
@ -316,6 +319,7 @@ class ColorModeIconButton(IconButton):
Args: Args:
*children: The children of the component. *children: The children of the component.
position: The position of the icon button. Follow document flow if None. position: The position of the icon button. Follow document flow if None.
allow_system: Allow picking the "system" value for the color mode.
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.
size: Button size "1" - "4" size: Button size "1" - "4"
variant: Variant of button: "classic" | "solid" | "soft" | "surface" | "outline" | "ghost" variant: Variant of button: "classic" | "solid" | "soft" | "surface" | "outline" | "ghost"

View File

@ -12,7 +12,7 @@ from reflex.event import (
EventSpec, EventSpec,
call_script, call_script,
) )
from reflex.style import Style, color_mode from reflex.style import Style, resolved_color_mode
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.utils.serializers import serialize, serializer from reflex.utils.serializers import serialize, serializer
@ -168,7 +168,7 @@ class Toaster(Component):
tag = "Toaster" tag = "Toaster"
# the theme of the toast # the theme of the toast
theme: Var[str] = color_mode theme: Var[str] = resolved_color_mode
# whether to show rich colors # whether to show rich colors
rich_colors: Var[bool] = Var.create_safe(True) rich_colors: Var[bool] = Var.create_safe(True)

View File

@ -13,7 +13,7 @@ from reflex.components.component import Component, ComponentNamespace
from reflex.components.lucide.icon import Icon from reflex.components.lucide.icon import Icon
from reflex.components.props import PropsBase from reflex.components.props import PropsBase
from reflex.event import EventSpec, call_script from reflex.event import EventSpec, call_script
from reflex.style import Style, color_mode from reflex.style import Style, resolved_color_mode
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.utils.serializers import serialize, serializer from reflex.utils.serializers import serialize, serializer

View File

@ -126,9 +126,11 @@ class Next(SimpleNamespace):
class ColorMode(SimpleNamespace): class ColorMode(SimpleNamespace):
"""Constants related to ColorMode.""" """Constants related to ColorMode."""
NAME = "colorMode" NAME = "rawColorMode"
RESOLVED_NAME = "resolvedColorMode"
USE = "useColorMode" USE = "useColorMode"
TOGGLE = "toggleColorMode" TOGGLE = "toggleColorMode"
SET = "setColorMode"
# Env modes # Env modes

View File

@ -17,27 +17,40 @@ LIGHT_COLOR_MODE: str = "light"
DARK_COLOR_MODE: str = "dark" DARK_COLOR_MODE: str = "dark"
# Reference the global ColorModeContext # Reference the global ColorModeContext
color_mode_var_data = VarData( color_mode_imports = {
imports={ f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")], "react": [ImportVar(tag="useContext")],
"react": [ImportVar(tag="useContext")], }
}, color_mode_toggle_hooks = {
hooks={ f"const {{ {constants.ColorMode.RESOLVED_NAME}, {constants.ColorMode.TOGGLE} }} = useContext(ColorModeContext)": None,
f"const [ {constants.ColorMode.NAME}, {constants.ColorMode.TOGGLE} ] = useContext(ColorModeContext)": None, }
}, color_mode_set_hooks = {
) f"const {{ {constants.ColorMode.NAME}, {constants.ColorMode.RESOLVED_NAME}, {constants.ColorMode.TOGGLE}, {constants.ColorMode.SET} }} = useContext(ColorModeContext)": None,
# Var resolves to the current color mode for the app ("light" or "dark") }
color_mode_var_data = VarData(imports=color_mode_imports, hooks=color_mode_toggle_hooks)
# Var resolves to the current color mode for the app ("light", "dark" or "system")
color_mode = BaseVar( color_mode = BaseVar(
_var_name=constants.ColorMode.NAME, _var_name=constants.ColorMode.NAME,
_var_type="str", _var_type="str",
_var_data=color_mode_var_data, _var_data=color_mode_var_data,
) )
# Var resolves to the resolved color mode for the app ("light" or "dark")
resolved_color_mode = BaseVar(
_var_name=constants.ColorMode.RESOLVED_NAME,
_var_type="str",
_var_data=color_mode_var_data,
)
# Var resolves to a function invocation that toggles the color mode # Var resolves to a function invocation that toggles the color mode
toggle_color_mode = BaseVar( toggle_color_mode = BaseVar(
_var_name=constants.ColorMode.TOGGLE, _var_name=constants.ColorMode.TOGGLE,
_var_type=EventChain, _var_type=EventChain,
_var_data=color_mode_var_data, _var_data=color_mode_var_data,
) )
set_color_mode = BaseVar(
_var_name=constants.ColorMode.SET,
_var_type=EventChain,
_var_data=VarData(imports=color_mode_imports, hooks=color_mode_set_hooks),
)
breakpoints = ["0", "30em", "48em", "62em", "80em", "96em"] breakpoints = ["0", "30em", "48em", "62em", "80em", "96em"]
@ -273,7 +286,7 @@ def format_as_emotion(style_dict: dict[str, Any]) -> Style | None:
def convert_dict_to_style_and_format_emotion( def convert_dict_to_style_and_format_emotion(
raw_dict: dict[str, Any] raw_dict: dict[str, Any],
) -> dict[str, Any] | None: ) -> dict[str, Any] | None:
"""Convert a dict to a style dict and then format as emotion. """Convert a dict to a style dict and then format as emotion.