Merge branch 'main' into remove-some-benchmarks-from-CI

This commit is contained in:
Khaleel Al-Adhami 2025-02-13 16:15:37 -08:00
commit 29f3a77a48
16 changed files with 185 additions and 122 deletions

View File

@ -3,7 +3,7 @@ fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.9.3
rev: v0.9.6
hooks:
- id: ruff-format
args: [reflex, tests]

40
poetry.lock generated
View File

@ -2415,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.9.3"
version = "0.9.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"},
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"},
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"},
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"},
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"},
{file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
{file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
{file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
{file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
{file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
{file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
{file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
{file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
{file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
{file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
{file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
{file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
]
[[package]]
@ -3188,4 +3188,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10, <4.0"
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f"
content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25"

View File

@ -61,7 +61,7 @@ dill = ">=0.3.8"
toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<7.0"
ruff = "0.9.3"
ruff = "0.9.6"
pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0"

View File

@ -22,6 +22,7 @@ from typing import (
TYPE_CHECKING,
Any,
AsyncIterator,
BinaryIO,
Callable,
Coroutine,
Dict,
@ -35,12 +36,15 @@ from typing import (
get_type_hints,
)
from fastapi import FastAPI, HTTPException, Request, UploadFile
from fastapi import FastAPI, HTTPException, Request
from fastapi import UploadFile as FastAPIUploadFile
from fastapi.middleware import cors
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer
from starlette.datastructures import Headers
from starlette.datastructures import UploadFile as StarletteUploadFile
from starlette_admin.contrib.sqla.admin import Admin
from starlette_admin.contrib.sqla.view import ModelView
@ -231,6 +235,53 @@ class OverlayFragment(Fragment):
pass
@dataclasses.dataclass(frozen=True)
class UploadFile(StarletteUploadFile):
"""A file uploaded to the server.
Args:
file: The standard Python file object (non-async).
filename: The original file name.
size: The size of the file in bytes.
headers: The headers of the request.
"""
file: BinaryIO
path: Optional[Path] = dataclasses.field(default=None)
_deprecated_filename: Optional[str] = dataclasses.field(default=None)
size: Optional[int] = dataclasses.field(default=None)
headers: Headers = dataclasses.field(default_factory=Headers)
@property
def name(self) -> Optional[str]:
"""Get the name of the uploaded file.
Returns:
The name of the uploaded file.
"""
if self.path:
return self.path.name
@property
def filename(self) -> Optional[str]:
"""Get the filename of the uploaded file.
Returns:
The filename of the uploaded file.
"""
console.deprecate(
feature_name="UploadFile.filename",
reason="Use UploadFile.name instead.",
deprecation_version="0.7.1",
removal_version="0.8.0",
)
return self._deprecated_filename
@dataclasses.dataclass(
frozen=True,
)
@ -591,7 +642,9 @@ class App(MiddlewareMixin, LifespanMixin):
Returns:
The generated component.
"""
return component if isinstance(component, Component) else component()
from reflex.compiler.compiler import into_component
return into_component(component)
def add_page(
self,
@ -1583,7 +1636,7 @@ def upload(app: App):
The upload function.
"""
async def upload_file(request: Request, files: List[UploadFile]):
async def upload_file(request: Request, files: List[FastAPIUploadFile]):
"""Upload a file.
Args:
@ -1659,7 +1712,8 @@ def upload(app: App):
file_copies.append(
UploadFile(
file=content_copy,
filename=file.filename,
path=Path(file.filename.lstrip("/")) if file.filename else None,
_deprecated_filename=file.filename,
size=file.size,
headers=file.headers,
)

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple, Type, Union
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Tuple, Type, Union
from reflex import constants
from reflex.compiler import templates, utils
@ -545,7 +545,47 @@ def purge_web_pages_dir():
if TYPE_CHECKING:
from reflex.app import UnevaluatedPage
from reflex.app import ComponentCallable, UnevaluatedPage
def _into_component_once(component: Component | ComponentCallable) -> Component | None:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
"""
if isinstance(component, Component):
return component
if isinstance(component, (Var, int, float, str)):
return Fragment.create(component)
if isinstance(component, Sequence):
return Fragment.create(*component)
return None
def into_component(component: Component | ComponentCallable) -> Component:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
Raises:
TypeError: If the component is not a Component.
"""
if (converted := _into_component_once(component)) is not None:
return converted
if (
callable(component)
and (converted := _into_component_once(component())) is not None
):
return converted
raise TypeError(f"Expected a Component, got {type(component)}")
def compile_unevaluated_page(
@ -568,12 +608,7 @@ def compile_unevaluated_page(
The compiled component and whether state should be enabled.
"""
# Generate the component if it is a callable.
component = page.component
component = component if isinstance(component, Component) else component()
# unpack components that return tuples in an rx.fragment.
if isinstance(component, tuple):
component = Fragment.create(*component)
component = into_component(page.component)
component._add_style_recursive(style or {}, theme)
@ -678,10 +713,8 @@ class ExecutorSafeFunctions:
The route, compiled component, and compiled page.
"""
component, enable_state = compile_unevaluated_page(
route, cls.UNCOMPILED_PAGES[route]
route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
)
component = component if isinstance(component, Component) else component()
component._add_style_recursive(style, theme)
return route, component, compile_page(route, component, cls.STATE)
@classmethod

View File

@ -51,13 +51,7 @@ from reflex.event import (
)
from reflex.style import Style, format_as_emotion
from reflex.utils import format, imports, types
from reflex.utils.imports import (
ImmutableParsedImportDict,
ImportDict,
ImportVar,
ParsedImportDict,
parse_imports,
)
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
from reflex.vars import VarData
from reflex.vars.base import (
CachedVarOperation,
@ -1208,7 +1202,7 @@ class Component(BaseComponent, ABC):
Returns:
True if the dependency should be transpiled.
"""
return (
return bool(self.transpile_packages) and (
dep in self.transpile_packages
or format.format_library_name(dep or "") in self.transpile_packages
)
@ -1291,9 +1285,10 @@ class Component(BaseComponent, ABC):
event_imports = Imports.EVENTS if self.event_triggers else {}
# Collect imports from Vars used directly by this component.
var_datas = [var._get_all_var_data() for var in self._get_vars()]
var_imports: List[ImmutableParsedImportDict] = [
var_data.imports for var_data in var_datas if var_data is not None
var_imports = [
var_data.imports
for var in self._get_vars()
if (var_data := var._get_all_var_data()) is not None
]
added_import_dicts: list[ParsedImportDict] = []

View File

@ -29,7 +29,7 @@ from reflex.event import (
from reflex.utils import format
from reflex.utils.imports import ImportVar
from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var, get_unique_variable_name
from reflex.vars.base import Var, get_unique_variable_name
from reflex.vars.sequence import LiteralStringVar
DEFAULT_UPLOAD_ID: str = "default"
@ -45,7 +45,6 @@ upload_files_context_var_data: VarData = VarData(
)
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the file upload drop trigger.
@ -75,7 +74,6 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
)
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the list of selected files.

View File

@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType
from reflex.style import Style
from reflex.utils.imports import ImportVar
from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var
from reflex.vars.base import Var
DEFAULT_UPLOAD_ID: str
upload_files_context_var_data: VarData
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableEventSpec
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...

View File

@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton):
if allow_system:
def color_mode_item(_color_mode: str):
def color_mode_item(_color_mode: Literal["light", "dark", "system"]):
return dropdown_menu.item(
_color_mode.title(), on_click=set_color_mode(_color_mode)
)

View File

@ -10,6 +10,7 @@ import os
import sys
import threading
import urllib.parse
from functools import lru_cache
from importlib.util import find_spec
from pathlib import Path
from types import ModuleType
@ -408,6 +409,19 @@ class EnvVar(Generic[T]):
os.environ[self.name] = str_value
@lru_cache()
def get_type_hints_environment(cls: type) -> dict[str, Any]:
"""Get the type hints for the environment variables.
Args:
cls: The class.
Returns:
The type hints.
"""
return get_type_hints(cls)
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
"""Descriptor for environment variables."""
@ -434,7 +448,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
"""
self.name = name
def __get__(self, instance: Any, owner: Any):
def __get__(
self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
):
"""Get the EnvVar instance.
Args:
@ -444,7 +460,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
Returns:
The EnvVar instance.
"""
type_ = get_args(get_type_hints(owner)[self.name])[0]
type_ = get_args(get_type_hints_environment(owner)[self.name])[0]
env_name = self.name
if self.internal:
env_name = f"__{env_name}"

View File

@ -1742,6 +1742,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Yields:
StateUpdate object
Raises:
ValueError: If a string value is received for an int or float type and cannot be converted.
"""
from reflex.utils import telemetry
@ -1779,12 +1782,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
hinted_args, (Base, BaseModelV1, BaseModelV2)
):
payload[arg] = hinted_args(**value)
if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
payload[arg] = set(value)
if isinstance(value, list) and (
elif isinstance(value, list) and (
hinted_args is tuple or hinted_args is Tuple
):
payload[arg] = tuple(value)
elif isinstance(value, str) and (
hinted_args is int or hinted_args is float
):
try:
payload[arg] = hinted_args(value)
except ValueError:
raise ValueError(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
) from None
else:
console.warn(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
)
# Wrap the function in a try/except block.
try:
@ -2459,6 +2475,8 @@ class ComponentState(State, mixin=True):
Returns:
A new instance of the Component with an independent copy of the State.
"""
from reflex.compiler.compiler import into_component
cls._per_component_state_instance_count += 1
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
component_state = type(
@ -2470,6 +2488,7 @@ class ComponentState(State, mixin=True):
# Save a reference to the dynamic state for pickle/unpickle.
setattr(reflex.istate.dynamic, state_cls_name, component_state)
component = component_state.get_component(*children, **props)
component = into_component(component)
component.State = component_state
return component

View File

@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
from reflex.utils.imports import ImportVar
from reflex.utils.types import get_origin
from reflex.vars import VarData
from reflex.vars.base import CallableVar, LiteralVar, Var
from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionVar
from reflex.vars.object import ObjectVar
@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
).guess_type()
@CallableVar
def set_color_mode(
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
) -> Var[EventChain]:

View File

@ -951,7 +951,7 @@ class Var(Generic[VAR_TYPE]):
"""
actual_name = self._var_field_name
def setter(state: BaseState, value: Any):
def setter(state: Any, value: Any):
"""Get the setter for the var.
Args:
@ -969,6 +969,8 @@ class Var(Generic[VAR_TYPE]):
else:
setattr(state, actual_name, value)
setter.__annotations__["value"] = self._var_type
setter.__qualname__ = self._get_setter_name()
return setter
@ -1901,61 +1903,6 @@ def _or_operation(a: Var, b: Var):
)
@dataclasses.dataclass(
eq=False,
frozen=True,
slots=True,
)
class CallableVar(Var):
"""Decorate a Var-returning function to act as both a Var and a function.
This is used as a compatibility shim for replacing Var objects in the
API with functions that return a family of Var.
"""
fn: Callable[..., Var] = dataclasses.field(
default_factory=lambda: lambda: Var(_js_expr="undefined")
)
original_var: Var = dataclasses.field(
default_factory=lambda: Var(_js_expr="undefined")
)
def __init__(self, fn: Callable[..., Var]):
"""Initialize a CallableVar.
Args:
fn: The function to decorate (must return Var)
"""
original_var = fn()
super(CallableVar, self).__init__(
_js_expr=original_var._js_expr,
_var_type=original_var._var_type,
_var_data=VarData.merge(original_var._get_all_var_data()),
)
object.__setattr__(self, "fn", fn)
object.__setattr__(self, "original_var", original_var)
def __call__(self, *args: Any, **kwargs: Any) -> Var:
"""Call the decorated function.
Args:
*args: The args to pass to the function.
**kwargs: The kwargs to pass to the function.
Returns:
The Var returned from calling the function.
"""
return self.fn(*args, **kwargs)
def __hash__(self) -> int:
"""Calculate the hash of the object.
Returns:
The hash of the object.
"""
return hash((type(self).__name__, self.original_var))
RETURN_TYPE = TypeVar("RETURN_TYPE")
DICT_KEY = TypeVar("DICT_KEY")

View File

@ -20,7 +20,11 @@ def BackgroundTask():
class State(rx.State):
counter: int = 0
_task_id: int = 0
iterations: int = 10
iterations: rx.Field[int] = rx.field(10)
@rx.event
def set_iterations(self, value: str):
self.iterations = int(value)
@rx.event(background=True)
async def handle_event(self):
@ -125,8 +129,8 @@ def BackgroundTask():
rx.input(
id="iterations",
placeholder="Iterations",
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue]
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue]
value=State.iterations.to_string(),
on_change=State.set_iterations,
),
rx.button(
"Delayed Increment",

View File

@ -87,7 +87,7 @@ def UploadFile():
),
rx.box(
rx.foreach(
rx.selected_files,
rx.selected_files(),
lambda f: rx.text(f, as_="p"),
),
id="selected_files",

View File

@ -61,7 +61,7 @@ def ColorToggleApp():
rx.icon(tag="moon", size=20),
value="dark",
),
on_change=set_color_mode,
on_change=set_color_mode(),
variant="classic",
radius="large",
value=color_mode,