From 6d86dff09d9c135b8e983d0a1bd78d3bb05d4cdb Mon Sep 17 00:00:00 2001 From: Lendemor Date: Tue, 14 Jan 2025 20:43:50 +0100 Subject: [PATCH 1/3] allow dynamic icons name --- reflex/components/lucide/icon.py | 24 ++++++++++++++- reflex/components/lucide/icon.pyi | 49 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 04410ac566..516f5b21e9 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -2,13 +2,14 @@ from reflex.components.component import Component from reflex.utils import format +from reflex.utils.imports import ImportVar from reflex.vars.base import Var class LucideIconComponent(Component): """Lucide Icon Component.""" - library = "lucide-react@0.469.0" + library = "lucide-react@0.471.1" class Icon(LucideIconComponent): @@ -47,6 +48,10 @@ def create(cls, *children, **props) -> Component: if "tag" not in props: raise AttributeError("Missing 'tag' keyword-argument for Icon") + if isinstance(props["tag"], Var): + icon_name = props.pop("tag") + return DynamicIcon.create(name=icon_name, **props) + if ( not isinstance(props["tag"], str) or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST @@ -67,6 +72,21 @@ def create(cls, *children, **props) -> Component: return super().create(*children, **props) +class DynamicIcon(LucideIconComponent): + """A DynamicIcon component.""" + + tag = "DynamicIcon" + + name: Var[str] + + def _get_imports(self): + _imports = super()._get_imports() + if self.library: + _imports.pop(self.library) + _imports["lucide-react/dynamic"] = [ImportVar("DynamicIcon", install=False)] + return _imports + + LUCIDE_ICON_LIST = [ "a_arrow_down", "a_arrow_up", @@ -846,6 +866,7 @@ def create(cls, *children, **props) -> Component: "house", "house_plug", "house_plus", + "house_wifi", "ice_cream_bowl", "ice_cream_cone", "id_card", @@ -1534,6 +1555,7 @@ def create(cls, *children, **props) -> Component: "trending_up_down", "triangle", "triangle_alert", + "triangle_dashed", "triangle_right", "trophy", "truck", diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index 39a1da0e61..13793d410d 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -110,6 +110,53 @@ class Icon(LucideIconComponent): """ ... +class DynamicIcon(LucideIconComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + name: 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, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[[], BASE_STATE]] = None, + on_click: Optional[EventType[[], BASE_STATE]] = None, + on_context_menu: Optional[EventType[[], BASE_STATE]] = None, + on_double_click: Optional[EventType[[], BASE_STATE]] = None, + on_focus: Optional[EventType[[], BASE_STATE]] = None, + on_mount: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_down: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_move: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_out: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_over: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_up: Optional[EventType[[], BASE_STATE]] = None, + on_scroll: Optional[EventType[[], BASE_STATE]] = None, + on_unmount: Optional[EventType[[], BASE_STATE]] = None, + **props, + ) -> "DynamicIcon": + """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 + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + LUCIDE_ICON_LIST = [ "a_arrow_down", "a_arrow_up", @@ -889,6 +936,7 @@ LUCIDE_ICON_LIST = [ "house", "house_plug", "house_plus", + "house_wifi", "ice_cream_bowl", "ice_cream_cone", "id_card", @@ -1577,6 +1625,7 @@ LUCIDE_ICON_LIST = [ "trending_up_down", "triangle", "triangle_alert", + "triangle_dashed", "triangle_right", "trophy", "truck", From 864bcf42749df07138f3a91b0ae6cf99cf0006e3 Mon Sep 17 00:00:00 2001 From: Lendemor Date: Tue, 14 Jan 2025 21:46:46 +0100 Subject: [PATCH 2/3] handle literal vars --- reflex/components/lucide/icon.py | 14 ++++++++++++-- reflex/components/lucide/icon.pyi | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 516f5b21e9..459f4f04b3 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -3,7 +3,8 @@ from reflex.components.component import Component from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.vars.base import Var +from reflex.vars.base import LiteralVar, Var +from reflex.vars.sequence import LiteralStringVar class LucideIconComponent(Component): @@ -33,6 +34,7 @@ def create(cls, *children, **props) -> Component: Raises: AttributeError: The errors tied to bad usage of the Icon component. ValueError: If the icon tag is invalid. + TypeError: If the icon name is not a string. Returns: The created component. @@ -48,8 +50,16 @@ def create(cls, *children, **props) -> Component: if "tag" not in props: raise AttributeError("Missing 'tag' keyword-argument for Icon") + if isinstance(props["tag"], LiteralVar): + if isinstance(props["tag"], LiteralStringVar): + props["tag"] = props["tag"]._var_value + else: + raise TypeError("Icon name must be a string") + if isinstance(props["tag"], Var): - icon_name = props.pop("tag") + icon_name: Var = props.pop("tag") + if icon_name._var_type is not str: + raise TypeError("Icon name must be a string") return DynamicIcon.create(name=icon_name, **props) if ( diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index 13793d410d..6094cfd87d 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -104,6 +104,7 @@ class Icon(LucideIconComponent): Raises: AttributeError: The errors tied to bad usage of the Icon component. ValueError: If the icon tag is invalid. + TypeError: If the icon name is not a string. Returns: The created component. From f7daa27cd6194c3e90be2bc813bec757a4148916 Mon Sep 17 00:00:00 2001 From: Lendemor Date: Wed, 15 Jan 2025 17:59:57 +0100 Subject: [PATCH 3/3] clean up code --- reflex/components/lucide/icon.py | 34 +++++++++++++------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 459f4f04b3..6c7cbede7d 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -42,7 +42,6 @@ def create(cls, *children, **props) -> Component: if children: if len(children) == 1 and isinstance(children[0], str): props["tag"] = children[0] - children = [] else: raise AttributeError( f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix" @@ -50,36 +49,31 @@ def create(cls, *children, **props) -> Component: if "tag" not in props: raise AttributeError("Missing 'tag' keyword-argument for Icon") - if isinstance(props["tag"], LiteralVar): - if isinstance(props["tag"], LiteralStringVar): - props["tag"] = props["tag"]._var_value + tag: str | Var | LiteralVar = props.pop("tag") + if isinstance(tag, LiteralVar): + if isinstance(tag, LiteralStringVar): + tag = tag._var_value else: - raise TypeError("Icon name must be a string") - - if isinstance(props["tag"], Var): - icon_name: Var = props.pop("tag") - if icon_name._var_type is not str: - raise TypeError("Icon name must be a string") - return DynamicIcon.create(name=icon_name, **props) + raise TypeError(f"Icon name must be a string, got {type(tag)}") + elif isinstance(tag, Var): + return DynamicIcon.create(name=tag, **props) if ( - not isinstance(props["tag"], str) - or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST + not isinstance(tag, str) + or format.to_snake_case(tag) not in LUCIDE_ICON_LIST ): raise ValueError( - f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..." + f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..." "\nSee full list at https://lucide.dev/icons." ) - if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE: - props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]] + if tag in LUCIDE_ICON_MAPPING_OVERRIDE: + props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag] else: - props["tag"] = ( - format.to_title_case(format.to_snake_case(props["tag"])) + "Icon" - ) + props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon" props["alias"] = f"Lucide{props['tag']}" props.setdefault("color", "var(--current-color)") - return super().create(*children, **props) + return super().create(**props) class DynamicIcon(LucideIconComponent):