Skip to content

Commit 2317bf8

Browse files
authored
ENG-6171: Link component (#20)
1 parent 51ccc4a commit 2317bf8

File tree

5 files changed

+110
-41
lines changed

5 files changed

+110
-41
lines changed

demo/demo/demo.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,6 @@ def index() -> rx.Component:
2323
),
2424
content="Seriously, click me",
2525
),
26-
ui.dialog(
27-
trigger=ui.button(
28-
ui.icon("Add02Icon"),
29-
"Open dialog",
30-
variant="secondary",
31-
),
32-
title="Welcome to My App",
33-
description="This dialog provides an overview of the application features and functionality.",
34-
content=rx.el.div(
35-
rx.el.p(
36-
"Did you click the button?",
37-
class_name="text-secondary-11 text-sm font-medium",
38-
),
39-
rx.el.div(
40-
ui.dialog.close(
41-
render_=ui.button(
42-
"Cancel",
43-
variant="outline",
44-
),
45-
),
46-
ui.button(
47-
"Click me",
48-
on_click=rx.toast.success("You are cool :)"),
49-
),
50-
class_name="flex flex-row gap-2 justify-end",
51-
),
52-
class_name="flex flex-col gap-2 w-full",
53-
),
54-
),
5526
ui.checkbox(
5627
label="Click me",
5728
on_checked_change=lambda value: rx.toast.success(f"Value: {value}"),
@@ -62,10 +33,6 @@ def index() -> rx.Component:
6233
on_value_committed=lambda value: rx.toast.success(f"Value: {value}"),
6334
class_name="max-w-xs",
6435
),
65-
rx.el.p(
66-
State.seed,
67-
class_name="text-secondary-11 text-sm font-medium",
68-
),
6936
ui.gradient_profile(
7037
seed=State.seed,
7138
class_name="size-10",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.0.1"
44
description = "A set of reusable components built on top of Base UI and Tailwind, designed for use across any Reflex project"
55
readme = "README.md"
66
requires-python = ">=3.13"
7-
dependencies = ["reflex>=0.8.0"]
7+
dependencies = ["reflex>=0.8.1"]
88

99
[build-system]
1010
requires = ["hatchling", "uv-dynamic-versioning"]

reflex_ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"components.base.checkbox": ["checkbox"],
1111
"components.base.dialog": ["dialog"],
1212
"components.base.gradient_profile": ["gradient_profile"],
13+
"components.base.link": ["link"],
1314
"components.base.popover": ["popover"],
1415
"components.base.scroll_area": ["scroll_area"],
1516
"components.base.select": ["select"],

reflex_ui/components/base/link.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Custom link component."""
2+
3+
from typing import Literal
4+
5+
from reflex.components.react_router.dom import ReactRouterLink
6+
from reflex.vars import Var
7+
8+
from reflex_ui.components.component import CoreComponent
9+
from reflex_ui.components.icons.hugeicon import hi
10+
11+
LiteralLinkVariant = Literal["primary", "secondary"]
12+
LiteralLinkSize = Literal["xs", "sm", "md", "lg", "xl"]
13+
14+
15+
class ClassNames:
16+
"""Class names for the link component."""
17+
18+
ROOT = "font-medium underline-offset-2 hover:underline w-fit group/link relative"
19+
20+
21+
LINK_VARIANTS: dict[str, dict[str, str]] = {
22+
"size": {
23+
"xs": "text-xs",
24+
"sm": "text-sm",
25+
"md": "text-md",
26+
"lg": "text-lg",
27+
"xl": "text-xl",
28+
},
29+
"variant": {
30+
"primary": "text-primary-11",
31+
"secondary": "text-secondary-11",
32+
},
33+
}
34+
35+
36+
class Link(ReactRouterLink, CoreComponent):
37+
"""Link component."""
38+
39+
# The size of the link. Defaults to "sm".
40+
size: Var[LiteralLinkSize]
41+
42+
# The variant of the link. Defaults to "secondary".
43+
variant: Var[LiteralLinkVariant]
44+
45+
# Whether to show the icon. Defaults to False.
46+
show_icon: Var[bool]
47+
48+
@classmethod
49+
def create(cls, *children, **props) -> ReactRouterLink:
50+
"""Create the link component."""
51+
size = props.pop("size", "sm")
52+
cls.validate_size(size)
53+
variant = props.pop("variant", "secondary")
54+
cls.validate_variant(variant)
55+
show_icon = props.pop("show_icon", False)
56+
57+
# Apply default styling
58+
cls.set_class_name(
59+
f"{ClassNames.ROOT} {LINK_VARIANTS['size'][size]} {LINK_VARIANTS['variant'][variant]}",
60+
props,
61+
)
62+
63+
children = list(children)
64+
if show_icon:
65+
children.append(
66+
hi(
67+
"LinkSquare02Icon",
68+
class_name="absolute top-1/2 -translate-y-1/2 right-[-1.375rem] group-hover/link:opacity-100 text-secondary-9 opacity-0",
69+
),
70+
)
71+
72+
return super().create(*children, **props)
73+
74+
@staticmethod
75+
def validate_variant(variant: LiteralLinkVariant):
76+
"""Validate the link variant."""
77+
if variant not in LINK_VARIANTS["variant"]:
78+
available_variants = ", ".join(LINK_VARIANTS["variant"].keys())
79+
message = (
80+
f"Invalid variant: {variant}. Available variants: {available_variants}"
81+
)
82+
raise ValueError(message)
83+
84+
@staticmethod
85+
def validate_size(size: LiteralLinkSize):
86+
"""Validate the link size."""
87+
if size not in LINK_VARIANTS["size"]:
88+
available_sizes = ", ".join(LINK_VARIANTS["size"].keys())
89+
message = f"Invalid size: {size}. Available sizes: {available_sizes}"
90+
raise ValueError(message)
91+
92+
def _exclude_props(self) -> list[str]:
93+
return [
94+
*super()._exclude_props(),
95+
"size",
96+
"variant",
97+
"show_icon",
98+
]
99+
100+
101+
link = Link.create

uv.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)