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 { useTheme } from "next-themes"
import { useEffect } from "react"
import { ColorModeContext } from "/utils/context.js"
import { useColorMode as chakraUseColorMode } from "@chakra-ui/react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { ColorModeContext, defaultColorMode } from "/utils/context.js";
export default function ChakraColorModeProvider({ children }) {
const {colorMode, toggleColorMode} = chakraUseColorMode()
const {theme, setTheme} = useTheme()
const { theme, resolvedTheme, setTheme } = useTheme();
const { colorMode, toggleColorMode } = chakraUseColorMode();
const [resolvedColorMode, setResolvedColorMode] = useState(theme);
useEffect(() => {
if (colorMode != theme) {
toggleColorMode()
if (colorMode != resolvedTheme) {
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 (
<ColorModeContext.Provider value={[ colorMode, toggleColorMode ]}>
<ColorModeContext.Provider
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
>
{children}
</ColorModeContext.Provider>
)
}
);
}

View File

@ -3,18 +3,32 @@ import { useEffect, useState } from "react";
import { ColorModeContext, defaultColorMode } from "/utils/context.js";
export default function RadixThemesColorModeProvider({ children }) {
const { resolvedTheme, setTheme } = useTheme();
const [colorMode, setColorMode] = useState(defaultColorMode);
const { theme, resolvedTheme, setTheme } = useTheme();
const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
const [resolvedColorMode, setResolvedColorMode] = useState(theme);
useEffect(() => {
setColorMode(resolvedTheme);
}, [resolvedTheme]);
setRawColorMode(theme);
setResolvedColorMode(resolvedTheme);
}, [theme, resolvedTheme]);
const toggleColorMode = () => {
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 (
<ColorModeContext.Provider value={[colorMode, toggleColorMode]}>
<ColorModeContext.Provider
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
>
{children}
</ColorModeContext.Provider>
);

View File

@ -37,7 +37,9 @@ class ReflexJinjaEnvironment(Environment):
constants.CompileVars.PROCESSING: False,
},
"color_mode": constants.ColorMode.NAME,
"resolved_color_mode": constants.ColorMode.RESOLVED_NAME,
"toggle_color_mode": constants.ColorMode.TOGGLE,
"set_color_mode": constants.ColorMode.SET,
"use_color_mode": constants.ColorMode.USE,
"hydrate": constants.CompileVars.HYDRATE,
"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.constants import Dirs
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.imports import ImportDict, ImportVar
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.
"""
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,
dark,
)

View File

@ -23,8 +23,10 @@ from typing import Literal, get_args
from reflex.components.component import BaseComponent
from reflex.components.core.cond import Cond, color_mode_cond, cond
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.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.vars import BaseVar, Var
@ -95,6 +97,7 @@ class ColorModeIconButton(IconButton):
cls,
*children,
position: LiteralPosition | None = None,
allow_system: bool = False,
**props,
):
"""Create a icon button component that calls toggle_color_mode on click.
@ -102,6 +105,7 @@ class ColorModeIconButton(IconButton):
Args:
*children: The children of the component.
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.
Returns:
@ -137,6 +141,32 @@ class ColorModeIconButton(IconButton):
props.setdefault("z_index", "20")
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(
ColorModeIcon.create(),
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.core.cond import Cond, color_mode_cond, cond
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.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.vars import BaseVar, Var
from .components.icon_button import IconButton
@ -113,6 +115,7 @@ class ColorModeIconButton(IconButton):
position: Optional[
Literal["top-left", "top-right", "bottom-left", "bottom-right"]
] = None,
allow_system: Optional[bool] = False,
as_child: Optional[Union[Var[bool], bool]] = None,
size: Optional[
Union[Var[Literal["1", "2", "3", "4"]], Literal["1", "2", "3", "4"]]
@ -316,6 +319,7 @@ class ColorModeIconButton(IconButton):
Args:
*children: The children of the component.
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.
size: Button size "1" - "4"
variant: Variant of button: "classic" | "solid" | "soft" | "surface" | "outline" | "ghost"

View File

@ -12,7 +12,7 @@ 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.imports import ImportVar
from reflex.utils.serializers import serialize, serializer
@ -168,7 +168,7 @@ class Toaster(Component):
tag = "Toaster"
# the theme of the toast
theme: Var[str] = color_mode
theme: Var[str] = resolved_color_mode
# whether to show rich colors
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.props import PropsBase
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.imports import ImportVar
from reflex.utils.serializers import serialize, serializer

View File

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

View File

@ -17,27 +17,40 @@ LIGHT_COLOR_MODE: str = "light"
DARK_COLOR_MODE: str = "dark"
# Reference the global ColorModeContext
color_mode_var_data = VarData(
imports={
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
"react": [ImportVar(tag="useContext")],
},
hooks={
f"const [ {constants.ColorMode.NAME}, {constants.ColorMode.TOGGLE} ] = useContext(ColorModeContext)": None,
},
)
# Var resolves to the current color mode for the app ("light" or "dark")
color_mode_imports = {
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
"react": [ImportVar(tag="useContext")],
}
color_mode_toggle_hooks = {
f"const {{ {constants.ColorMode.RESOLVED_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,
}
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(
_var_name=constants.ColorMode.NAME,
_var_type="str",
_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
toggle_color_mode = BaseVar(
_var_name=constants.ColorMode.TOGGLE,
_var_type=EventChain,
_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"]
@ -273,7 +286,7 @@ def format_as_emotion(style_dict: dict[str, Any]) -> Style | None:
def convert_dict_to_style_and_format_emotion(
raw_dict: dict[str, Any]
raw_dict: dict[str, Any],
) -> dict[str, Any] | None:
"""Convert a dict to a style dict and then format as emotion.