diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 052936f7f..9f4b7cbe5 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -460,7 +460,12 @@ def remove_tailwind_from_postcss() -> tuple[str, str]: def purge_web_pages_dir(): - """Empty out .web directory.""" + """Empty out .web/pages directory.""" + if _is_dev_mode() and os.environ.get("REFLEX_PERSIST_WEB_DIR"): + # Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set. + return + + # Empty out the web pages directory. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"]) diff --git a/reflex/components/chakra/base.py b/reflex/components/chakra/base.py index 2244be898..9e1f3f698 100644 --- a/reflex/components/chakra/base.py +++ b/reflex/components/chakra/base.py @@ -15,7 +15,6 @@ class ChakraComponent(Component): library = "@chakra-ui/react@2.6.1" lib_dependencies: List[str] = [ "@chakra-ui/system@2.5.7", - "focus-visible@5.2.0", "framer-motion@10.16.4", ] @@ -26,25 +25,6 @@ class ChakraComponent(Component): (60, "ChakraProvider"): chakra_provider, } - def get_imports(self) -> imports.ImportDict: - """Chakra requires focus-visible and imported into each page. - - This allows the GlobalStyle defined by the ChakraProvider to hide the blue border. - - Returns: - The imports for the component. - """ - return imports.merge_imports( - super().get_imports(), - { - "": { - imports.ImportVar( - tag="focus-visible/dist/focus-visible", install=False - ) - } - }, - ) - def _get_style(self) -> dict: """Get the style for the component. @@ -65,25 +45,11 @@ class ChakraComponent(Component): dep: [imports.ImportVar(tag=None, render=False)] for dep in [ "@chakra-ui/system@2.5.7", - "focus-visible@5.2.0", "framer-motion@10.16.4", ] } -class Global(Component): - """The emotion/react Global styling component.""" - - library = "@emotion/react@^11.11.0" - lib_dependencies: List[str] = [ - "@emotion/styled@^11.11.0", - ] - - tag = "Global" - - styles: Var[str] - - class ChakraProvider(ChakraComponent): """Top level Chakra provider must be included in any app using chakra components.""" @@ -99,7 +65,6 @@ class ChakraProvider(ChakraComponent): A new ChakraProvider component. """ return super().create( - Global.create(styles=Var.create("GlobalStyles", _var_is_local=False)), theme=Var.create("extendTheme(theme)", _var_is_local=False), ) @@ -111,22 +76,8 @@ class ChakraProvider(ChakraComponent): _imports.setdefault("/utils/theme.js", []).append( imports.ImportVar(tag="theme", is_default=True), ) - _imports.setdefault(Global.__fields__["library"].default, []).append( - imports.ImportVar(tag="css", is_default=False), - ) return _imports - def _get_custom_code(self) -> str | None: - return """ -const GlobalStyles = css` - /* Hide the blue border around Chakra components. */ - .js-focus-visible :focus:not([data-focus-visible-added]) { - outline: none; - box-shadow: none; - } -`; -""" - @staticmethod @lru_cache(maxsize=None) def _get_app_wrap_components() -> dict[tuple[int, str], Component]: diff --git a/reflex/components/chakra/base.pyi b/reflex/components/chakra/base.pyi index bdfe142b2..44a1fd4a1 100644 --- a/reflex/components/chakra/base.pyi +++ b/reflex/components/chakra/base.pyi @@ -14,7 +14,6 @@ from reflex.utils import imports from reflex.vars import Var class ChakraComponent(Component): - def get_imports(self) -> imports.ImportDict: ... @overload @classmethod def create( # type: ignore @@ -95,88 +94,6 @@ class ChakraComponent(Component): """ ... -class Global(Component): - @overload - @classmethod - def create( # type: ignore - cls, - *children, - styles: Optional[Union[Var[str], str]] = None, - style: Optional[Style] = None, - key: Optional[Any] = None, - id: Optional[Any] = None, - class_name: Optional[Any] = None, - autofocus: Optional[bool] = None, - _rename_props: Optional[Dict[str, str]] = None, - custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, - on_blur: 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 - ) -> "Global": - """Create the component. - - Args: - *children: The children of the component. - 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 - _rename_props: props to change the name of - custom_attrs: custom attribute - **props: The props of the component. - - Returns: - The component. - - Raises: - TypeError: If an invalid child is passed. - """ - ... - class ChakraProvider(ChakraComponent): @overload @classmethod diff --git a/reflex/components/chakra/forms/input.py b/reflex/components/chakra/forms/input.py index 9b6edb369..363878f62 100644 --- a/reflex/components/chakra/forms/input.py +++ b/reflex/components/chakra/forms/input.py @@ -91,15 +91,9 @@ class Input(ChakraComponent): Returns: The component. """ - if ( - isinstance(props.get("value"), Var) and props.get("on_change") - ) or "debounce_timeout" in props: - # Currently default to 50ms, which appears to be a good balance - debounce_timeout = props.pop("debounce_timeout", 50) + if props.get("value") is not None and props.get("on_change"): # create a debounced input if the user requests full control to avoid typing jank - return DebounceInput.create( - super().create(*children, **props), debounce_timeout=debounce_timeout - ) + return DebounceInput.create(super().create(*children, **props)) return super().create(*children, **props) diff --git a/reflex/components/chakra/forms/textarea.py b/reflex/components/chakra/forms/textarea.py index 1591a35ea..ecbe15e1e 100644 --- a/reflex/components/chakra/forms/textarea.py +++ b/reflex/components/chakra/forms/textarea.py @@ -74,13 +74,7 @@ class TextArea(ChakraComponent): Returns: The component. """ - if ( - isinstance(props.get("value"), Var) and props.get("on_change") - ) or "debounce_timeout" in props: - # Currently default to 50ms, which appears to be a good balance - debounce_timeout = props.pop("debounce_timeout", 50) + if props.get("value") is not None and props.get("on_change"): # create a debounced input if the user requests full control to avoid typing jank - return DebounceInput.create( - super().create(*children, **props), debounce_timeout=debounce_timeout - ) + return DebounceInput.create(super().create(*children, **props)) return super().create(*children, **props) diff --git a/reflex/components/core/debounce.py b/reflex/components/core/debounce.py index 21cfcc91a..2767e8d21 100644 --- a/reflex/components/core/debounce.py +++ b/reflex/components/core/debounce.py @@ -7,6 +7,8 @@ from reflex.components.component import Component from reflex.constants import EventTriggers from reflex.vars import Var, VarData +DEFAULT_DEBOUNCE_TIMEOUT = 300 + class DebounceInput(Component): """The DebounceInput component is used to buffer input events on the client side. @@ -23,7 +25,7 @@ class DebounceInput(Component): min_length: Var[int] # Time to wait between end of input and triggering on_change - debounce_timeout: Var[int] + debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore # If true, notify when Enter key is pressed force_notify_by_enter: Var[bool] diff --git a/reflex/components/core/debounce.pyi b/reflex/components/core/debounce.pyi index 9cd13e9ad..db9a3e8b0 100644 --- a/reflex/components/core/debounce.pyi +++ b/reflex/components/core/debounce.pyi @@ -12,6 +12,8 @@ from reflex.components.component import Component from reflex.constants import EventTriggers from reflex.vars import Var, VarData +DEFAULT_DEBOUNCE_TIMEOUT = 300 + class DebounceInput(Component): @overload @classmethod diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py index 00d674d73..16c79076d 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/reflex/components/radix/primitives/accordion.py @@ -3,7 +3,7 @@ from __future__ import annotations from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.components.component import Component from reflex.components.core import match @@ -344,6 +344,8 @@ class AccordionRoot(AccordionComponent): # The var_data associated with the component. _var_data: VarData = VarData() # type: ignore + _valid_children: List[str] = ["AccordionItem"] + @classmethod def create(cls, *children, **props) -> Component: """Create the Accordion root component. @@ -451,6 +453,14 @@ class AccordionItem(AccordionComponent): # When true, prevents the user from interacting with the item. disabled: Var[bool] + _valid_children: List[str] = [ + "AccordionHeader", + "AccordionTrigger", + "AccordionContent", + ] + + _valid_parents: List[str] = ["AccordionRoot"] + def _apply_theme(self, theme: Component): self.style = Style( { diff --git a/reflex/components/radix/primitives/accordion.pyi b/reflex/components/radix/primitives/accordion.pyi index a7c88b001..19f996ad5 100644 --- a/reflex/components/radix/primitives/accordion.pyi +++ b/reflex/components/radix/primitives/accordion.pyi @@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.components.component import Component from reflex.components.core import match from reflex.components.lucide.icon import Icon diff --git a/reflex/components/radix/themes/components/contextmenu.py b/reflex/components/radix/themes/components/contextmenu.py index 46465b202..822cb123d 100644 --- a/reflex/components/radix/themes/components/contextmenu.py +++ b/reflex/components/radix/themes/components/contextmenu.py @@ -1,6 +1,6 @@ """Interactive components provided by @radix-ui/themes.""" from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var @@ -19,6 +19,8 @@ class ContextMenuRoot(RadixThemesComponent): # The modality of the context menu. When set to true, interaction with outside elements will be disabled and only menu content will be visible to screen readers. modal: Var[bool] + _invalid_children: List[str] = ["ContextMenuItem"] + def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. @@ -39,6 +41,10 @@ class ContextMenuTrigger(RadixThemesComponent): # Whether the trigger is disabled disabled: Var[bool] + _valid_parents: List[str] = ["ContextMenuRoot"] + + _invalid_children: List[str] = ["ContextMenuContent"] + class ContextMenuContent(RadixThemesComponent): """Trigger an action or event, such as submitting a form or displaying a dialog.""" @@ -60,7 +66,7 @@ class ContextMenuContent(RadixThemesComponent): # The vertical distance in pixels from the anchor. align_offset: Var[int] - # When true, overrides the side andalign preferences to prevent collisions with boundary edges. + # When true, overrides the side and aligns preferences to prevent collisions with boundary edges. avoid_collisions: Var[bool] def get_event_triggers(self) -> Dict[str, Any]: @@ -93,6 +99,8 @@ class ContextMenuSubTrigger(RadixThemesComponent): # Whether the trigger is disabled disabled: Var[bool] + _valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSub"] + class ContextMenuSubContent(RadixThemesComponent): """Trigger an action or event, such as submitting a form or displaying a dialog.""" @@ -102,6 +110,8 @@ class ContextMenuSubContent(RadixThemesComponent): # When true, keyboard navigation will loop from last item to first, and vice versa. loop: Var[bool] + _valid_parents: List[str] = ["ContextMenuSub"] + def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. @@ -128,6 +138,8 @@ class ContextMenuItem(RadixThemesComponent): # Shortcut to render a menu item as a link shortcut: Var[str] + _valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSubContent"] + class ContextMenuSeparator(RadixThemesComponent): """Trigger an action or event, such as submitting a form or displaying a dialog.""" diff --git a/reflex/components/radix/themes/components/contextmenu.pyi b/reflex/components/radix/themes/components/contextmenu.pyi index d18956e4b..25e52fb12 100644 --- a/reflex/components/radix/themes/components/contextmenu.pyi +++ b/reflex/components/radix/themes/components/contextmenu.pyi @@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var from ..base import LiteralAccentColor, RadixThemesComponent @@ -472,7 +472,7 @@ class ContextMenuContent(RadixThemesComponent): variant: Variant of button: "solid" | "soft" | "outline" | "ghost" high_contrast: Whether to render the button with higher contrast color against background align_offset: The vertical distance in pixels from the anchor. - avoid_collisions: When true, overrides the side andalign preferences to prevent collisions with boundary edges. + avoid_collisions: When true, overrides the side and aligns preferences to prevent collisions with boundary edges. style: The style of the component. key: A unique key for the component. id: The id for the component. diff --git a/reflex/components/radix/themes/components/dropdownmenu.py b/reflex/components/radix/themes/components/dropdownmenu.py index c3dc50564..64686bd92 100644 --- a/reflex/components/radix/themes/components/dropdownmenu.py +++ b/reflex/components/radix/themes/components/dropdownmenu.py @@ -1,6 +1,6 @@ """Interactive components provided by @radix-ui/themes.""" from types import SimpleNamespace -from typing import Any, Dict, Literal, Union +from typing import Any, Dict, List, Literal, Union from reflex.constants import EventTriggers from reflex.vars import Var @@ -44,6 +44,8 @@ class DropdownMenuRoot(RadixThemesComponent): # The reading direction of submenus when applicable. If omitted, inherits globally from DirectionProvider or assumes LTR (left-to-right) reading mode. dir: Var[LiteralDirType] + _invalid_children: List[str] = ["DropdownMenuItem"] + def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. @@ -64,6 +66,10 @@ class DropdownMenuTrigger(RadixThemesComponent): # Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False. as_child: Var[bool] + _valid_parents: List[str] = ["DropdownMenuRoot"] + + _invalid_children: List[str] = ["DropdownMenuContent"] + class DropdownMenuContent(RadixThemesComponent): """The Dropdown Menu Content component that pops out when the dropdown menu is open.""" @@ -148,6 +154,8 @@ class DropdownMenuSubTrigger(RadixThemesComponent): # Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside. text_value: Var[str] + _valid_parents: List[str] = ["DropdownMenuContent", "DropdownMenuSub"] + class DropdownMenuSub(RadixThemesComponent): """Contains all the parts of a submenu.""" @@ -219,6 +227,8 @@ class DropdownMenuSubContent(RadixThemesComponent): # Whether to hide the content when the trigger becomes fully occluded. Defaults to False. hide_when_detached: Var[bool] + _valid_parents: List[str] = ["DropdownMenuSub"] + def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. @@ -254,6 +264,8 @@ class DropdownMenuItem(RadixThemesComponent): # Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside. text_value: Var[str] + _valid_parents: List[str] = ["DropdownMenuContent", "DropdownMenuSubContent"] + def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. diff --git a/reflex/components/radix/themes/components/dropdownmenu.pyi b/reflex/components/radix/themes/components/dropdownmenu.pyi index 4b1899ea8..32f752e13 100644 --- a/reflex/components/radix/themes/components/dropdownmenu.pyi +++ b/reflex/components/radix/themes/components/dropdownmenu.pyi @@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from types import SimpleNamespace -from typing import Any, Dict, Literal, Union +from typing import Any, Dict, List, Literal, Union from reflex.constants import EventTriggers from reflex.vars import Var from ..base import LiteralAccentColor, RadixThemesComponent diff --git a/reflex/components/radix/themes/components/select.py b/reflex/components/radix/themes/components/select.py index e8dbd7648..fc2feac2e 100644 --- a/reflex/components/radix/themes/components/select.py +++ b/reflex/components/radix/themes/components/select.py @@ -78,6 +78,8 @@ class SelectTrigger(RadixThemesComponent): # The placeholder of the select trigger placeholder: Var[str] + _valid_parents: List[str] = ["SelectRoot"] + class SelectContent(RadixThemesComponent): """The component that pops out when the select is open.""" @@ -127,6 +129,8 @@ class SelectGroup(RadixThemesComponent): tag = "Select.Group" + _valid_parents: List[str] = ["SelectContent"] + class SelectItem(RadixThemesComponent): """The component that contains the select items.""" @@ -139,12 +143,16 @@ class SelectItem(RadixThemesComponent): # Whether the select item is disabled disabled: Var[bool] + _valid_parents: List[str] = ["SelectGroup", "SelectContent"] + class SelectLabel(RadixThemesComponent): """Used to render the label of a group, it isn't focusable using arrow keys.""" tag = "Select.Label" + _valid_parents: List[str] = ["SelectGroup"] + class SelectSeparator(RadixThemesComponent): """Used to visually separate items in the Select.""" diff --git a/reflex/components/radix/themes/components/slider.py b/reflex/components/radix/themes/components/slider.py index c9ea861b0..ea54798b0 100644 --- a/reflex/components/radix/themes/components/slider.py +++ b/reflex/components/radix/themes/components/slider.py @@ -1,6 +1,7 @@ """Interactive components provided by @radix-ui/themes.""" -from typing import Any, Dict, List, Literal, Union +from typing import Any, Dict, List, Literal, Optional, Union +from reflex.components.component import Component from reflex.constants import EventTriggers from reflex.vars import Var @@ -35,7 +36,7 @@ class Slider(RadixThemesComponent): radius: Var[LiteralRadius] # The value of the slider when initially rendered. Use when you do not need to control the state of the slider. - default_value: Var[List[Union[float, int]]] + default_value: Var[Union[List[Union[float, int]], float, int]] # The controlled value of the slider. Must be used in conjunction with onValueChange. value: Var[List[Union[float, int]]] @@ -73,5 +74,39 @@ class Slider(RadixThemesComponent): EventTriggers.ON_VALUE_COMMIT: lambda e0: [e0], } + @classmethod + def create( + cls, + *children, + width: Optional[str] = "100%", + **props, + ) -> Component: + """Create a Slider component. -slider = Slider.create + Args: + *children: The children of the component. + width: The width of the slider. + **props: The properties of the component. + + Returns: + The component. + """ + default_value = props.pop("default_value", [50]) + + if isinstance(default_value, Var): + if issubclass(default_value._var_type, (int, float)): + default_value = [default_value] + + elif isinstance(default_value, (int, float)): + default_value = [default_value] + + style = props.setdefault("style", {}) + style.update( + { + "width": width, + } + ) + return super().create(*children, default_value=default_value, **props) + + +slider = Slider.create \ No newline at end of file diff --git a/reflex/components/radix/themes/components/slider.pyi b/reflex/components/radix/themes/components/slider.pyi index 28d4165d6..0c531dcbf 100644 --- a/reflex/components/radix/themes/components/slider.pyi +++ b/reflex/components/radix/themes/components/slider.pyi @@ -7,7 +7,8 @@ from typing import Any, Dict, Literal, Optional, Union, overload from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style -from typing import Any, Dict, List, Literal, Union +from typing import Any, Dict, List, Literal, Optional, Union +from reflex.components.component import Component from reflex.constants import EventTriggers from reflex.vars import Var from ..base import LiteralAccentColor, LiteralRadius, RadixThemesComponent @@ -19,7 +20,17 @@ class Slider(RadixThemesComponent): def create( # type: ignore cls, *children, - color: Optional[Union[Var[str], str]] = None, + width: Optional[str] = "100%", + as_child: Optional[Union[Var[bool], bool]] = 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[ @@ -82,16 +93,6 @@ class Slider(RadixThemesComponent): ], ] ] = None, - as_child: Optional[Union[Var[bool], bool]] = 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, high_contrast: Optional[Union[Var[bool], bool]] = None, radius: Optional[ Union[ @@ -100,7 +101,10 @@ class Slider(RadixThemesComponent): ] ] = None, default_value: Optional[ - Union[Var[List[Union[float, int]]], List[Union[float, int]]] + Union[ + Var[Union[List[Union[float, int]], float, int]], + Union[List[Union[float, int]], float, int], + ] ] = None, value: Optional[ Union[Var[List[Union[float, int]]], List[Union[float, int]]] @@ -176,18 +180,15 @@ class Slider(RadixThemesComponent): ] = None, **props ) -> "Slider": - """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. + """Create a Slider component. Args: - *children: Child components. - color: map to CSS default color property. - color_scheme: map to radix color property. + *children: The children of the component. + width: The width of the slider. as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. size: Button size "1" - "3" variant: Variant of button + 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" default_value: The value of the slider when initially rendered. Use when you do not need to control the state of the slider. @@ -205,10 +206,10 @@ class Slider(RadixThemesComponent): autofocus: Whether the component should take the focus once the page is loaded _rename_props: props to change the name of custom_attrs: custom attribute - **props: Component properties. + **props: The properties of the component. Returns: - A new component instance. + The component. """ ... diff --git a/reflex/components/radix/themes/components/table.py b/reflex/components/radix/themes/components/table.py index 5fe67ae0d..c50bc5eac 100644 --- a/reflex/components/radix/themes/components/table.py +++ b/reflex/components/radix/themes/components/table.py @@ -1,6 +1,6 @@ """Interactive components provided by @radix-ui/themes.""" from types import SimpleNamespace -from typing import Literal, Union +from typing import List, Literal, Union from reflex import el from reflex.vars import Var @@ -27,6 +27,10 @@ class TableHeader(el.Thead, RadixThemesComponent): tag = "Table.Header" + _invalid_children: List[str] = ["TableBody"] + + _valid_parents: List[str] = ["TableRoot"] + class TableRow(el.Tr, RadixThemesComponent): """A row containing table cells.""" @@ -36,6 +40,8 @@ class TableRow(el.Tr, RadixThemesComponent): # The alignment of the row align: Var[Literal["start", "center", "end", "baseline"]] + _invalid_children: List[str] = ["TableBody", "TableHeader", "TableRow"] + class TableColumnHeaderCell(el.Th, RadixThemesComponent): """A table cell that is semantically treated as a column header.""" @@ -48,12 +54,30 @@ class TableColumnHeaderCell(el.Th, RadixThemesComponent): # width of the column width: Var[Union[str, int]] + _invalid_children: List[str] = [ + "TableBody", + "TableHeader", + "TableRow", + "TableCell", + "TableColumnHeaderCell", + "TableRowHeaderCell", + ] + class TableBody(el.Tbody, RadixThemesComponent): """The body of the table contains the data rows.""" tag = "Table.Body" + _invalid_children: List[str] = [ + "TableHeader", + "TableRowHeaderCell", + "TableColumnHeaderCell", + "TableCell", + ] + + _valid_parents: List[str] = ["TableRoot"] + class TableCell(el.Td, RadixThemesComponent): """A cell containing data.""" @@ -66,6 +90,14 @@ class TableCell(el.Td, RadixThemesComponent): # width of the column width: Var[Union[str, int]] + _invalid_children: List[str] = [ + "TableBody", + "TableHeader", + "TableRowHeaderCell", + "TableColumnHeaderCell", + "TableCell", + ] + class TableRowHeaderCell(el.Th, RadixThemesComponent): """A table cell that is semantically treated as a row header.""" @@ -78,6 +110,15 @@ class TableRowHeaderCell(el.Th, RadixThemesComponent): # width of the column width: Var[Union[str, int]] + _invalid_children: List[str] = [ + "TableBody", + "TableHeader", + "TableRow", + "TableCell", + "TableColumnHeaderCell", + "TableRowHeaderCell", + ] + class Table(SimpleNamespace): """Table components namespace.""" diff --git a/reflex/components/radix/themes/components/table.pyi b/reflex/components/radix/themes/components/table.pyi index 8918332ea..0ce59ac2e 100644 --- a/reflex/components/radix/themes/components/table.pyi +++ b/reflex/components/radix/themes/components/table.pyi @@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from types import SimpleNamespace -from typing import Literal, Union +from typing import List, Literal, Union from reflex import el from reflex.vars import Var from ..base import RadixThemesComponent diff --git a/reflex/components/radix/themes/components/tabs.py b/reflex/components/radix/themes/components/tabs.py index 43cf4aa48..80e6a0a83 100644 --- a/reflex/components/radix/themes/components/tabs.py +++ b/reflex/components/radix/themes/components/tabs.py @@ -1,6 +1,6 @@ """Interactive components provided by @radix-ui/themes.""" from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var @@ -59,6 +59,8 @@ class TabsTrigger(RadixThemesComponent): # Whether the tab is disabled disabled: Var[bool] + _valid_parents: List[str] = ["TabsList"] + class TabsContent(RadixThemesComponent): """Trigger an action or event, such as submitting a form or displaying a dialog.""" diff --git a/reflex/components/radix/themes/components/tabs.pyi b/reflex/components/radix/themes/components/tabs.pyi index 2adf4b253..60f8047fd 100644 --- a/reflex/components/radix/themes/components/tabs.pyi +++ b/reflex/components/radix/themes/components/tabs.pyi @@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from types import SimpleNamespace -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var from ..base import RadixThemesComponent diff --git a/reflex/components/radix/themes/components/textarea.py b/reflex/components/radix/themes/components/textarea.py index 13b5ee308..7e38cb409 100644 --- a/reflex/components/radix/themes/components/textarea.py +++ b/reflex/components/radix/themes/components/textarea.py @@ -40,15 +40,9 @@ class TextArea(RadixThemesComponent, el.Textarea): Returns: The component. """ - if ( - isinstance(props.get("value"), Var) and props.get("on_change") - ) or "debounce_timeout" in props: - # Currently default to 50ms, which appears to be a good balance - debounce_timeout = props.pop("debounce_timeout", 50) + if props.get("value") is not None and props.get("on_change"): # create a debounced input if the user requests full control to avoid typing jank - return DebounceInput.create( - super().create(*children, **props), debounce_timeout=debounce_timeout - ) + return DebounceInput.create(super().create(*children, **props)) return super().create(*children, **props) def get_event_triggers(self) -> Dict[str, Any]: diff --git a/reflex/components/radix/themes/components/textfield.py b/reflex/components/radix/themes/components/textfield.py index 08ea90c19..218528962 100644 --- a/reflex/components/radix/themes/components/textfield.py +++ b/reflex/components/radix/themes/components/textfield.py @@ -56,15 +56,9 @@ class TextFieldInput(el.Input, TextFieldRoot): Returns: The component. """ - if ( - isinstance(props.get("value"), Var) and props.get("on_change") - ) or "debounce_timeout" in props: - # Currently default to 50ms, which appears to be a good balance - debounce_timeout = props.pop("debounce_timeout", 50) + if props.get("value") is not None and props.get("on_change"): # create a debounced input if the user requests full control to avoid typing jank - return DebounceInput.create( - super().create(*children, **props), debounce_timeout=debounce_timeout - ) + return DebounceInput.create(super().create(*children, **props)) return super().create(*children, **props) def get_event_triggers(self) -> Dict[str, Any]: diff --git a/reflex/style.py b/reflex/style.py index 700aceb74..6117b3369 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any +from typing import Any, Tuple from reflex import constants from reflex.event import EventChain @@ -40,6 +40,15 @@ toggle_color_mode = BaseVar( breakpoints = ["0", "30em", "48em", "62em", "80em", "96em"] +STYLE_PROP_SHORTHAND_MAPPING = { + "paddingX": ("padding-inline-start", "padding-inline-end"), + "paddingY": ("padding-top", "padding-bottom"), + "marginX": ("margin-inline-start", "margin-inline-end"), + "marginY": ("margin-top", "margin-bottom"), + "bg": ("background",), + "bgColor": ("background-color",), +} + def media_query(breakpoint_index: int): """Create a media query selector. @@ -110,21 +119,43 @@ def convert(style_dict): """ var_data = None # Track import/hook data from any Vars in the style dict. out = {} + + def update_out_dict(return_value, keys_to_update): + for k in keys_to_update: + out[k] = return_value + for key, value in style_dict.items(): - key = format.to_camel_case(key, allow_hyphens=True) + keys = format_style_key(key) if isinstance(value, dict): # Recursively format nested style dictionaries. - out[key], new_var_data = convert(value) + return_val, new_var_data = convert(value) + update_out_dict(return_val, keys) elif isinstance(value, list): # Responsive value is a list of dict or value - out[key], new_var_data = convert_list(value) + return_val, new_var_data = convert_list(value) + update_out_dict(return_val, keys) else: - out[key], new_var_data = convert_item(value) + return_val, new_var_data = convert_item(value) + update_out_dict(return_val, keys) # Combine all the collected VarData instances. var_data = VarData.merge(var_data, new_var_data) return out, var_data +def format_style_key(key: str) -> Tuple[str, ...]: + """Convert style keys to camel case and convert shorthand + styles names to their corresponding css names. + + Args: + key: The style key to convert. + + Returns: + Tuple of css style names corresponding to the key provided. + """ + key = format.to_camel_case(key, allow_hyphens=True) + return STYLE_PROP_SHORTHAND_MAPPING.get(key, (key,)) + + class Style(dict): """A style dictionary.""" diff --git a/tests/components/forms/test_debounce.py b/tests/components/forms/test_debounce.py index b4fca6566..a63604ca8 100644 --- a/tests/components/forms/test_debounce.py +++ b/tests/components/forms/test_debounce.py @@ -3,6 +3,7 @@ import pytest import reflex as rx +from reflex.components.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT from reflex.state import BaseState from reflex.vars import BaseVar @@ -107,7 +108,7 @@ def test_full_control_implicit_debounce(): value=S.value, on_change=S.on_change, )._render() - assert tag.props["debounceTimeout"]._var_name == "50" + assert tag.props["debounceTimeout"]._var_name == str(DEFAULT_DEBOUNCE_TIMEOUT) assert len(tag.props["onChange"].events) == 1 assert tag.props["onChange"].events[0].handler == S.on_change assert tag.contents == "" @@ -119,7 +120,7 @@ def test_full_control_implicit_debounce_text_area(): value=S.value, on_change=S.on_change, )._render() - assert tag.props["debounceTimeout"]._var_name == "50" + assert tag.props["debounceTimeout"]._var_name == str(DEFAULT_DEBOUNCE_TIMEOUT) assert len(tag.props["onChange"].events) == 1 assert tag.props["onChange"].events[0].handler == S.on_change assert tag.contents == "" diff --git a/tests/components/typography/test_markdown.py b/tests/components/typography/test_markdown.py index 3ce9a830c..e5580dd5f 100644 --- a/tests/components/typography/test_markdown.py +++ b/tests/components/typography/test_markdown.py @@ -56,4 +56,8 @@ def test_pass_custom_styles(): md = Markdown.create("# Hello", custom_styles={"h1": {"color": "red"}}) comp = md.get_component("h1") # type: ignore - assert comp.style == {"color": "red", "marginY": "0.5em"} + assert comp.style == { + "color": "red", + "margin-bottom": "0.5em", + "margin-top": "0.5em", + } diff --git a/tests/test_app.py b/tests/test_app.py index f6581e626..08b24b2ea 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1274,7 +1274,6 @@ def test_app_wrap_priority(compilable_app): "return (" "" "" - "" "" "" "" diff --git a/tests/test_style.py b/tests/test_style.py index 41a6c765c..79ecee23c 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -20,6 +20,19 @@ test_style = [ {"::-webkit-scrollbar": {"display": "none"}}, {"::-webkit-scrollbar": {"display": "none"}}, ), + ({"margin_y": "2rem"}, {"margin-bottom": "2rem", "margin-top": "2rem"}), + ({"marginY": "2rem"}, {"margin-bottom": "2rem", "margin-top": "2rem"}), + ( + {"::-webkit-scrollbar": {"bgColor": "red"}}, + {"::-webkit-scrollbar": {"background-color": "red"}}, + ), + ( + {"paddingX": ["2rem", "3rem"]}, + { + "padding-inline-start": ["2rem", "3rem"], + "padding-inline-end": ["2rem", "3rem"], + }, + ), ]