From 8e2c9681f79d7e5776805323645ef64d663cf206 Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Tue, 6 Feb 2024 18:55:00 +0000 Subject: [PATCH 1/6] Radix Components Valid children/parents (#2495) --- .../components/radix/primitives/accordion.py | 12 +++++- .../components/radix/primitives/accordion.pyi | 2 +- .../radix/themes/components/contextmenu.py | 16 ++++++- .../radix/themes/components/contextmenu.pyi | 4 +- .../radix/themes/components/dropdownmenu.py | 14 +++++- .../radix/themes/components/dropdownmenu.pyi | 2 +- .../radix/themes/components/select.py | 8 ++++ .../radix/themes/components/table.py | 43 ++++++++++++++++++- .../radix/themes/components/table.pyi | 2 +- .../radix/themes/components/tabs.py | 4 +- .../radix/themes/components/tabs.pyi | 2 +- 11 files changed, 97 insertions(+), 12 deletions(-) diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py index 0fc980c1a..29083bbd2 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/reflex/components/radix/primitives/accordion.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.components.component import Component from reflex.components.core import cond, match @@ -416,6 +416,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. @@ -506,6 +508,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 ec2635c39..1a440af58 100644 --- a/reflex/components/radix/primitives/accordion.pyi +++ b/reflex/components/radix/primitives/accordion.pyi @@ -7,7 +7,7 @@ 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, Literal +from typing import Any, Dict, List, Literal from reflex.components.component import Component from reflex.components.core import cond, 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 05277d05c..09f90dabe 100644 --- a/reflex/components/radix/themes/components/contextmenu.py +++ b/reflex/components/radix/themes/components/contextmenu.py @@ -1,5 +1,5 @@ """Interactive components provided by @radix-ui/themes.""" -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var @@ -18,6 +18,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. @@ -38,6 +40,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.""" @@ -59,7 +65,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]: @@ -92,6 +98,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.""" @@ -101,6 +109,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. @@ -127,6 +137,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 bf48232ce..42365f39b 100644 --- a/reflex/components/radix/themes/components/contextmenu.pyi +++ b/reflex/components/radix/themes/components/contextmenu.pyi @@ -7,7 +7,7 @@ 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, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var from ..base import LiteralAccentColor, RadixThemesComponent @@ -471,7 +471,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 fd14fc4f5..dfb56c359 100644 --- a/reflex/components/radix/themes/components/dropdownmenu.py +++ b/reflex/components/radix/themes/components/dropdownmenu.py @@ -1,5 +1,5 @@ """Interactive components provided by @radix-ui/themes.""" -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 @@ -43,6 +43,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. @@ -63,6 +65,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.""" @@ -147,6 +153,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.""" @@ -218,6 +226,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. @@ -253,6 +263,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 46ad8342d..9fdee9eb6 100644 --- a/reflex/components/radix/themes/components/dropdownmenu.pyi +++ b/reflex/components/radix/themes/components/dropdownmenu.pyi @@ -7,7 +7,7 @@ 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, 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 8d4467312..eab11dff3 100644 --- a/reflex/components/radix/themes/components/select.py +++ b/reflex/components/radix/themes/components/select.py @@ -77,6 +77,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.""" @@ -126,6 +128,8 @@ class SelectGroup(RadixThemesComponent): tag = "Select.Group" + _valid_parents: List[str] = ["SelectContent"] + class SelectItem(RadixThemesComponent): """The component that contains the select items.""" @@ -138,12 +142,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/table.py b/reflex/components/radix/themes/components/table.py index 4f67a2a2c..e627ebc14 100644 --- a/reflex/components/radix/themes/components/table.py +++ b/reflex/components/radix/themes/components/table.py @@ -1,5 +1,5 @@ """Interactive components provided by @radix-ui/themes.""" -from typing import Literal, Union +from typing import List, Literal, Union from reflex import el from reflex.vars import Var @@ -26,6 +26,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.""" @@ -35,6 +39,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.""" @@ -47,12 +53,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.""" @@ -65,6 +89,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.""" @@ -76,3 +108,12 @@ class TableRowHeaderCell(el.Th, RadixThemesComponent): # width of the column width: Var[Union[str, int]] + + _invalid_children: List[str] = [ + "TableBody", + "TableHeader", + "TableRow", + "TableCell", + "TableColumnHeaderCell", + "TableRowHeaderCell", + ] diff --git a/reflex/components/radix/themes/components/table.pyi b/reflex/components/radix/themes/components/table.pyi index cc38617b1..2a8df22da 100644 --- a/reflex/components/radix/themes/components/table.pyi +++ b/reflex/components/radix/themes/components/table.pyi @@ -7,7 +7,7 @@ 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 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 bb3270dd3..bbef9a07a 100644 --- a/reflex/components/radix/themes/components/tabs.py +++ b/reflex/components/radix/themes/components/tabs.py @@ -1,5 +1,5 @@ """Interactive components provided by @radix-ui/themes.""" -from typing import Any, Dict, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var @@ -58,6 +58,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 5bceb6f3d..8a26dabfe 100644 --- a/reflex/components/radix/themes/components/tabs.pyi +++ b/reflex/components/radix/themes/components/tabs.pyi @@ -7,7 +7,7 @@ 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, Literal +from typing import Any, Dict, List, Literal from reflex.constants import EventTriggers from reflex.vars import Var from ..base import RadixThemesComponent From e2c3081d1e145b1c3c1ed2d9a0ab7b74cedf287a Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Wed, 7 Feb 2024 02:36:12 +0700 Subject: [PATCH 2/6] Remove focus-visible package (#2535) --- reflex/components/chakra/base.py | 49 ------------------ reflex/components/chakra/base.pyi | 83 ------------------------------- tests/test_app.py | 1 - 3 files changed, 133 deletions(-) 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/tests/test_app.py b/tests/test_app.py index b9a018434..756b701fa 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1273,7 +1273,6 @@ def test_app_wrap_priority(compilable_app): "return (" "" "" - "" "" "" "" From 51a9b75141757712a7cfb9917b9f66867793617a Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Wed, 7 Feb 2024 05:27:44 +0700 Subject: [PATCH 3/6] Don't purge web dir if flag is set (#2529) --- reflex/compiler/compiler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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"]) From eacd5341371843765591eea8353899840f4bf641 Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Thu, 8 Feb 2024 02:55:43 +0700 Subject: [PATCH 4/6] Increase debounce timeout to 300ms (#2541) --- reflex/components/chakra/forms/input.py | 10 ++-------- reflex/components/chakra/forms/textarea.py | 10 ++-------- reflex/components/core/debounce.py | 4 +++- reflex/components/core/debounce.pyi | 2 ++ reflex/components/radix/themes/components/textarea.py | 10 ++-------- reflex/components/radix/themes/components/textfield.py | 10 ++-------- tests/components/forms/test_debounce.py | 5 +++-- 7 files changed, 16 insertions(+), 35 deletions(-) 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/themes/components/textarea.py b/reflex/components/radix/themes/components/textarea.py index 0fe0ca575..f196c12d9 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 1b840cd74..fba6362b9 100644 --- a/reflex/components/radix/themes/components/textfield.py +++ b/reflex/components/radix/themes/components/textfield.py @@ -54,15 +54,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/tests/components/forms/test_debounce.py b/tests/components/forms/test_debounce.py index 97cfa8648..d893ba02c 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 == "" From ce867d2f81c898ae8104a3756af740574184c2c7 Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Wed, 7 Feb 2024 20:05:04 +0000 Subject: [PATCH 5/6] Support for Shorthand css props. (#2547) --- reflex/style.py | 41 +++++++++++++++++--- tests/components/typography/test_markdown.py | 6 ++- tests/test_style.py | 13 +++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/reflex/style.py b/reflex/style.py index accd25db4..692965c8a 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 @@ -37,6 +37,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. @@ -107,21 +116,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/typography/test_markdown.py b/tests/components/typography/test_markdown.py index 69c8e4a0c..2d136b498 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_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"], + }, + ), ] From 57b75c64975db91d772e65c9e1495a8a87dea368 Mon Sep 17 00:00:00 2001 From: Tom Gotsman <64492814+tgberkeley@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:06:41 -0800 Subject: [PATCH 6/6] update slider so width automatically set (#2542) --- .../radix/themes/components/slider.py | 39 +++++++++++++++- .../radix/themes/components/slider.pyi | 45 ++++++++++--------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/reflex/components/radix/themes/components/slider.py b/reflex/components/radix/themes/components/slider.py index 2914d2f62..1ed5de004 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]]] @@ -72,3 +73,37 @@ class Slider(RadixThemesComponent): EventTriggers.ON_CHANGE: lambda e0: [e0], EventTriggers.ON_VALUE_COMMIT: lambda e0: [e0], } + + @classmethod + def create( + cls, + *children, + width: Optional[str] = "100%", + **props, + ) -> Component: + """Create a Slider component. + + 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) diff --git a/reflex/components/radix/themes/components/slider.pyi b/reflex/components/radix/themes/components/slider.pyi index 305a2b262..2c1ad4f6f 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,9 +206,9 @@ 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. """ ...