Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/app/news/+icon-gallery.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the five `grid_2x_2`/`grid_3x_3` icons rendering blank in the icon gallery, and lighten the page by replacing per-icon tooltip popovers with native titles.
20 changes: 9 additions & 11 deletions docs/library/data-display/icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ def lucide_icons() -> rx.Component:
LUCIDE_ICON_LIST,
lambda icon: rx.cond(
icon.startswith(icon_search_cs.value),
rx.tooltip(
rx.box(
rx.icon(tag=icon),
class_name="flex items-center justify-center rounded-md hover:bg-accent transition-bg p-2 cursor-pointer",
on_click=[
rx.set_clipboard(icon),
rx.toast.success(f"Copied {icon} to clipboard"),
],
key=icon,
),
content=icon,
rx.box(
rx.icon(tag=icon),
title=icon,
class_name="flex items-center justify-center rounded-md hover:bg-accent transition-bg p-2 cursor-pointer",
on_click=[
rx.set_clipboard(icon),
rx.toast.success(f"Copied {icon} to clipboard"),
],
key=icon,
),
None,
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `rx.icon` with a Var tag rendering blank for digit-x-digit icons such as `grid_2x_2`, `grid_2x_2_check` and `grid_3x_3`.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from reflex_base.components.component import Component, field
from reflex_base.utils import format
from reflex_base.utils.imports import ImportVar
from reflex_base.vars.base import LiteralVar, Var
from reflex_base.vars.base import LiteralVar, Var, var_operation, var_operation_return
from reflex_base.vars.sequence import LiteralStringVar, StringVar

logger = logging.getLogger(__name__)
Expand All @@ -20,6 +20,26 @@ class LucideIconComponent(Component):
library = LUCIDE_LIBRARY


@var_operation
def dynamic_icon_name_operation(tag: StringVar):
"""Convert an icon tag to the name Lucide's DynamicIcon expects.

Underscores become hyphens, and the hyphen that snake_case puts inside
digit-x-digit names (``grid_2x_2`` -> ``grid-2x-2``) is dropped so the
name matches Lucide's own ``grid-2x2``.

Args:
tag: The snake_case or kebab-case icon tag.

Returns:
The kebab-case Lucide icon name.
"""
return var_operation_return(
js_expression=f'{tag!s}.replaceAll("_", "-").replace(/(\\d)x-(\\d)/g, "$1x$2")',
var_type=str,
)


class Icon(LucideIconComponent):
"""An Icon component."""

Expand Down Expand Up @@ -74,7 +94,9 @@ def create(cls, *children, **props) -> Component:
if not isinstance(tag_stringified, StringVar):
msg = f"Icon name must be a string, got {tag_var._var_type}"
raise TypeError(msg)
return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props)
return DynamicIcon.create(
name=dynamic_icon_name_operation(tag_stringified), **props
)

if tag not in LUCIDE_ICON_LIST:
icons_sorted = sorted(
Expand Down
2 changes: 1 addition & 1 deletion pyi_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "7617635157b816f70354e2db98b38642",
"packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "1faf9b4e77e589f4d071483c14fe16f7",
"packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2ce1c076ecf5c2fa4945b4abdbf2f91d",
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "0bbc5e33f2c2175c3ddcbf1b08dc8923",
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "78d8643641b2338f59bf0212a30b025a",
"packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "6759daf6e242e209d9298045c7b4456a",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "21c3396cb28b1ef6442c5a9588dc708b",
"packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "f411341e59a3065fee964747584f9fcf",
Expand Down
26 changes: 26 additions & 0 deletions tests/units/components/lucide/test_icon.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

import pytest
from reflex_base.utils import format
from reflex_base.utils.imports import ImportVar
Expand Down Expand Up @@ -87,6 +89,30 @@ def test_dynamic_icon_uses_dynamic_module():
assert not import_var.is_default


def test_dynamic_icon_name_normalizes_digit_x_digit():
"""Var-tagged icons drop the hyphen snake_case leaves in ``grid_2x_2``."""
icon = Icon.create(Var("state_icon").to(str))
assert (
str(icon.render()["props"][0])
== 'name:state_icon.replaceAll("_", "-").replace(/(\\d)x-(\\d)/g, "$1x$2")'
)


@pytest.mark.parametrize("tag", LUCIDE_ICON_LIST)
def test_dynamic_icon_name_matches_lucide_name(tag):
"""The runtime name conversion agrees with the verified per-icon file names."""
# Mirror the JS expression emitted by dynamic_icon_name_operation.
name = re.sub(r"(\d)x-(\d)", r"\1x\2", tag.replace("_", "-"))
# "fingerprint" is a Lucide alias that DynamicIcon resolves under its own
# name; only the deep import needs the aliased file name.
expected = (
"fingerprint"
if tag == "fingerprint"
else LUCIDE_ICON_FILENAME_OVERRIDE.get(tag, format.to_kebab_case(tag))
)
assert name == expected


@pytest.mark.parametrize(
("tag", "expected"),
[
Expand Down
Loading