Skip to content

Commit

Permalink
allow dynamic icons name (#4636)
Browse files Browse the repository at this point in the history
* allow dynamic icons name

* handle literal vars

* clean up code
  • Loading branch information
Lendemor authored Jan 18, 2025
1 parent 6e54652 commit 4da32a1
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 12 deletions.
50 changes: 38 additions & 12 deletions reflex/components/lucide/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

from reflex.components.component import Component
from reflex.utils import format
from reflex.vars.base import Var
from reflex.utils.imports import ImportVar
from reflex.vars.base import LiteralVar, Var
from reflex.vars.sequence import LiteralStringVar


class LucideIconComponent(Component):
"""Lucide Icon Component."""

library = "lucide-react@0.469.0"
library = "lucide-react@0.471.1"


class Icon(LucideIconComponent):
Expand All @@ -32,39 +34,61 @@ 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.
"""
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"
)
if "tag" not in props:
raise AttributeError("Missing 'tag' keyword-argument for Icon")

tag: str | Var | LiteralVar = props.pop("tag")
if isinstance(tag, LiteralVar):
if isinstance(tag, LiteralStringVar):
tag = tag._var_value
else:
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):
"""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 = [
Expand Down Expand Up @@ -846,6 +870,7 @@ def create(cls, *children, **props) -> Component:
"house",
"house_plug",
"house_plus",
"house_wifi",
"ice_cream_bowl",
"ice_cream_cone",
"id_card",
Expand Down Expand Up @@ -1534,6 +1559,7 @@ def create(cls, *children, **props) -> Component:
"trending_up_down",
"triangle",
"triangle_alert",
"triangle_dashed",
"triangle_right",
"trophy",
"truck",
Expand Down
50 changes: 50 additions & 0 deletions reflex/components/lucide/icon.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,60 @@ 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.
"""
...

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",
Expand Down Expand Up @@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [
"house",
"house_plug",
"house_plus",
"house_wifi",
"ice_cream_bowl",
"ice_cream_cone",
"id_card",
Expand Down Expand Up @@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [
"trending_up_down",
"triangle",
"triangle_alert",
"triangle_dashed",
"triangle_right",
"trophy",
"truck",
Expand Down

0 comments on commit 4da32a1

Please sign in to comment.