diff --git a/app/examples/components.py b/app/examples/components.py
index 0541905..051d7a8 100644
--- a/app/examples/components.py
+++ b/app/examples/components.py
@@ -36,9 +36,9 @@ def card_one() -> rx.Component:
),
rx.el.div(
rx.el.div(
- button("Default", variant="default", size="sm"),
- button("Secondary", variant="secondary", size="sm"),
- button("Outline", variant="outline", size="sm"),
+ button("Default", variant="default"),
+ button("Secondary", variant="secondary"),
+ button("Outline", variant="outline"),
class_name="w-full grid grid-cols-3 items-center gap-x-2",
),
rx.el.div(
@@ -53,8 +53,8 @@ def card_one() -> rx.Component:
class_name="flex flex-col gap-y-4",
),
rx.el.div(
- button("Close", variant="default", size="sm"),
- button("Send Text", variant="outline", size="sm"),
+ button("Close", variant="default"),
+ button("Send Text", variant="outline"),
class_name="w-full grid grid-cols-2 items-center gap-x-2",
),
class_name="w-full flex flex-col gap-y-card",
@@ -65,32 +65,24 @@ def card_one() -> rx.Component:
def card_two() -> rx.Component:
return rx.el.div(
rx.el.div(
- rx.el.p("Project Team", class_name="text-lg font-semibold text-foreground"),
- rx.el.p(
- "Manage collaborators",
- class_name="text-sm font-light text-muted-foreground",
- ),
- class_name="flex flex-col gap-y-1",
- ),
- rx.el.div(
- rx.el.div(
- avatar(
- src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@buridan-ui",
- fallback="BUI",
- class_name="size-12",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-12",
+ avatar.group(
+ avatar.root(
+ avatar.image(
+ src="https://github.com/reflex-dev.png",
+ custom_attrs={"alt": "@reflex"},
+ ),
+ avatar.fallback("RD"),
+ size="xl",
),
- class_name=(
- "flex -space-x-2 "
- "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] "
- "*:data-[slot=avatar]:grayscale"
+ avatar.root(
+ avatar.image(
+ src="https://github.com/LineIndent.png",
+ custom_attrs={"alt": "LineIndent"},
+ ),
+ avatar.fallback("LI"),
+ size="xl",
),
+ class_name="grayscale",
),
rx.el.p(
"No Team Members", class_name="text-md font-medium text-foreground"
@@ -132,10 +124,10 @@ def card_three() -> rx.Component:
),
rx.el.div(
rx.el.div(
- button("Cooking", variant="outline", size="sm"),
- button("Dining", variant="outline", size="sm"),
- button("Light", variant="outline", size="sm"),
- button("Focus", variant="outline", size="sm"),
+ button("Cooking", variant="outline"),
+ button("Dining", variant="outline"),
+ button("Light", variant="outline"),
+ button("Focus", variant="outline"),
class_name="w-full grid grid-cols-4 items-center gap-x-2",
),
rx.el.div(
@@ -160,7 +152,7 @@ def card_three() -> rx.Component:
default_value=item["value"],
),
),
- class_name="w-full grid grid-cols-2 items-center gap-x-2 border border-input rounded-radius p-2.5",
+ class_name="w-full grid grid-cols-2 items-center gap-x-2 border border-input rounded-lg p-2.5",
)
for item in room_controls
],
@@ -354,7 +346,6 @@ def card_five() -> rx.Component:
button(
hi("Cancel01Icon", class_name="size-4"),
variant="ghost",
- size="sm",
class_name="!px-2 !py-2 self-start",
),
class_name="w-full flex flex-row items-start justify-between",
@@ -391,7 +382,7 @@ def card_five() -> rx.Component:
"Business Account (--3301) — $5,200.00", value="business"
),
class_name=(
- "w-full rounded-radius border border-input bg-secondary "
+ "w-full rounded-lg border border-input bg-secondary "
"px-3 py-2 text-sm text-foreground appearance-none cursor-pointer "
"focus:outline-none focus:ring-1 focus:ring-input"
),
@@ -409,7 +400,7 @@ def card_five() -> rx.Component:
),
rx.el.option("Roth IRA (··7745) — $18,300.00", value="ira"),
class_name=(
- "w-full rounded-radius border border-input bg-secondary "
+ "w-full rounded-lg border border-input bg-secondary "
"px-3 py-2 text-sm text-foreground appearance-none cursor-pointer "
"focus:outline-none focus:ring-1 focus:ring-input"
),
@@ -436,7 +427,7 @@ def card_five() -> rx.Component:
)
for row in summary_rows
],
- class_name="w-full flex flex-col rounded-radius border border-input px-3",
+ class_name="w-full flex flex-col rounded-lg border border-input px-3",
),
rx.el.div(
button("Confirm Transfer", variant="default", class_name="w-full"),
@@ -504,7 +495,7 @@ def card_seven() -> rx.Component:
"Pending Setup",
class_name="text-xs font-medium text-foreground",
),
- class_name="flex flex-row items-center gap-x-2 px-3 py-1.5 rounded-radius border border-input bg-secondary w-fit",
+ class_name="flex flex-row items-center gap-x-2 px-3 py-1.5 rounded-lg border border-input bg-secondary w-fit",
),
class_name="w-full flex flex-col gap-y-3",
),
@@ -532,7 +523,7 @@ def card_seven() -> rx.Component:
)
for row in summary_rows
],
- class_name="w-full flex flex-col bg-secondary rounded-radius px-3",
+ class_name="w-full flex flex-col bg-secondary rounded-lg px-3",
),
# Footer note
rx.el.div(
@@ -640,10 +631,10 @@ def goal_item(goal: dict) -> rx.Component:
# Progress bar
rx.el.div(
rx.el.div(
- class_name="h-full bg-primary rounded-radius",
+ class_name="h-full bg-primary rounded-lg",
style={"width": f"{goal['percent']}%"},
),
- class_name="w-full h-1 bg-input rounded-radius overflow-hidden",
+ class_name="w-full h-1 bg-input rounded-lg overflow-hidden",
),
# Percent + amount row
rx.el.div(
@@ -657,7 +648,7 @@ def goal_item(goal: dict) -> rx.Component:
),
class_name="w-full flex flex-row items-center justify-between",
),
- class_name="w-full flex flex-col gap-y-3 bg-secondary rounded-radius p-4",
+ class_name="w-full flex flex-col gap-y-3 bg-secondary rounded-lg p-4",
)
return rx.el.div(
@@ -674,7 +665,7 @@ def goal_item(goal: dict) -> rx.Component:
),
class_name="flex flex-col gap-y-0.5",
),
- button("New Goal", variant="outline", size="sm"),
+ button("New Goal", variant="outline"),
class_name="w-full flex flex-row items-start justify-between",
),
# Goal items
@@ -756,8 +747,8 @@ def card_ten() -> rx.Component:
),
# Buttons
rx.el.div(
- button("Discard", variant="secondary", size="sm"),
- button("Save Changes", variant="default", size="sm"),
+ button("Discard", variant="secondary"),
+ button("Save Changes", variant="default"),
class_name="w-full flex flex-row items-center justify-end gap-x-2",
),
class_name="w-full flex flex-col gap-y-card text-foreground",
@@ -782,13 +773,11 @@ def codespaces_panel() -> rx.Component:
button(
hi("Add01Icon", class_name="size-4"),
variant="ghost",
- size="sm",
class_name="!px-2 !py-2",
),
button(
hi("MoreHorizontalIcon", class_name="size-4"),
variant="ghost",
- size="sm",
class_name="!px-2 !py-2",
),
class_name="flex flex-row items-center gap-x-1",
@@ -809,7 +798,7 @@ def codespaces_panel() -> rx.Component:
"You don't have any codespaces with this repository checked out",
class_name="text-xs font-light text-muted-foreground text-center max-w-[200px]",
),
- button("Create Codespace", variant="default", size="sm"),
+ button("Create Codespace", variant="default"),
rx.el.p(
"Learn more about codespaces",
class_name="text-xs text-muted-foreground underline underline-offset-2 cursor-pointer",
@@ -854,7 +843,7 @@ def local_panel() -> rx.Component:
"Clone using the web URL.",
class_name="text-xs font-light text-muted-foreground px-1",
),
- class_name="w-full flex flex-col gap-y-2 border border-input rounded-radius p-2",
+ class_name="w-full flex flex-col gap-y-2 border border-input rounded-lg p-2",
),
class_name="w-full pt-3",
),
@@ -982,7 +971,7 @@ def card_twelve() -> rx.Component:
),
rx.el.div(
hi("Cancel01Icon", class_name="size-3.5 text-foreground"),
- class_name="p-1 flex items-center justify-center bg-secondary rounded-radius",
+ class_name="p-1 flex items-center justify-center bg-secondary rounded-lg",
),
class_name="w-full flex flex-row items-center justify-between",
),
@@ -1005,7 +994,7 @@ def card_twelve() -> rx.Component:
rx.el.option("EUR — Euro", value="eur"),
rx.el.option("GBP — British Pound", value="gbp"),
class_name=(
- "w-full rounded-radius border border-input bg-secondary "
+ "w-full rounded-lg border border-input bg-secondary "
"px-3 py-2 text-sm text-foreground appearance-none cursor-pointer "
"focus:outline-none focus:ring-1 focus:ring-input"
),
@@ -1039,8 +1028,8 @@ def card_twelve() -> rx.Component:
),
# Buttons
rx.el.div(
- button("Reset", variant="outline", size="sm"),
- button("Save Preferences", variant="default", size="sm"),
+ button("Reset", variant="outline"),
+ button("Save Preferences", variant="default"),
class_name="w-full flex flex-row items-center justify-between pt-2",
),
class_name="w-full flex flex-col gap-y-card text-foreground",
@@ -1144,17 +1133,17 @@ def day_bar(day: dict) -> rx.Component:
rx.el.div(
# Grey background portion (remaining capacity)
rx.el.div(
- class_name="w-full bg-secondary rounded-radius",
+ class_name="w-full bg-secondary rounded-lg",
style={"height": f"{100 - day['calories']}%"},
),
# Green filled portion
rx.el.div(
- class_name="w-full bg-chart-1 rounded-radius",
+ class_name="w-full bg-chart-1 rounded-lg",
style={"height": f"{day['calories']}%"},
),
class_name="w-full flex-1 flex flex-col justify-end gap-y-0.5 overflow-hidden",
),
- class_name="flex flex-col items-center gap-y-2 border border-input rounded-radius p-2 flex-1 h-36",
+ class_name="flex flex-col items-center gap-y-2 border border-input rounded-lg p-2 flex-1 h-36",
)
return rx.el.div(
@@ -1225,7 +1214,7 @@ def card_fifteen() -> rx.Component:
def bar_column(height: int, color: str) -> rx.Component:
return rx.el.div(
- class_name=f"w-5 rounded-radius {color}",
+ class_name=f"w-5 rounded-lg {color}",
style={"height": f"{height}%"},
)
@@ -1289,8 +1278,8 @@ def sleep_group(group: dict) -> rx.Component:
rx.el.div(class_name="w-full h-px bg-input"),
# Footer
rx.el.div(
- button("Good", variant="outline", size="sm", class_name="rounded-full"),
- button("Details", variant="outline", size="sm"),
+ button("Good", variant="outline", class_name="rounded-full"),
+ button("Details", variant="outline"),
class_name="w-full flex flex-row items-center justify-between pt-2",
),
class_name="w-full flex flex-col gap-y-card text-foreground",
@@ -1417,7 +1406,7 @@ def card_eighteen() -> rx.Component:
class_name="text-sm font-semibold text-foreground",
),
rx.el.div(
- *[button(t, size="sm", variant="secondary") for t in times],
+ *[button(t, variant="secondary") for t in times],
class_name="w-full flex flex-row flex-wrap gap-2",
),
class_name="w-full flex flex-col gap-y-3",
@@ -1429,7 +1418,7 @@ def card_eighteen() -> rx.Component:
"Please arrive 15 minutes early.",
class_name="text-sm font-light text-muted-foreground",
),
- class_name="w-full flex flex-col gap-y-1 border border-input rounded-radius p-3",
+ class_name="w-full flex flex-col gap-y-1 border border-input rounded-lg p-3",
),
rx.el.div(
button("Book Appointment", class_name="w-full"),
@@ -1520,7 +1509,7 @@ def swatch(color: dict) -> rx.Component:
border_class = "border border-input" if color.get("border") else ""
return rx.el.div(
rx.el.div(
- class_name=f"rounded-radius {color['bg']} {border_class} size-12 flex-shrink-0",
+ class_name=f"rounded-lg {color['bg']} {border_class} size-12 flex-shrink-0",
),
# Updated class_name below
rx.el.p(
diff --git a/app/examples/utils.py b/app/examples/utils.py
index 2a0f20e..918a597 100644
--- a/app/examples/utils.py
+++ b/app/examples/utils.py
@@ -33,7 +33,7 @@ def open_in_reflex_build(
tooltip.positioner(
tooltip.popup(
rx.el.p(tooltip_content, class_name="!text-xs"),
- class_name="rounded-radius p-2",
+ class_name="rounded-lg p-2",
),
side="top",
side_offset=8,
@@ -134,7 +134,7 @@ def wrapper(*args, **kwargs):
"break-inside-avoid",
"w-full",
"bg-card",
- "rounded-radius",
+ "rounded-lg",
"border",
"border-input",
"p-card",
diff --git a/app/templates/options.py b/app/templates/options.py
index edabb6c..08989f8 100644
--- a/app/templates/options.py
+++ b/app/templates/options.py
@@ -231,7 +231,7 @@ def menu_panel(desktop: bool = True) -> rx.Component:
class_name="" if desktop else "w-full",
),
),
- name="radius_select",
+ name="menu_panel",
default_value="Medium",
)
@@ -688,7 +688,7 @@ def open_preset_menu() -> rx.Component:
input(
placeholder="ex: b2D0wqNxT",
id="seed-input-el",
- class_name="rounded-radius text-foreground",
+ class_name="rounded-lg text-foreground",
),
rx.el.div(
dialog.close(
diff --git a/app/templates/toc.py b/app/templates/toc.py
index 5eb06be..c98d9c1 100644
--- a/app/templates/toc.py
+++ b/app/templates/toc.py
@@ -45,7 +45,7 @@ def create_copy_button(url: str) -> rx.Component:
tooltip.positioner(
tooltip.popup(
rx.el.p("Copy Page", class_name="!text-xs"),
- class_name="rounded-radius p-2",
+ class_name="rounded-lg p-2",
),
side="bottom",
side_offset=8,
@@ -114,7 +114,7 @@ def external_tool_item(
tooltip.positioner(
tooltip.popup(
rx.el.p(tooltip_content, class_name="!text-xs"),
- class_name="rounded-radius p-2",
+ class_name="rounded-lg p-2",
),
side="bottom",
side_offset=8,
diff --git a/app/templates/utils.py b/app/templates/utils.py
index 0564537..332cefb 100644
--- a/app/templates/utils.py
+++ b/app/templates/utils.py
@@ -12,7 +12,7 @@ def attach_tooltip(func: rx.Component, label: str):
tooltip.popup(
tooltip.arrow(),
label,
- class_name="rounded-radius",
+ class_name="rounded-lg",
),
side="bottom",
),
diff --git a/app/www/library/examples/avatar_basic.py b/app/www/library/examples/avatar_basic.py
new file mode 100644
index 0000000..4a80d04
--- /dev/null
+++ b/app/www/library/examples/avatar_basic.py
@@ -0,0 +1,13 @@
+import reflex as rx
+
+from components.ui.avatar import avatar
+
+
+def avatar_basic() -> rx.Component:
+ return avatar.root(
+ avatar.image(
+ src="https://github.com/LineIndent.png",
+ custom_attrs={"alt": "@lineindent"},
+ ),
+ avatar.fallback("AH"),
+ )
diff --git a/app/www/library/examples/avatar_dropdown_menu.py b/app/www/library/examples/avatar_dropdown_menu.py
new file mode 100644
index 0000000..962fc87
--- /dev/null
+++ b/app/www/library/examples/avatar_dropdown_menu.py
@@ -0,0 +1,36 @@
+import reflex as rx
+
+from components.ui.avatar import avatar
+from components.ui.button import button
+from components.ui.menu import menu
+
+
+def avatar_dropdown_menu() -> rx.Component:
+ return menu.root(
+ menu.trigger(
+ render_=button(
+ avatar.root(
+ avatar.image(
+ src="https://github.com/LineIndent.png",
+ custom_attrs={"alt": "lineindent"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ variant="ghost",
+ size="icon",
+ class_name="rounded-full",
+ )
+ ),
+ menu.portal(
+ menu.positioner(
+ menu.popup(
+ menu.group(
+ menu.item("Profile"),
+ menu.item("Billing"),
+ menu.item("Settings"),
+ ),
+ class_name="w-32",
+ ),
+ )
+ ),
+ )
diff --git a/app/www/library/examples/avatar_general.py b/app/www/library/examples/avatar_general.py
deleted file mode 100644
index 7e755b8..0000000
--- a/app/www/library/examples/avatar_general.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import reflex as rx
-from components.ui.avatar import avatar
-
-
-def avatar_general():
- return rx.box(
- avatar(
- src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@LineIndent",
- fallback="CN",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/198465274?s=200&v=4",
- alt="@buridan-ui",
- fallback="BUI",
- class_name="rounded-lg",
- ),
- rx.box(
- avatar(
- src="",
- alt="@buridan-ui",
- fallback="BU",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@buridan-ui",
- fallback="BUI",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- ),
- class_name=(
- "flex -space-x-2 "
- "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] "
- "*:data-[slot=avatar]:grayscale"
- ),
- ),
- class_name="flex flex-row flex-wrap items-center gap-12 p-8",
- )
-
diff --git a/app/www/library/examples/avatar_group.py b/app/www/library/examples/avatar_group.py
new file mode 100644
index 0000000..dc5c5e5
--- /dev/null
+++ b/app/www/library/examples/avatar_group.py
@@ -0,0 +1,26 @@
+import reflex as rx
+
+from components.ui.avatar import avatar
+
+
+def avatar_as_group() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.image(
+ src="/avatars/01.png",
+ custom_attrs={"alt": "@avatar-1"},
+ ),
+ avatar.fallback("RD"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/02.png",
+ custom_attrs={"alt": "@avatar-2"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ avatar.root(
+ avatar.fallback("AH"),
+ ),
+ class_name="grayscale",
+ )
diff --git a/app/www/library/examples/avatar_group_count.py b/app/www/library/examples/avatar_group_count.py
new file mode 100644
index 0000000..bea4b94
--- /dev/null
+++ b/app/www/library/examples/avatar_group_count.py
@@ -0,0 +1,27 @@
+import reflex as rx
+
+from components.ui.avatar import avatar
+
+
+def avatar_with_group_count() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.fallback("AH"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/01.png",
+ custom_attrs={"alt": "@avatar-1"},
+ ),
+ avatar.fallback("RD"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/02.png",
+ custom_attrs={"alt": "@avatar-2"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ avatar.group_count("+3"),
+ class_name="grayscale",
+ )
diff --git a/app/www/library/examples/avatar_sizes.py b/app/www/library/examples/avatar_sizes.py
index c71066b..daa9d8c 100644
--- a/app/www/library/examples/avatar_sizes.py
+++ b/app/www/library/examples/avatar_sizes.py
@@ -1,40 +1,32 @@
import reflex as rx
+
from components.ui.avatar import avatar
-def avatar_sizes():
- """Example showing different avatar sizes"""
- return rx.box(
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-6",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-8",
+def avatar_sizes() -> rx.Component:
+ return rx.el.div(
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
+ ),
+ avatar.fallback("LI"),
+ size="sm",
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-10",
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
+ ),
+ avatar.fallback("LI"),
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-12",
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
+ ),
+ avatar.fallback("LI"),
+ size="lg",
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-16",
- ),
- class_name="flex items-center gap-4 p-8",
+ class_name="flex flex-wrap items-center gap-2",
)
-
diff --git a/app/www/library/examples/avatar_with_badge.py b/app/www/library/examples/avatar_with_badge.py
index 6ad60b5..761f094 100644
--- a/app/www/library/examples/avatar_with_badge.py
+++ b/app/www/library/examples/avatar_with_badge.py
@@ -1,24 +1,16 @@
import reflex as rx
+
from components.ui.avatar import avatar
-def avatar_with_badge():
- """Example showing avatar with status badge"""
- return rx.box(
- rx.box(
- avatar(
- src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@LineIndent",
- fallback="CN",
- class_name="size-12",
- ),
- rx.box(
- class_name=(
- "absolute bottom-0 right-0 size-3 rounded-full "
- "bg-green-500 border-2 border-[var(--background)]"
- ),
- ),
- class_name="relative inline-block",
+def avatar_with_badge() -> rx.Component:
+ return avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
+ ),
+ avatar.fallback("AH"),
+ avatar.badge(
+ class_name="bg-green-600 dark:bg-green-800",
),
- class_name="p-8",
)
diff --git a/app/www/library/examples/avatar_with_icon_badge.py b/app/www/library/examples/avatar_with_icon_badge.py
new file mode 100644
index 0000000..fa1d34a
--- /dev/null
+++ b/app/www/library/examples/avatar_with_icon_badge.py
@@ -0,0 +1,17 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.avatar import avatar
+
+
+def avatar_badge_icon() -> rx.Component:
+ return avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
+ custom_attrs={"alt": "Reflex Dev"},
+ ),
+ avatar.fallback("RD"),
+ avatar.badge(
+ hi("PlusSignIcon"),
+ ),
+ )
diff --git a/app/www/library/examples/avatar_with_icon_count.py b/app/www/library/examples/avatar_with_icon_count.py
new file mode 100644
index 0000000..493426a
--- /dev/null
+++ b/app/www/library/examples/avatar_with_icon_count.py
@@ -0,0 +1,30 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.avatar import avatar
+
+
+def avatar_group_count_icon() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.fallback("AH"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
+ custom_attrs={"alt": "@reflex-dev"},
+ ),
+ avatar.fallback("CN"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "LineIndent"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ avatar.group_count(
+ hi("PlusSignIcon"),
+ ),
+ class_name="grayscale",
+ )
diff --git a/app/www/library/examples/badge_as_link.py b/app/www/library/examples/badge_as_link.py
new file mode 100644
index 0000000..abf078a
--- /dev/null
+++ b/app/www/library/examples/badge_as_link.py
@@ -0,0 +1,15 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.badge import badge
+
+
+def badge_as_link() -> rx.Component:
+ return badge(
+ rx.el.a(
+ "Open Link",
+ hi("ArrowUpRightIcon", custom_attrs={"data-icon": "inline-end"}),
+ href="#link",
+ class_name="inline-flex items-center gap-1 text-inherit no-underline",
+ ),
+ )
diff --git a/app/www/library/examples/badge_custom_colors.py b/app/www/library/examples/badge_custom_colors.py
new file mode 100644
index 0000000..7b7292d
--- /dev/null
+++ b/app/www/library/examples/badge_custom_colors.py
@@ -0,0 +1,29 @@
+import reflex as rx
+
+from components.ui.badge import badge
+
+
+def badge_custom_colors() -> rx.Component:
+ return rx.el.div(
+ badge(
+ "Blue",
+ class_name="bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300",
+ ),
+ badge(
+ "Green",
+ class_name="bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300",
+ ),
+ badge(
+ "Sky",
+ class_name="bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300",
+ ),
+ badge(
+ "Purple",
+ class_name="bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300",
+ ),
+ badge(
+ "Red",
+ class_name="bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300",
+ ),
+ class_name="flex flex-wrap gap-2",
+ )
diff --git a/app/www/library/examples/badge_default.py b/app/www/library/examples/badge_default.py
deleted file mode 100644
index c09afbf..0000000
--- a/app/www/library/examples/badge_default.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import reflex as rx
-from components.ui.badge import badge
-
-
-def badge_default():
- return rx.box(
- rx.box(
- badge("Badge"),
- badge("Secondary", variant="secondary"),
- badge("Destructive", variant="destructive"),
- badge("Outline", variant="outline"),
- class_name="flex w-full flex-wrap gap-2",
- ),
- rx.box(
- badge(
- rx.icon(tag="badge-check"),
- "Verified",
- variant="secondary",
- class_name="bg-blue-500 text-white dark:bg-blue-600",
- ),
- badge(
- "8",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "99",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "20+",
- variant="outline",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- class_name="flex w-full flex-wrap gap-2",
- ),
- class_name="flex flex-col items-center gap-2 p-8",
- )
-
diff --git a/app/www/library/examples/badge_notification_count.py b/app/www/library/examples/badge_notification_count.py
deleted file mode 100644
index 48da4d3..0000000
--- a/app/www/library/examples/badge_notification_count.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import reflex as rx
-from components.ui.badge import badge
-
-
-def badge_notification_count():
- return rx.box(
- badge(
- "1",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "5",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "10",
- variant="secondary",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "99+",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- class_name="flex items-center gap-2 p-8",
- )
diff --git a/app/www/library/examples/badge_status.py b/app/www/library/examples/badge_status.py
deleted file mode 100644
index 8848821..0000000
--- a/app/www/library/examples/badge_status.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import reflex as rx
-from components.ui.badge import badge
-
-
-def badge_status():
- return rx.box(
- badge("New", variant="default"),
- badge("Popular", variant="secondary"),
- badge("Sale", variant="destructive"),
- badge("Draft", variant="outline"),
- badge(
- rx.icon(tag="star"),
- "Featured",
- class_name="bg-yellow-500 text-white dark:bg-yellow-600",
- ),
- class_name="flex flex-wrap gap-2 p-8",
- )
-
diff --git a/app/www/library/examples/badge_variants.py b/app/www/library/examples/badge_variants.py
new file mode 100644
index 0000000..5435217
--- /dev/null
+++ b/app/www/library/examples/badge_variants.py
@@ -0,0 +1,14 @@
+import reflex as rx
+
+from components.ui.badge import badge
+
+
+def badge_with_variants() -> rx.Component:
+ return rx.el.div(
+ badge("Default"),
+ badge("Secondary", variant="secondary"),
+ badge("Destructive", variant="destructive"),
+ badge("Outline", variant="outline"),
+ badge("Ghost", variant="ghost"),
+ class_name="flex flex-wrap gap-2",
+ )
diff --git a/app/www/library/examples/badge_with_icon.py b/app/www/library/examples/badge_with_icon.py
new file mode 100644
index 0000000..9f29fa5
--- /dev/null
+++ b/app/www/library/examples/badge_with_icon.py
@@ -0,0 +1,20 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.badge import badge
+
+
+def badge_with_icon() -> rx.Component:
+ return rx.el.div(
+ badge(
+ hi("CheckmarkBadge01Icon", custom_attrs={"data-icon": "inline-start"}),
+ "Verified",
+ variant="secondary",
+ ),
+ badge(
+ "Bookmark",
+ hi("Bookmark02Icon", custom_attrs={"data-icon": "inline-end"}),
+ variant="outline",
+ ),
+ class_name="flex flex-wrap gap-2",
+ )
diff --git a/app/www/library/examples/badge_with_icons.py b/app/www/library/examples/badge_with_icons.py
deleted file mode 100644
index 41a1cc1..0000000
--- a/app/www/library/examples/badge_with_icons.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import reflex as rx
-from components.ui.badge import badge
-
-
-def badge_with_icons():
- return rx.box(
- badge(
- rx.icon(tag="check"),
- "Success",
- variant="secondary",
- class_name="bg-green-500 text-white dark:bg-green-600",
- ),
- badge(
- rx.icon(tag="x"),
- "Error",
- variant="destructive",
- ),
- badge(
- rx.icon(tag="triangle-alert"),
- "Warning",
- variant="secondary",
- class_name="bg-yellow-500 text-white dark:bg-yellow-600",
- ),
- badge(
- rx.icon(tag="info"),
- "Info",
- variant="secondary",
- class_name="bg-blue-500 text-white dark:bg-blue-600",
- ),
- class_name="flex flex-wrap gap-2 p-8",
- )
-
diff --git a/app/www/library/examples/badge_wtih_spinner.py b/app/www/library/examples/badge_wtih_spinner.py
new file mode 100644
index 0000000..1b657ca
--- /dev/null
+++ b/app/www/library/examples/badge_wtih_spinner.py
@@ -0,0 +1,20 @@
+import reflex as rx
+
+from components.ui.badge import badge
+from components.ui.spinner import spinner
+
+
+def badge_with_spinner() -> rx.Component:
+ return rx.el.div(
+ badge(
+ spinner(custom_attrs={"data-icon": "inline-start"}),
+ "Deleting",
+ variant="destructive",
+ ),
+ badge(
+ "Generating",
+ spinner(custom_attrs={"data-icon": "inline-end"}),
+ variant="secondary",
+ ),
+ class_name="flex flex-wrap gap-2",
+ )
diff --git a/app/www/library/examples/breadcrumb_basic.py b/app/www/library/examples/breadcrumb_basic.py
new file mode 100644
index 0000000..e5a4752
--- /dev/null
+++ b/app/www/library/examples/breadcrumb_basic.py
@@ -0,0 +1,21 @@
+import reflex as rx
+
+from components.ui.breadcrumb import breadcrumb
+
+
+def breadcrumb_basic() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
+ )
diff --git a/app/www/library/examples/breadcrumb_basic_demo.py b/app/www/library/examples/breadcrumb_basic_demo.py
deleted file mode 100644
index b05bca7..0000000
--- a/app/www/library/examples/breadcrumb_basic_demo.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import reflex as rx
-from components.ui.breadcrumb import (
- breadcrumb,
- breadcrumb_ellipsis,
- breadcrumb_item,
- breadcrumb_link,
- breadcrumb_list,
- breadcrumb_page,
- breadcrumb_separator,
-)
-
-
-def breadcrumb_basic_demo():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- rx.menu.root(
- rx.menu.trigger(
- rx.box(
- breadcrumb_ellipsis(class_name="size-4"),
- rx.el.span("Toggle menu", class_name="sr-only"),
- class_name="flex items-center gap-1",
- ),
- ),
- rx.menu.content(
- rx.menu.item("Documentation"),
- rx.menu.item("Themes"),
- rx.menu.item("GitHub"),
- class_name="min-w-[8rem]",
- ),
- ),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Components", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page("Breadcrumb"),
- ),
- ),
- ),
- class_name="p-8",
- )
-
diff --git a/app/www/library/examples/breadcrumb_custom_separator.py b/app/www/library/examples/breadcrumb_custom_separator.py
index f00e90d..10db40b 100644
--- a/app/www/library/examples/breadcrumb_custom_separator.py
+++ b/app/www/library/examples/breadcrumb_custom_separator.py
@@ -1,34 +1,22 @@
import reflex as rx
-from components.ui.breadcrumb import (
- breadcrumb,
- breadcrumb_item,
- breadcrumb_link,
- breadcrumb_list,
- breadcrumb_page,
- breadcrumb_separator,
-)
+from components.icons.hugeicon import hi
+from components.ui.breadcrumb import breadcrumb
-def breadcrumb_custom_separator():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(
- rx.text("/", class_name="text-[var(--muted-foreground)]")
- ),
- breadcrumb_item(
- breadcrumb_link("Blog", href="#"),
- ),
- breadcrumb_separator(
- rx.text("/", class_name="text-[var(--muted-foreground)]")
- ),
- breadcrumb_item(
- breadcrumb_page("Article"),
- ),
+
+def breadcrumb_custom_separator() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
),
- ),
- class_name="p-8",
+ )
)
diff --git a/app/www/library/examples/breadcrumb_drop_down.py b/app/www/library/examples/breadcrumb_drop_down.py
new file mode 100644
index 0000000..ea0e101
--- /dev/null
+++ b/app/www/library/examples/breadcrumb_drop_down.py
@@ -0,0 +1,48 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.breadcrumb import breadcrumb
+from components.ui.button import button
+from components.ui.menu import menu
+
+
+def breadcrumb_dropdown_demo() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ menu.root(
+ menu.trigger(
+ render_=rx.el.button(
+ "Components",
+ hi(
+ "ArrowDown01Icon",
+ custom_attrs={"data-icon": "inline-end"},
+ class_name="size-4",
+ ),
+ class_name="flex flex-row items-center gap-x-2",
+ )
+ ),
+ menu.portal(
+ menu.positioner(
+ menu.popup(
+ menu.group(
+ menu.item("Documentation"),
+ menu.item("Themes"),
+ menu.item("GitHub"),
+ ),
+ align="start",
+ )
+ ),
+ ),
+ ),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
+ )
diff --git a/app/www/library/examples/breadcrumb_ellipsis.py b/app/www/library/examples/breadcrumb_ellipsis.py
new file mode 100644
index 0000000..46c29e1
--- /dev/null
+++ b/app/www/library/examples/breadcrumb_ellipsis.py
@@ -0,0 +1,25 @@
+import reflex as rx
+
+from components.ui.breadcrumb import breadcrumb
+
+
+def breadcrumb_ellipsis() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.ellipsis(),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
+ )
diff --git a/app/www/library/examples/breadcrumb_icon_breadcrumb.py b/app/www/library/examples/breadcrumb_icon_breadcrumb.py
deleted file mode 100644
index ea36234..0000000
--- a/app/www/library/examples/breadcrumb_icon_breadcrumb.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import reflex as rx
-from components.ui.breadcrumb import (
- breadcrumb,
- breadcrumb_item,
- breadcrumb_link,
- breadcrumb_list,
- breadcrumb_page,
- breadcrumb_separator,
-)
-
-
-def breadcrumb_icon_breadcrumb():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link(
- rx.icon(tag="home", size=14),
- "Home",
- href="#",
- class_name="flex flex-row gap-x-1 items-center",
- ),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link(
- rx.icon(tag="folder", size=14),
- "Documents",
- href="#",
- class_name="flex flex-row gap-x-1 items-center",
- ),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page(
- rx.icon(tag="file-text", size=14),
- "README.md",
- class_name="flex flex-row gap-x-1 items-center",
- ),
- ),
- ),
- ),
- class_name="p-8",
- )
-
diff --git a/app/www/library/examples/breadcrumb_simple_breadcrumb.py b/app/www/library/examples/breadcrumb_simple_breadcrumb.py
deleted file mode 100644
index f1e0354..0000000
--- a/app/www/library/examples/breadcrumb_simple_breadcrumb.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import reflex as rx
-from components.ui.breadcrumb import (
- breadcrumb,
- breadcrumb_item,
- breadcrumb_link,
- breadcrumb_list,
- breadcrumb_page,
- breadcrumb_separator,
-)
-
-
-def breadcrumb_simple_breadcrumb():
- return rx.box(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Products", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Electronics", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page("Laptop"),
- ),
- ),
- ),
- class_name="p-8",
- )
-
diff --git a/app/www/library/examples/button_as_link.py b/app/www/library/examples/button_as_link.py
new file mode 100644
index 0000000..57a2611
--- /dev/null
+++ b/app/www/library/examples/button_as_link.py
@@ -0,0 +1,11 @@
+import reflex as rx
+
+from components.ui.button import button_variants
+
+
+def button_render() -> rx.Component:
+ return rx.el.a(
+ "Login",
+ href="#",
+ class_name=button_variants(variant="secondary", size="sm"),
+ )
diff --git a/app/www/library/examples/button_default.py b/app/www/library/examples/button_default.py
index a701f28..51b8015 100644
--- a/app/www/library/examples/button_default.py
+++ b/app/www/library/examples/button_default.py
@@ -2,4 +2,4 @@
def button_default():
- return button("Default", variant="default", size="sm")
+ return button("Button")
diff --git a/app/www/library/examples/button_icon.py b/app/www/library/examples/button_icon.py
index a2d9312..9f76f4b 100644
--- a/app/www/library/examples/button_icon.py
+++ b/app/www/library/examples/button_icon.py
@@ -4,5 +4,7 @@
def button_icon():
return button(
- hi("Mail01Icon", class_name="size-4"), variant="outline", size="icon-sm"
+ hi("CircleArrowUp01Icon", class_name="size-4"),
+ variant="outline",
+ size="icon",
)
diff --git a/app/www/library/examples/button_loading.py b/app/www/library/examples/button_loading.py
new file mode 100644
index 0000000..9ad8a43
--- /dev/null
+++ b/app/www/library/examples/button_loading.py
@@ -0,0 +1,21 @@
+import reflex as rx
+
+from components.ui.button import button
+from components.ui.spinner import spinner
+
+
+def button_loading() -> rx.Component:
+ return rx.el.div(
+ button(
+ spinner(custom_attrs={"data-icon": "inline-start"}),
+ "Generating",
+ variant="outline",
+ ),
+ button(
+ "Downloading",
+ spinner(custom_attrs={"data-icon": "inline-end"}),
+ variant="secondary",
+ disabled=True,
+ ),
+ class_name="flex gap-2",
+ )
diff --git a/app/www/library/examples/button_rounded.py b/app/www/library/examples/button_rounded.py
new file mode 100644
index 0000000..1777593
--- /dev/null
+++ b/app/www/library/examples/button_rounded.py
@@ -0,0 +1,20 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.button import button
+
+
+def button_rounded() -> rx.Component:
+ return rx.el.div(
+ button(
+ "Get Started",
+ class_name="rounded-full",
+ ),
+ button(
+ hi("ArrowUp02Icon"),
+ variant="outline",
+ size="icon",
+ class_name="rounded-full",
+ ),
+ class_name="flex gap-2",
+ )
diff --git a/app/www/library/examples/button_secondary.py b/app/www/library/examples/button_secondary.py
index 6b54bce..9b095f2 100644
--- a/app/www/library/examples/button_secondary.py
+++ b/app/www/library/examples/button_secondary.py
@@ -2,4 +2,4 @@
def button_secondary():
- return button("Secondary", variant="secondary", size="sm")
+ return button("Secondary", variant="secondary")
diff --git a/app/www/library/examples/button_sizes.py b/app/www/library/examples/button_sizes.py
index 3caa341..ebe92a1 100644
--- a/app/www/library/examples/button_sizes.py
+++ b/app/www/library/examples/button_sizes.py
@@ -1,12 +1,50 @@
import reflex as rx
+
+from components.icons.hugeicon import hi
from components.ui.button import button
-def button_sizes():
+def button_size() -> rx.Component:
return rx.el.div(
- button("Small", size="sm"),
- button("Default", size="default"),
- button("Large", size="lg"),
- class_name="flex items-center gap-3",
+ rx.el.div(
+ button("Extra Small", size="xs", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-xs",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Small", size="sm", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-sm",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Default", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Large", variant="outline", size="lg"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-lg",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ class_name="flex flex-col items-start gap-8 sm:flex-row",
)
-
diff --git a/app/www/library/examples/button_with_icon.py b/app/www/library/examples/button_with_icon.py
new file mode 100644
index 0000000..c282c66
--- /dev/null
+++ b/app/www/library/examples/button_with_icon.py
@@ -0,0 +1,20 @@
+import reflex as rx
+
+from components.icons.hugeicon import hi
+from components.ui.button import button
+
+
+def button_with_icon() -> rx.Component:
+ return rx.el.div(
+ button(
+ hi("GitBranchIcon", custom_attrs={"data-icon": "inline-start"}),
+ "New Branch",
+ variant="outline",
+ ),
+ button(
+ "Fork",
+ hi("GitForkIcon", custom_attrs={"data-icon": "inline-end"}),
+ variant="outline",
+ ),
+ class_name="flex gap-2",
+ )
diff --git a/app/www/library/examples/scroll_area_horizontal.py b/app/www/library/examples/scroll_area_horizontal.py
index 0dcc5de..55b70aa 100644
--- a/app/www/library/examples/scroll_area_horizontal.py
+++ b/app/www/library/examples/scroll_area_horizontal.py
@@ -49,5 +49,5 @@ def scroll_area_horizontal():
),
scroll_area.scrollbar(scroll_area.thumb(), orientation="horizontal"),
scroll_area.corner(),
- class_name="w-96 rounded-radius border border-input",
+ class_name="w-96 rounded-lg border border-input",
)
diff --git a/app/www/library/examples/skeleton_card.py b/app/www/library/examples/skeleton_card.py
index 40e6739..8af7be2 100644
--- a/app/www/library/examples/skeleton_card.py
+++ b/app/www/library/examples/skeleton_card.py
@@ -11,5 +11,5 @@ def skeleton_card():
card.content(
skeleton_component(class_name="aspect-video w-full rounded-md"),
),
- class_name="w-full max-w-xs border border-input rounded-radius",
+ class_name="w-full max-w-xs border border-input rounded-lg",
)
diff --git a/app/www/wrapper.py b/app/www/wrapper.py
index 6e94afe..1a791fc 100644
--- a/app/www/wrapper.py
+++ b/app/www/wrapper.py
@@ -94,7 +94,7 @@ def file_codeblock(file_path: str, source: str) -> rx.Component:
id=f"code-panel-{toggle_height_id}",
class_name="scrollbar-none flex-1 min-h-0 flex flex-col h-full relative overflow-x-auto",
),
- class_name="rounded-radius outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card",
+ class_name="rounded-lg outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card",
)
@@ -166,7 +166,7 @@ def file_codeblock_full(file_path: str, source: str) -> rx.Component:
},
class_name="scrollbar-none flex-1 min-h-0 flex flex-col h-full",
),
- class_name="rounded-radius outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card",
+ class_name="rounded-lg outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card",
)
@@ -194,7 +194,7 @@ def chart_util_wrapper(source: str):
),
class_name="bg-secondary dark:bg-card relative overflow-hidden",
),
- class_name="w-full border border-input rounded-radius mb-8 !overflow-hidden",
+ class_name="w-full border border-input rounded-lg mb-8 !overflow-hidden",
),
)
diff --git a/assets/docs/components/accordion.md b/assets/docs/components/accordion.md
index deb99e5..f37f8a9 100644
--- a/assets/docs/components/accordion.md
+++ b/assets/docs/components/accordion.md
@@ -17,8 +17,6 @@ buridan add component accordion
### Manual Installation
```python
-"""Custom Accordion component."""
-
from typing import Any, Literal
from reflex.components.component import Component, ComponentNamespace
@@ -51,8 +49,6 @@ class ClassNames:
class AccordionBaseComponent(BaseUIComponent):
- """Base component for accordion components."""
-
library = f"{PACKAGE_NAME}/accordion"
@property
@@ -66,34 +62,24 @@ class AccordionRoot(AccordionBaseComponent):
tag = "Accordion.Root"
- # The uncontrolled value of the item(s) that should be initially expanded. To render a controlled accordion, use the `value` prop instead.
default_value: Var[list[Any]]
- # The controlled value of the item(s) that should be expanded. To render an uncontrolled accordion, use the `default_value` prop instead.
value: Var[list[Any]]
- # Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.
on_value_change: EventHandler[passthrough_event_spec(list[str])]
- # Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False.
hidden_until_found: Var[bool]
- # Whether multiple items can be open at the same time. Defaults to True.
multiple: Var[bool]
- # Whether the component should ignore user interaction. Defaults to False.
disabled: Var[bool]
- # Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Defaults to True.
loop_focus: Var[bool]
- # The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. Defaults to 'vertical'.
orientation: Var[LiteralOrientation]
- # Whether to keep the element in the DOM while the panel is closed. This prop is ignored when hidden_until_found is used. Defaults to False.
keep_mounted: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -105,20 +91,14 @@ class AccordionRoot(AccordionBaseComponent):
class AccordionItem(AccordionBaseComponent):
- """Groups an accordion header with the corresponding panel."""
-
tag = "Accordion.Item"
- # The value that identifies this item.
value: Var[str]
- # Event handler called when the panel is opened or closed.
on_open_change: EventHandler[passthrough_event_spec(bool)]
- # Whether the component should ignore user interaction. Defaults to False.
disabled: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -130,11 +110,8 @@ class AccordionItem(AccordionBaseComponent):
class AccordionHeader(AccordionBaseComponent):
- """A heading that labels the corresponding panel."""
-
tag = "Accordion.Header"
- # The render prop.
render_: Var[Component]
@classmethod
@@ -146,13 +123,13 @@ class AccordionHeader(AccordionBaseComponent):
class AccordionTrigger(AccordionBaseComponent):
- """A button that opens and closes the corresponding panel."""
-
tag = "Accordion.Trigger"
title: Var[str]
+
native_button: Var[bool]
- render_: Var[Component] = None
+
+ render_: Var[Component] | None = None
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
@@ -191,17 +168,12 @@ class AccordionTrigger(AccordionBaseComponent):
class AccordionPanel(AccordionBaseComponent):
- """A collapsible panel with the accordion item contents."""
-
tag = "Accordion.Panel"
- # Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False.
hidden_until_found: Var[bool]
- # Whether to keep the element in the DOM while the panel is closed. This prop is ignored when `hidden_until_found` is used. Defaults to False.
keep_mounted: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -212,132 +184,13 @@ class AccordionPanel(AccordionBaseComponent):
return super().create(*children, **props)
-class HighLevelAccordion(AccordionRoot):
- """High level wrapper for the Accordion component."""
-
- items: Var[ITEMS_TYPE] | ITEMS_TYPE
-
- _item_props = {"on_open_change", "disabled"}
- _trigger_props = {"native_button"}
- _panel_props = {"hidden_until_found", "keep_mounted"}
-
- @classmethod
- def create(
- cls,
- items: Var[ITEMS_TYPE] | ITEMS_TYPE,
- **props,
- ) -> BaseUIComponent:
- """Create a high level accordion component.
-
- Args:
- items: List of dictionaries with 'trigger', 'content', and optional 'value' and 'disabled' keys.
- **props: Additional properties to apply to the accordion component.
-
- Returns:
- The accordion component with all necessary subcomponents.
- """
- # Extract props for different parts
- item_props = {k: props.pop(k) for k in cls._item_props & props.keys()}
- trigger_props = {k: props.pop(k) for k in cls._trigger_props & props.keys()}
- panel_props = {k: props.pop(k) for k in cls._panel_props & props.keys()}
-
- if isinstance(items, Var):
- accordion_items = foreach(
- items,
- lambda item: cls._create_accordion_item_dynamic(
- item, item_props, trigger_props, panel_props
- ),
- )
- return AccordionRoot.create(accordion_items, **props)
- accordion_items = [
- cls._create_accordion_item(
- item, index, item_props, trigger_props, panel_props
- )
- for index, item in enumerate(items)
- ]
- return AccordionRoot.create(*accordion_items, **props)
-
- @classmethod
- def _create_trigger_icon(cls) -> Component:
- """Create the accordion trigger icon."""
- return icon(
- "PlusSignIcon",
- class_name=ClassNames.TRIGGER_ICON,
- data_slot="accordion-trigger-icon",
- )
-
- @classmethod
- def _create_accordion_item(
- cls,
- item: dict[str, str | Component],
- index: int,
- item_props: dict,
- trigger_props: dict,
- panel_props: dict,
- ) -> BaseUIComponent:
- """Create a single accordion item from a dictionary (for normal lists)."""
- return AccordionItem.create(
- AccordionHeader.create(
- AccordionTrigger.create(
- item.get("trigger"),
- cls._create_trigger_icon(),
- **trigger_props,
- ),
- ),
- AccordionPanel.create(
- Div.create(
- item.get("content"),
- class_name=ClassNames.PANEL_DIV,
- data_slot="accordion-panel-div",
- ),
- **panel_props,
- ),
- value=item.get("value", f"item-{index + 1}"),
- disabled=item.get("disabled", False),
- **item_props,
- )
-
- @classmethod
- def _create_accordion_item_dynamic(
- cls,
- item: ObjectVar[dict[str, str | Component]],
- item_props: dict,
- trigger_props: dict,
- panel_props: dict,
- ) -> BaseUIComponent:
- """Create a single accordion item from a dictionary (for Var items)."""
- return AccordionItem.create(
- AccordionHeader.create(
- AccordionTrigger.create(
- item["trigger"],
- cls._create_trigger_icon(),
- **trigger_props,
- ),
- ),
- AccordionPanel.create(
- Div.create(
- item["content"],
- class_name=ClassNames.PANEL_DIV,
- data_slot="accordion-panel-div",
- ),
- **panel_props,
- ),
- value=item.get("value", ""),
- disabled=item.get("disabled", False).bool(),
- **item_props,
- )
-
-
class Accordion(ComponentNamespace):
- """Namespace for Accordion components."""
-
root = staticmethod(AccordionRoot.create)
item = staticmethod(AccordionItem.create)
header = staticmethod(AccordionHeader.create)
trigger = staticmethod(AccordionTrigger.create)
panel = staticmethod(AccordionPanel.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelAccordion.create)
accordion = Accordion()
@@ -348,7 +201,7 @@ accordion = Accordion()
```python
-from components.ui.accordion import accordion
+from components.ui.accordion import Accordion
```
diff --git a/assets/docs/components/attachment.md b/assets/docs/components/attachment.md
index d826589..1353fb7 100644
--- a/assets/docs/components/attachment.md
+++ b/assets/docs/components/attachment.md
@@ -17,8 +17,6 @@ buridan add component attachment
### Manual Installation
```python
-"""Attachment component — file and image attachment previews for chat UI."""
-
from typing import Literal
import reflex as rx
@@ -43,7 +41,7 @@ class ClassNames:
"data-[state=idle]:border-dashed"
)
- SIZES: dict[str, str] = {
+ SIZES = {
"default": (
"gap-2 text-sm "
"has-data-[slot=attachment-content]:px-2.5 "
@@ -64,7 +62,7 @@ class ClassNames:
),
}
- ORIENTATIONS: dict[str, str] = {
+ ORIENTATIONS = {
"horizontal": "min-w-40 items-center",
"vertical": "w-24 flex-col has-data-[slot=attachment-content]:w-30",
}
@@ -84,7 +82,7 @@ class ClassNames:
"group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5"
)
- MEDIA_VARIANTS: dict[str, str] = {
+ MEDIA_VARIANTS = {
"icon": "",
"image": (
"opacity-60 "
@@ -136,13 +134,7 @@ def attachment_root(
class_name: str = "",
**props,
) -> rx.Component:
- """
- Root attachment container.
- orientation: horizontal (default) | vertical
- size: default | sm | xs
- state: idle | uploading | processing | error | done (default)
- """
return rx.el.div(
*children,
data_slot="attachment",
@@ -165,7 +157,7 @@ def attachment_media(
class_name: str = "",
**props,
) -> rx.Component:
- """Icon or image preview slot."""
+
return rx.el.div(
*children,
data_slot="attachment-media",
@@ -180,7 +172,7 @@ def attachment_media(
def attachment_content(*children, class_name: str = "", **props) -> rx.Component:
- """Text content area — holds title and description."""
+
return rx.el.div(
*children,
data_slot="attachment-content",
@@ -190,7 +182,7 @@ def attachment_content(*children, class_name: str = "", **props) -> rx.Component
def attachment_title(*children, class_name: str = "", **props) -> rx.Component:
- """Filename or attachment title. Shimmers during uploading/processing."""
+
return rx.el.span(
*children,
data_slot="attachment-title",
@@ -200,7 +192,7 @@ def attachment_title(*children, class_name: str = "", **props) -> rx.Component:
def attachment_description(*children, class_name: str = "", **props) -> rx.Component:
- """File type, size, or other metadata."""
+
return rx.el.span(
*children,
data_slot="attachment-description",
@@ -210,10 +202,7 @@ def attachment_description(*children, class_name: str = "", **props) -> rx.Compo
def attachment_actions(*children, class_name: str = "", **props) -> rx.Component:
- """
- Row of action buttons.
- In vertical orientation, absolutely positioned top-right.
- """
+
return rx.el.div(
*children,
data_slot="attachment-actions",
@@ -223,9 +212,10 @@ def attachment_actions(*children, class_name: str = "", **props) -> rx.Component
def attachment_action(*children, class_name: str = "", **props) -> rx.Component:
- """Individual action button."""
props.setdefault("variant", "ghost")
+
props.setdefault("size", "icon-xs")
+
return button(
*children,
data_slot="attachment-action",
@@ -250,10 +240,7 @@ def attachment_trigger(
def attachment_group(*children, class_name: str = "", **props) -> rx.Component:
- """
- Horizontal scrolling row of attachments.
- Snaps to each attachment on scroll.
- """
+
return rx.el.div(
*children,
data_slot="attachment-group",
@@ -263,8 +250,6 @@ def attachment_group(*children, class_name: str = "", **props) -> rx.Component:
class Attachment(ComponentNamespace):
- """Attachment namespace."""
-
root = staticmethod(attachment_root)
media = staticmethod(attachment_media)
content = staticmethod(attachment_content)
diff --git a/assets/docs/components/autocomplete.md b/assets/docs/components/autocomplete.md
index 4503aa2..09e01ee 100644
--- a/assets/docs/components/autocomplete.md
+++ b/assets/docs/components/autocomplete.md
@@ -19,8 +19,6 @@ buridan add component autocomplete
### Manual Installation
```python
-"""Custom autocomplete component with flat and grouped item support."""
-
from typing import Any
from reflex.components.component import Component
@@ -417,23 +415,35 @@ function BuridanAutocompleteRoot({
class BuridanAutocomplete(Component):
tag = "BuridanAutocompleteRoot"
+
is_default = False
items: Var[Any]
+
placeholder: Var[str]
+
root_class: Var[str]
+
input_class: Var[str]
+
popup_class: Var[str]
+
list_class: Var[str]
+
item_class: Var[str]
+
item_highlighted_class: Var[str]
+
empty_class: Var[str]
+
group_label_class: Var[str]
+
separator_class: Var[str]
set_query_on_select: Var[bool] = True
on_change_query: EventHandler[passthrough_event_spec(str)]
+
on_select_item: EventHandler[passthrough_event_spec(str)]
def add_custom_code(self) -> list[str]:
diff --git a/assets/docs/components/avatar.md b/assets/docs/components/avatar.md
index 2af408a..847acbe 100644
--- a/assets/docs/components/avatar.md
+++ b/assets/docs/components/avatar.md
@@ -17,129 +17,128 @@ buridan add component avatar
### Manual Installation
```python
-"""Custom avatar component."""
-
from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventHandler, passthrough_event_spec
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var
+from reflex_components_core.el import Div, Span
+from ..utils.twmerge import cn
from .base_ui import PACKAGE_NAME, BaseUIComponent
+from .component import CoreComponent
class ClassNames:
- """Class names for avatar components."""
-
- ROOT = "shrink-0 inline-flex size-8 items-center justify-center overflow-hidden rounded-full align-middle text-base font-medium select-none"
- IMAGE = "size-full object-cover shrink-0"
- FALLBACK = "flex size-full items-center justify-center text-sm bg-muted"
+ ROOT = "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=xl]:size-14 data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten"
+ IMAGE = "aspect-square size-full rounded-full object-cover"
+ FALLBACK = "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs"
+ BADGE = "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2"
+ GROUP = "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background"
+ GROUP_COUNT = "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3"
class AvatarBaseComponent(BaseUIComponent):
- """Base component for avatar components."""
-
library = f"{PACKAGE_NAME}/avatar"
@property
def import_var(self):
- """Return the import variable for the avatar component."""
return ImportVar(tag="Avatar", package_path="", install=False)
class AvatarRoot(AvatarBaseComponent):
- """Displays a user's profile picture, initials, or fallback icon."""
-
tag = "Avatar.Root"
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar root component."""
+ custom_classes = props.pop("class_name", "")
+ size = props.pop("size", "default")
props["data-slot"] = "avatar"
- cls.set_class_name(ClassNames.ROOT, props)
- return super().create(*children, **props)
+ props["data-size"] = size
+
+ return super().create(
+ *children, class_name=cn(ClassNames.ROOT, custom_classes), **props
+ )
class AvatarImage(AvatarBaseComponent):
- """The image to be displayed in the avatar."""
-
tag = "Avatar.Image"
-
- # The image source URL
src: Var[str]
-
- # Callback when loading status changes
on_loading_status_change: EventHandler[passthrough_event_spec(str)]
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar image component."""
+ custom_classes = props.pop("class_name", "")
props["data-slot"] = "avatar-image"
- cls.set_class_name(ClassNames.IMAGE, props)
- return super().create(*children, **props)
+
+ return super().create(
+ *children, class_name=cn(ClassNames.IMAGE, custom_classes), **props
+ )
class AvatarFallback(AvatarBaseComponent):
- """Rendered when the image fails to load or when no image is provided."""
-
tag = "Avatar.Fallback"
-
- # How long to wait before showing the fallback. Specified in milliseconds
delay: Var[int]
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar fallback component."""
+ custom_classes = props.pop("class_name", "")
props["data-slot"] = "avatar-fallback"
- cls.set_class_name(ClassNames.FALLBACK, props)
- return super().create(*children, **props)
+ return super().create(
+ *children,
+ class_name=cn(ClassNames.FALLBACK, custom_classes),
+ **props,
+ )
-class HighLevelAvatar(AvatarRoot):
- """High level wrapper for the Avatar component."""
- # The image source URL
- src: Var[str]
+class AvatarBadge(Span, CoreComponent):
+ @classmethod
+ def create(cls, *children, **props) -> Span:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-badge"
+
+ return super().create(
+ *children, class_name=cn(ClassNames.BADGE, custom_classes), **props
+ )
- # Image props
- _image_props = {"src", "on_loading_status_change", "render_"}
- # Fallback props
- _fallback_props = {"delay"}
+class AvatarGroup(Div, CoreComponent):
+ """The flex container wrapper for grouping multiple avatars."""
@classmethod
- def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar component."""
- # Extract props for each subcomponent
- image_props = {k: props.pop(k) for k in cls._image_props & props.keys()}
- fallback_props = {k: props.pop(k) for k in cls._fallback_props & props.keys()}
+ def create(cls, *children, **props) -> Div:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-group"
- fallback_content = props.pop("fallback", "")
+ return super().create(
+ *children, class_name=cn(ClassNames.GROUP, custom_classes), **props
+ )
+
+
+class AvatarGroupCount(Div, CoreComponent):
+ @classmethod
+ def create(cls, *children, **props) -> Div:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-group-count"
- return AvatarRoot.create(
- AvatarImage.create(**image_props),
- AvatarFallback.create(fallback_content, **fallback_props),
+ return super().create(
*children,
+ class_name=cn(ClassNames.GROUP_COUNT, custom_classes),
**props,
)
class Avatar(ComponentNamespace):
- """Namespace for Avatar components."""
-
root = staticmethod(AvatarRoot.create)
image = staticmethod(AvatarImage.create)
fallback = staticmethod(AvatarFallback.create)
+ badge = staticmethod(AvatarBadge.create)
+ group = staticmethod(AvatarGroup.create)
+ group_count = staticmethod(AvatarGroupCount.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelAvatar.create)
avatar = Avatar()
@@ -150,7 +149,7 @@ avatar = Avatar()
```python
-from components.ui.avatar import avatar
+from components.ui.avatar import Avatar
```
@@ -166,125 +165,288 @@ avatar.root(
```
-
# Examples
+## Basic
+
+A basic avatar component with an image and a fallback.
-## General
-Displays a basic avatar with either a user image or a fallback placeholder.
+```python
+def avatar_basic() -> rx.Component:
+ return avatar.root(
+ avatar.image(
+ src="https://github.com/LineIndent.png",
+ custom_attrs={"alt": "@lineindent"},
+ ),
+ avatar.fallback("AH"),
+ )
+```
+
+
+## Badge
+
+Use the `avatar.badge` component to add a badge to the avatar. The badge is positioned at the bottom right of the avatar.
```python
-def avatar_general():
- return rx.box(
- avatar(
+def avatar_with_badge() -> rx.Component:
+ return avatar.root(
+ avatar.image(
src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@LineIndent",
- fallback="CN",
+ custom_attrs={"alt": "@LineIndent"},
),
- avatar(
- src="https://avatars.githubusercontent.com/u/198465274?s=200&v=4",
- alt="@buridan-ui",
- fallback="BUI",
- class_name="rounded-lg",
+ avatar.fallback("AH"),
+ avatar.badge(
+ class_name="bg-green-600 dark:bg-green-800",
),
- rx.box(
- avatar(
- src="",
- alt="@buridan-ui",
- fallback="BU",
+ )
+```
+
+
+Use the `class_Name` prop to add custom styles to the badge such as custom colors, sizes, etc.
+
+```reflex
+avatar.badge(class_name="bg-green-600 dark:bg-green-800")
+```
+
+## Badge with Icon
+
+You can also use an icon inside `avatar.badge`.
+
+
+```python
+def avatar_badge_icon() -> rx.Component:
+ return avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
+ custom_attrs={"alt": "Reflex Dev"},
+ ),
+ avatar.fallback("RD"),
+ avatar.badge(
+ hi("PlusSignIcon"),
+ ),
+ )
+```
+
+
+## Avatar Group
+
+Use the `avatar.group` component to add a group of avatars.
+
+
+```python
+def avatar_as_group() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.image(
+ src="/avatars/01.png",
+ custom_attrs={"alt": "@avatar-1"},
),
- avatar(
- src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@buridan-ui",
- fallback="BUI",
+ avatar.fallback("RD"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/02.png",
+ custom_attrs={"alt": "@avatar-2"},
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
+ avatar.fallback("LI"),
+ ),
+ avatar.root(
+ avatar.fallback("AH"),
+ ),
+ class_name="grayscale",
+ )
+```
+
+
+## Avatar Group Count
+
+Use `avatar.group_count` to add a count to the group.
+
+
+```python
+def avatar_with_group_count() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.fallback("AH"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/01.png",
+ custom_attrs={"alt": "@avatar-1"},
),
- class_name=(
- "flex -space-x-2 "
- "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] "
- "*:data-[slot=avatar]:grayscale"
+ avatar.fallback("RD"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="/avatars/02.png",
+ custom_attrs={"alt": "@avatar-2"},
),
+ avatar.fallback("LI"),
),
- class_name="flex flex-row flex-wrap items-center gap-12 p-8",
+ avatar.group_count("+3"),
+ class_name="grayscale",
)
```
-## Sizes
+## Avatar Group with Icon
-Demonstrates how to scale the avatar component using Tailwind utility classes.
+You can also use an icon inside `avatar.group_count`.
```python
-def avatar_sizes():
- """Example showing different avatar sizes"""
- return rx.box(
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-6",
+def avatar_group_count_icon() -> rx.Component:
+ return avatar.group(
+ avatar.root(
+ avatar.fallback("AH"),
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-8",
- ),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-10",
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
+ custom_attrs={"alt": "@reflex-dev"},
+ ),
+ avatar.fallback("CN"),
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-12",
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "LineIndent"},
+ ),
+ avatar.fallback("LI"),
),
- avatar(
- src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4",
- alt="@reflex",
- fallback="RE",
- class_name="size-16",
+ avatar.group_count(
+ hi("PlusSignIcon"),
),
- class_name="flex items-center gap-4 p-8",
+ class_name="grayscale",
)
```
-## With Badge
+## Sizes
-Shows how to combine an avatar with status or notification badges for added context.
+Use the `size` prop to change the size of the avatar.
```python
-def avatar_with_badge():
- """Example showing avatar with status badge"""
- return rx.box(
- rx.box(
- avatar(
+def avatar_sizes() -> rx.Component:
+ return rx.el.div(
+ avatar.root(
+ avatar.image(
src="https://avatars.githubusercontent.com/u/84860195?v=4",
- alt="@LineIndent",
- fallback="CN",
- class_name="size-12",
+ custom_attrs={"alt": "@LineIndent"},
),
- rx.box(
- class_name=(
- "absolute bottom-0 right-0 size-3 rounded-full "
- "bg-green-500 border-2 border-[var(--background)]"
- ),
+ avatar.fallback("LI"),
+ size="sm",
+ ),
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ avatar.root(
+ avatar.image(
+ src="https://avatars.githubusercontent.com/u/84860195?v=4",
+ custom_attrs={"alt": "@LineIndent"},
),
- class_name="relative inline-block",
+ avatar.fallback("LI"),
+ size="lg",
),
- class_name="p-8",
+ class_name="flex flex-wrap items-center gap-2",
)
```
+
+## Dropdown
+
+You can use the `Avatar` component as a trigger for a dropdown menu.
+
+
+```python
+def avatar_dropdown_menu() -> rx.Component:
+ return menu.root(
+ menu.trigger(
+ render_=button(
+ avatar.root(
+ avatar.image(
+ src="https://github.com/LineIndent.png",
+ custom_attrs={"alt": "lineindent"},
+ ),
+ avatar.fallback("LI"),
+ ),
+ variant="ghost",
+ size="icon",
+ class_name="rounded-full",
+ )
+ ),
+ menu.portal(
+ menu.positioner(
+ menu.popup(
+ menu.group(
+ menu.item("Profile"),
+ menu.item("Billing"),
+ menu.item("Settings"),
+ ),
+ class_name="w-32",
+ ),
+ )
+ ),
+ )
+```
+
+
+# API Reference
+
+## avatar.root
+
+The `avatar.root` component is the root component that wraps the avatar image and fallback.
+
+| Prop | Type | Default |
+| ----------- | --------------------------- | ----------- |
+| `size` | `"default" \| "sm" \| "lg"` | `"default"` |
+| `class_name` | `string` | - |
+
+## avatar.image
+
+The `avatar.image` component displays the avatar image. It accepts all Base UI Avatar Image props.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `src` | `string` | - |
+| `alt` | `string` | - |
+| `class_name` | `string` | - |
+
+## avatar.fallback
+
+The `avatar.fallback` component displays a fallback when the image fails to load. It accepts all Base UI Avatar Fallback props.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.badge
+
+The `avatar.badge` component displays a badge indicator on the avatar, typically positioned at the bottom right.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.group
+
+The `avatar.group` component displays a group of avatars with overlapping styling.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.group_count
+
+The `avatar.group_count` component displays a count indicator in an avatar group, typically showing the number of additional avatars.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
diff --git a/assets/docs/components/badge.md b/assets/docs/components/badge.md
index f4a8bf2..788d841 100644
--- a/assets/docs/components/badge.md
+++ b/assets/docs/components/badge.md
@@ -22,86 +22,54 @@ from typing import Literal
from reflex.vars.base import Var
from reflex_components_core.el import Span
+from ..utils.twmerge import cn
from .component import CoreComponent
-LiteralBadgeVariant = Literal["default", "secondary", "destructive", "outline"]
+LiteralBadgeVariant = Literal[
+ "default", "secondary", "destructive", "outline", "ghost", "link"
+]
DEFAULT_BASE_CLASSES = (
- "inline-flex items-center justify-center rounded-radius border px-2 py-0.5 text-xs font-medium "
- "w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none "
- "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] "
- "aria-invalid:ring-[var(--destructive)]/20 dark:aria-invalid:ring-[var(--destructive)]/40 "
- "aria-invalid:border-[var(--destructive)] transition-[color,box-shadow] overflow-hidden"
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden "
+ "rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap "
+ "transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive "
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!"
)
BADGE_VARIANTS = {
- "default": (
- "border-transparent bg-[var(--primary)] text-[var(--primary-foreground)] "
- "[a&]:hover:bg-[var(--primary)]/90"
- ),
- "secondary": (
- "border-transparent bg-[var(--secondary)] text-[var(--secondary-foreground)] "
- "[a&]:hover:bg-[var(--secondary)]/90"
- ),
+ "default": "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
+ "secondary": "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
"destructive": (
- "border-transparent bg-[var(--destructive)] text-white "
- "[a&]:hover:bg-[var(--destructive)]/90 "
- "focus-visible:ring-[var(--destructive)]/20 dark:focus-visible:ring-[var(--destructive)]/40 "
- "dark:bg-[var(--destructive)]/60"
- ),
- "outline": (
- "text-[var(--foreground)] border-[var(--input)] "
- "[a&]:hover:bg-[var(--accent)] [a&]:hover:text-[var(--accent-foreground)]"
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 "
+ "dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20"
),
+ "outline": "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
+ "ghost": "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ "link": "text-primary underline-offset-4 hover:underline",
}
-def get_badge_classes(variant: LiteralBadgeVariant) -> str:
- """Get the complete badge class string.
-
- Args:
- variant: The badge variant to apply
-
- Returns:
- The complete class string for the badge
- """
- variant_classes = BADGE_VARIANTS[variant]
- return f"{DEFAULT_BASE_CLASSES} {variant_classes}"
+def badge_variants(variant: str = "default") -> Var:
+ return cn(
+ DEFAULT_BASE_CLASSES,
+ BADGE_VARIANTS.get(variant, BADGE_VARIANTS["default"]),
+ )
class Badge(Span, CoreComponent):
- """A badge component that displays a label."""
-
- # Badge variant
- variant: Var[LiteralBadgeVariant]
-
@classmethod
def create(cls, *children, **props) -> Span:
- """Create the badge component.
-
- Args:
- *children: The badge content
- **props: Component properties including variant
-
- Returns:
- A configured Span component
- """
+ custom_classes = props.pop("class_name", "")
variant = props.pop("variant", "default")
+ props["data-slot"] = "badge"
+ props["data-variant"] = variant
- cls.set_class_name(get_badge_classes(variant), props)
-
- # Add data-slot attribute
- props.setdefault("data_slot", "badge")
-
- return super().create(*children, **props)
-
- def _exclude_props(self) -> list[str]:
- """Exclude component-specific props from being passed to the DOM.
-
- Returns:
- List of prop names to exclude
- """
- return [*super()._exclude_props(), "variant"]
+ return super().create(
+ *children,
+ class_name=cn(badge_variants(variant), custom_classes),
+ **props,
+ )
badge = Badge.create
@@ -125,139 +93,131 @@ badge()
```
-
# Examples
+## Variants
-## Default
-
-Displays a standard badge using the default variant, ideal for basic labeling.
+Use the `variant` prop to change the variant of the badge.
```python
-def badge_default():
- return rx.box(
- rx.box(
- badge("Badge"),
- badge("Secondary", variant="secondary"),
- badge("Destructive", variant="destructive"),
- badge("Outline", variant="outline"),
- class_name="flex w-full flex-wrap gap-2",
- ),
- rx.box(
- badge(
- rx.icon(tag="badge-check"),
- "Verified",
- variant="secondary",
- class_name="bg-blue-500 text-white dark:bg-blue-600",
- ),
- badge(
- "8",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "99",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- badge(
- "20+",
- variant="outline",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
- ),
- class_name="flex w-full flex-wrap gap-2",
- ),
- class_name="flex flex-col items-center gap-2 p-8",
+def badge_with_variants() -> rx.Component:
+ return rx.el.div(
+ badge("Default"),
+ badge("Secondary", variant="secondary"),
+ badge("Destructive", variant="destructive"),
+ badge("Outline", variant="outline"),
+ badge("Ghost", variant="ghost"),
+ class_name="flex flex-wrap gap-2",
)
```
## With Icons
-Demonstrates how to include icons inside badges for visual context or emphasis.
+You can render an icon inside the badge. Use `data-icon="inline-start"` to render the icon on the left and `data-icon="inline-end"` to render the icon on the right.
```python
-def badge_with_icons():
- return rx.box(
+def badge_with_icon() -> rx.Component:
+ return rx.el.div(
badge(
- rx.icon(tag="check"),
- "Success",
+ hi("CheckmarkBadge01Icon", custom_attrs={"data-icon": "inline-start"}),
+ "Verified",
variant="secondary",
- class_name="bg-green-500 text-white dark:bg-green-600",
),
badge(
- rx.icon(tag="x"),
- "Error",
- variant="destructive",
+ "Bookmark",
+ hi("Bookmark02Icon", custom_attrs={"data-icon": "inline-end"}),
+ variant="outline",
),
+ class_name="flex flex-wrap gap-2",
+ )
+```
+
+
+## With Spinner
+
+You can render a spinner inside the badge. Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` prop to the spinner.
+
+
+```python
+def badge_with_spinner() -> rx.Component:
+ return rx.el.div(
badge(
- rx.icon(tag="triangle-alert"),
- "Warning",
- variant="secondary",
- class_name="bg-yellow-500 text-white dark:bg-yellow-600",
+ spinner(custom_attrs={"data-icon": "inline-start"}),
+ "Deleting",
+ variant="destructive",
),
badge(
- rx.icon(tag="info"),
- "Info",
+ "Generating",
+ spinner(custom_attrs={"data-icon": "inline-end"}),
variant="secondary",
- class_name="bg-blue-500 text-white dark:bg-blue-600",
),
- class_name="flex flex-wrap gap-2 p-8",
+ class_name="flex flex-wrap gap-2",
)
```
-## Status
+## Link
-Showcases how badges can represent different statuses, like success or error, using color.
+You can pass in `rx.el.a` to turn a badge into a link. The `badge` component accepts `*children` so any interactive element can be passed to it.
```python
-def badge_status():
- return rx.box(
- badge("New", variant="default"),
- badge("Popular", variant="secondary"),
- badge("Sale", variant="destructive"),
- badge("Draft", variant="outline"),
- badge(
- rx.icon(tag="star"),
- "Featured",
- class_name="bg-yellow-500 text-white dark:bg-yellow-600",
+def badge_as_link() -> rx.Component:
+ return badge(
+ rx.el.a(
+ "Open Link",
+ hi("ArrowUpRightIcon", custom_attrs={"data-icon": "inline-end"}),
+ href="#link",
+ class_name="inline-flex items-center gap-1 text-inherit no-underline",
),
- class_name="flex flex-wrap gap-2 p-8",
)
```
-## Notification Count
+## Custom Colors
-Illustrates how to use badges for showing counts, such as unread notifications or messages.
+You can customize the colors of a badge by adding custom classes such as `bg-green-50 dark:bg-green-800` to the `badge` component.
```python
-def badge_notification_count():
- return rx.box(
+def badge_custom_colors() -> rx.Component:
+ return rx.el.div(
badge(
- "1",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
+ "Blue",
+ class_name="bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300",
),
badge(
- "5",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
+ "Green",
+ class_name="bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300",
),
badge(
- "10",
- variant="secondary",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
+ "Sky",
+ class_name="bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300",
),
badge(
- "99+",
- variant="destructive",
- class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums",
+ "Purple",
+ class_name="bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300",
+ ),
+ badge(
+ "Red",
+ class_name="bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300",
),
- class_name="flex items-center gap-2 p-8",
+ class_name="flex flex-wrap gap-2",
)
```
+
+
+# API Reference
+
+## badge
+
+The `badge` component displays a badge or a component that looks like a badge.
+
+| Prop | Type | Default |
+| ----------- | ----------------------------------------------------------------------------- | ----------- |
+| `variant` | `"default" \| "secondary" \| "destructive" \| "outline" \| "ghost" \| "link"` | `"default"` |
+| `class_name` | `string` | - |
diff --git a/assets/docs/components/breadcrumb.md b/assets/docs/components/breadcrumb.md
index bc7daf1..78b9721 100644
--- a/assets/docs/components/breadcrumb.md
+++ b/assets/docs/components/breadcrumb.md
@@ -19,99 +19,139 @@ buridan add component breadcrumb
```python
import reflex as rx
+from ..icons.hugeicon import hi
+from ..utils.twmerge import cn
-def breadcrumb(*children, **props):
- """Breadcrumb navigation container"""
- return rx.el.nav(
- *children, aria_label="breadcrumb", data_slot="breadcrumb", **props
- )
+class Breadcrumb:
+ @classmethod
+ def root(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
-def breadcrumb_list(*children, class_name: str = "", **props):
- """Ordered list container for breadcrumb items"""
- base_classes = (
- "text-[var(--muted-foreground)] flex flex-wrap items-center gap-1 text-sm "
- "break-words sm:gap-2.5"
- )
+ props["aria-label"] = "breadcrumb"
- return rx.el.ol(
- *children,
- data_slot="breadcrumb-list",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ props["data-slot"] = "breadcrumb"
+ return rx.el.nav(
+ *children,
+ class_name=cn(custom_classes),
+ **props,
+ )
-def breadcrumb_item(*children, class_name: str = "", **props):
- """Individual breadcrumb item"""
- base_classes = "inline-flex items-center gap-1.5"
+ @classmethod
+ def list(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
- return rx.el.li(
- *children,
- data_slot="breadcrumb-item",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ props["data-slot"] = "breadcrumb-list"
+ base_classes = (
+ "flex flex-wrap items-center gap-1.5 text-sm wrap-break-word "
+ "text-muted-foreground"
+ )
-def breadcrumb_link(*children, href: str = "#", class_name: str = "", **props):
- """Breadcrumb link (clickable)"""
- base_classes = "hover:text-[var(--foreground)] transition-colors no-underline"
+ return rx.el.ol(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
- return rx.el.a(
- *children,
- href=href,
- data_slot="breadcrumb-link",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ @classmethod
+ def item(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "breadcrumb-item"
-def breadcrumb_page(*children, class_name: str = "", **props):
- """Current page breadcrumb (non-clickable)"""
- base_classes = "text-[var(--foreground)] font-normal"
+ base_classes = "inline-flex items-center gap-1"
- return rx.el.span(
- *children,
- role="link",
- aria_disabled="true",
- aria_current="page",
- data_slot="breadcrumb-page",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ return rx.el.li(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+ @classmethod
+ def link(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
-def breadcrumb_separator(*children, class_name: str = "", **props):
- """Separator between breadcrumb items"""
- base_classes = "[&>svg]:size-3.5"
+ props["data-slot"] = "breadcrumb-link"
- if not children:
- children = (rx.icon(tag="chevron-right", size=14),)
+ props.setdefault("href", "#")
- return rx.el.li(
- *children,
- role="presentation",
- aria_hidden="true",
- data_slot="breadcrumb-separator",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ base_classes = "transition-colors hover:text-foreground no-underline"
+ return rx.el.a(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
-def breadcrumb_ellipsis(class_name: str = "", **props):
- """Ellipsis for collapsed breadcrumb items"""
- base_classes = "flex size-9 items-center justify-center"
+ @classmethod
+ def page(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
- return rx.el.span(
- rx.icon(tag="ellipsis", size=16),
- rx.el.span("More", class_name="sr-only"),
- role="presentation",
- aria_hidden="true",
- data_slot="breadcrumb-ellipsis",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+ props["data-slot"] = "breadcrumb-page"
+
+ props["role"] = "link"
+
+ props["aria-disabled"] = "true"
+
+ props["aria-current"] = "page"
+
+ base_classes = "font-normal text-foreground"
+
+ return rx.el.span(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def separator(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-separator"
+
+ props["role"] = "presentation"
+
+ props["aria-hidden"] = "true"
+
+ base_classes = "[&>svg]:size-3.5"
+
+ if not children:
+ children = (hi("ArrowRight01Icon"),)
+
+ return rx.el.li(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def ellipsis(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-ellipsis"
+
+ props["role"] = "presentation"
+
+ props["aria-hidden"] = "true"
+
+ base_classes = "flex size-5 items-center justify-center [&>svg]:size-4"
+
+ if not children:
+ children = (
+ rx.icon(tag="ellipsis"),
+ rx.el.span("More", class_name="sr-only"),
+ )
+
+ return rx.el.span(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+
+breadcrumb = Breadcrumb
```
@@ -137,150 +177,187 @@ breadcrumb(
# Examples
-## Basic Demo
-A basic breadcrumb showing the default navigation structure.
+## Basic
+A basic breadcrumb with a home link and a components link.
```python
-def breadcrumb_basic_demo():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- rx.menu.root(
- rx.menu.trigger(
- rx.box(
- breadcrumb_ellipsis(class_name="size-4"),
- rx.el.span("Toggle menu", class_name="sr-only"),
- class_name="flex items-center gap-1",
- ),
- ),
- rx.menu.content(
- rx.menu.item("Documentation"),
- rx.menu.item("Themes"),
- rx.menu.item("GitHub"),
- class_name="min-w-[8rem]",
- ),
- ),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Components", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page("Breadcrumb"),
- ),
+def breadcrumb_basic() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
),
- ),
- class_name="p-8",
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
)
```
-## Simple Breadcrumb
-A minimal breadcrumb with plain text links.
+## Custom Separator
+Use a custom component as `children` for `breadcrumb.separator` to create a custom separator.
```python
-def breadcrumb_simple_breadcrumb():
- return rx.box(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Products", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link("Electronics", href="#"),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page("Laptop"),
- ),
+def breadcrumb_custom_separator() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
),
- ),
- class_name="p-8",
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
)
```
-## Icon Breadcrumb
-A breadcrumb that includes icons alongside link labels.
+## Dropdown
+
+You can compose `breadcrumb.item` with a `menu.root` to create a dropdown in the breadcrumb.
```python
-def breadcrumb_icon_breadcrumb():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link(
- rx.icon(tag="home", size=14),
- "Home",
- href="#",
- class_name="flex flex-row gap-x-1 items-center",
- ),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_link(
- rx.icon(tag="folder", size=14),
- "Documents",
- href="#",
- class_name="flex flex-row gap-x-1 items-center",
+def breadcrumb_dropdown_demo() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
+ ),
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ menu.root(
+ menu.trigger(
+ render_=rx.el.button(
+ "Components",
+ hi(
+ "ArrowDown01Icon",
+ custom_attrs={"data-icon": "inline-end"},
+ class_name="size-4",
+ ),
+ class_name="flex flex-row items-center gap-x-2",
+ )
),
- ),
- breadcrumb_separator(),
- breadcrumb_item(
- breadcrumb_page(
- rx.icon(tag="file-text", size=14),
- "README.md",
- class_name="flex flex-row gap-x-1 items-center",
+ menu.portal(
+ menu.positioner(
+ menu.popup(
+ menu.group(
+ menu.item("Documentation"),
+ menu.item("Themes"),
+ menu.item("GitHub"),
+ ),
+ align="start",
+ )
+ ),
),
),
),
- ),
- class_name="p-8",
+ breadcrumb.separator(hi("LinerIcon")),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
)
```
-## Custom Separator
-A breadcrumb with a customized separator between items.
+## Collapsed
+
+We provide a `breadcrumb.ellipsis` component to show a collapsed state when the breadcrumb is too long.
```python
-def breadcrumb_custom_separator():
- return rx.el.div(
- breadcrumb(
- breadcrumb_list(
- breadcrumb_item(
- breadcrumb_link("Home", href="#"),
- ),
- breadcrumb_separator(
- rx.text("/", class_name="text-[var(--muted-foreground)]")
- ),
- breadcrumb_item(
- breadcrumb_link("Blog", href="#"),
- ),
- breadcrumb_separator(
- rx.text("/", class_name="text-[var(--muted-foreground)]")
- ),
- breadcrumb_item(
- breadcrumb_page("Article"),
- ),
+def breadcrumb_ellipsis() -> rx.Component:
+ return breadcrumb.root(
+ breadcrumb.list(
+ breadcrumb.item(
+ breadcrumb.link("Home", href="#"),
),
- ),
- class_name="p-8",
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.ellipsis(),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.link("Components", href="#"),
+ ),
+ breadcrumb.separator(),
+ breadcrumb.item(
+ breadcrumb.page("Breadcrumb"),
+ ),
+ )
)
```
+
+# API Reference
+
+## breadcrumb.root
+
+The `breadcrumb.root` component is the root navigation element that wraps all breadcrumb components.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.list
+
+The `breadcrumb.list` component displays the ordered list of breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.item
+
+The `breadcrumb.item` component wraps individual breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.link
+
+The `breadcrumb.link` component displays a clickable link in the breadcrumb.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.page
+
+The `breadcrumb.page` component displays the current page in the breadcrumb (non-clickable).
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.separator
+
+The `breadcrumb.separator` component displays a separator between breadcrumb items. You can pass custom children to override the default separator icon.
+
+| Prop | Type | Default |
+| ----------- | ----------------- | ------- |
+| `children` | `rx.Component` | - |
+| `class_name` | `string` | - |
+
+## breadcrumb.ellipsis
+
+The `breadcrumb.ellipsis` component displays an ellipsis indicator for collapsed breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
diff --git a/assets/docs/components/button.md b/assets/docs/components/button.md
index c093a36..02668cf 100644
--- a/assets/docs/components/button.md
+++ b/assets/docs/components/button.md
@@ -20,121 +20,139 @@ buridan add component button
from typing import Literal
from reflex.vars.base import Var
-from reflex_components_core.core import cond
from reflex_components_core.el import Button as BaseButton
-from ..icons.others import spinner
+from ..utils.twmerge import cn
from .component import CoreComponent
LiteralButtonVariant = Literal[
- "primary", "destructive", "outline", "secondary", "ghost", "link", "dark"
+ "default",
+ "destructive",
+ "outline",
+ "secondary",
+ "ghost",
+ "link",
]
LiteralButtonSize = Literal[
- "default", "xs", "sm", "lg", "icon", "icon-xs", "icon-sm", "icon-lg"
+ "default",
+ "xs",
+ "sm",
+ "lg",
+ "icon",
+ "icon-xs",
+ "icon-sm",
+ "icon-lg",
]
DEFAULT_CLASS_NAME = (
- "font-theme inline-flex items-center justify-center gap-2 whitespace-nowrap "
- "rounded-radius text-sm font-medium transition-all "
- "disabled:pointer-events-none disabled:opacity-50 outline-none "
- "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 "
- "[&_svg]:shrink-0 shrink-0"
+ "group/button inline-flex shrink-0 items-center justify-center "
+ "rounded-lg border border-transparent bg-clip-padding "
+ "text-sm font-medium whitespace-nowrap transition-all outline-none select-none "
+ "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 "
+ "active:not-aria-[haspopup]:translate-y-px "
+ "disabled:pointer-events-none disabled:opacity-50 "
+ "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 "
+ "dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 "
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 "
+ "[&_svg:not([class*='size-'])]:size-4"
)
BUTTON_VARIANTS = {
"variant": {
- "default": "bg-primary text-primary-foreground hover:bg-primary/90",
+ "default": ("bg-primary text-primary-foreground hover:bg-primary/80"),
+ "outline": (
+ "border-border bg-background hover:bg-muted hover:text-foreground "
+ "aria-expanded:bg-muted aria-expanded:text-foreground "
+ "dark:border-input dark:bg-input/30 dark:hover:bg-input/50"
+ ),
+ "secondary": (
+ "bg-secondary text-secondary-foreground "
+ "hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] "
+ "aria-expanded:bg-secondary aria-expanded:text-secondary-foreground"
+ ),
+ "ghost": (
+ "hover:bg-muted hover:text-foreground "
+ "aria-expanded:bg-muted aria-expanded:text-foreground "
+ "dark:hover:bg-muted/50"
+ ),
"destructive": (
"bg-destructive/10 text-destructive hover:bg-destructive/20 "
"focus-visible:border-destructive/40 focus-visible:ring-destructive/20 "
- "dark:bg-destructive/20 dark:hover:bg-destructive/30"
- ),
- "outline": (
- "border border-input bg-background shadow-xs text-foreground "
- "hover:bg-accent hover:text-accent-foreground "
- "dark:bg-input/30 dark:hover:bg-input/50"
+ "dark:bg-destructive/20 dark:hover:bg-destructive/30 "
+ "dark:focus-visible:ring-destructive/40"
),
- "secondary": "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- "ghost": "hover:bg-accent hover:text-accent-foreground dark:hover:bg-muted/50",
"link": "text-primary underline-offset-4 hover:underline",
},
"size": {
- "default": "h-8 gap-1.5 px-2.5 has-[>svg]:px-2.5",
- "xs": "h-6 rounded-[min(calc(var(--radius) * 0.8),10px)] gap-1 px-2 text-xs",
- "sm": "h-7 rounded-[min(calc(var(--radius) * 0.8),12px)] gap-1 px-2.5 text-[0.8rem]",
- "lg": "h-9 gap-1.5 px-2.5",
+ "default": (
+ "h-8 gap-1.5 px-2.5 "
+ "has-data-[icon=inline-end]:pr-2 "
+ "has-data-[icon=inline-start]:pl-2"
+ ),
+ "xs": (
+ "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs "
+ "in-data-[slot=button-group]:rounded-lg "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 "
+ "[&_svg:not([class*='size-'])]:size-3"
+ ),
+ "sm": (
+ "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] "
+ "in-data-[slot=button-group]:rounded-lg "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 "
+ "[&_svg:not([class*='size-'])]:size-3.5"
+ ),
+ "lg": (
+ "h-9 gap-1.5 px-2.5 "
+ "has-data-[icon=inline-end]:pr-2 "
+ "has-data-[icon=inline-start]:pl-2"
+ ),
"icon": "size-8",
- "icon-xs": "size-6 rounded-[min(calc(var(--radius) * 0.8),10px)]",
- "icon-sm": "size-7 rounded-[min(calc(var(--radius) * 0.8),12px)]",
+ "icon-xs": (
+ "size-6 rounded-[min(var(--radius-md),10px)] "
+ "in-data-[slot=button-group]:rounded-lg "
+ "[&_svg:not([class*='size-'])]:size-3"
+ ),
+ "icon-sm": (
+ "size-7 rounded-[min(var(--radius-md),12px)] "
+ "in-data-[slot=button-group]:rounded-lg"
+ ),
"icon-lg": "size-9",
},
}
class Button(BaseButton, CoreComponent):
- """A custom button component."""
-
- # Button variant. Defaults to "primary".
variant: Var[LiteralButtonVariant]
-
- # Button size. Defaults to "md".
size: Var[LiteralButtonSize]
- # The loading state of the button
- loading: Var[bool]
-
@classmethod
def create(cls, *children, **props) -> BaseButton:
- """Create the button component."""
variant = props.pop("variant", "default")
- cls.validate_variant(variant)
-
size = props.pop("size", "default")
- cls.validate_size(size)
-
- loading = props.pop("loading", False)
- disabled = props.pop("disabled", False)
-
- button_classes = f"{DEFAULT_CLASS_NAME} {BUTTON_VARIANTS['variant'][variant]} {BUTTON_VARIANTS['size'][size]}"
-
- cls.set_class_name(button_classes, props)
-
- children_list = list(children)
-
- if isinstance(loading, Var):
- props["disabled"] = cond(loading, True, disabled)
- children_list.insert(0, cond(loading, spinner()))
- else:
- props["disabled"] = True if loading else disabled
- children_list.insert(0, spinner()) if loading else None
-
- return super().create(*children_list, **props)
+ custom_classes = props.pop("class_name", "")
+
+ return super().create(
+ *children,
+ data_slot="button",
+ class_name=cn(
+ DEFAULT_CLASS_NAME,
+ BUTTON_VARIANTS["variant"].get(variant, ""),
+ BUTTON_VARIANTS["size"].get(size, ""),
+ custom_classes,
+ ),
+ **props,
+ )
- @staticmethod
- def validate_variant(variant: LiteralButtonVariant):
- """Validate the button variant."""
- if variant not in BUTTON_VARIANTS["variant"]:
- available_variants = ", ".join(BUTTON_VARIANTS["variant"].keys())
- message = (
- f"Invalid variant: {variant}. Available variants: {available_variants}"
- )
- raise ValueError(message)
+ def _exclude_props(self) -> list[str]:
+ return [*super()._exclude_props(), "size", "variant"]
- @staticmethod
- def validate_size(size: LiteralButtonSize):
- """Validate the button size."""
- if size not in BUTTON_VARIANTS["size"]:
- available_sizes = ", ".join(BUTTON_VARIANTS["size"].keys())
- message = f"Invalid size: {size}. Available sizes: {available_sizes}"
- raise ValueError(message)
- def _exclude_props(self) -> list[str]:
- return [
- *super()._exclude_props(),
- "size",
- "variant",
- "loading",
- ]
+def button_variants(variant: str = "default", size: str = "default") -> Var:
+ return cn(
+ DEFAULT_CLASS_NAME,
+ BUTTON_VARIANTS["variant"].get(variant, ""),
+ BUTTON_VARIANTS["size"].get(size, ""),
+ )
button = Button.create
@@ -158,52 +176,81 @@ button()
```
-
# Examples
-
## Sizes
-Showcases buttons in different predefined sizes (default, small, large, icon, etc).
+Use the `size` prop to change the size of the button.
```python
-def button_sizes():
+def button_size() -> rx.Component:
return rx.el.div(
- button("Small", size="sm"),
- button("Default", size="default"),
- button("Large", size="lg"),
- class_name="flex items-center gap-3",
+ rx.el.div(
+ button("Extra Small", size="xs", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-xs",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Small", size="sm", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-sm",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Default", variant="outline"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ rx.el.div(
+ button("Large", variant="outline", size="lg"),
+ button(
+ hi("ArrowUpRight03Icon"),
+ size="icon-lg",
+ aria_label="Submit",
+ variant="outline",
+ ),
+ class_name="flex items-start gap-2",
+ ),
+ class_name="flex flex-col items-start gap-8 sm:flex-row",
)
```
## Default
-The default visual style for buttons with standard background and hover effects.
-
```python
def button_default():
- return button("Default", variant="default", size="sm")
+ return button("Button")
```
## Secondary
-A more muted alternative to the default button, useful for less prominent actions.
-
```python
def button_secondary():
- return button("Secondary", variant="secondary", size="sm")
+ return button("Secondary", variant="secondary")
```
## Outline
-Buttons with a bordered outline, blending well with minimal UIs or light themes.
-
```python
def button_outline():
@@ -213,8 +260,6 @@ def button_outline():
## Ghost
-A button style with no background or border, ideal for subtle UI actions.
-
```python
def button_ghost():
@@ -224,8 +269,6 @@ def button_ghost():
## Link
-A button styled to look like a hyperlink — useful for inline actions or navigation.
-
```python
def button_link():
@@ -235,8 +278,6 @@ def button_link():
## Destructive
-A bold style used for destructive or dangerous actions like “Delete”.
-
```python
def button_destructive():
@@ -246,13 +287,112 @@ def button_destructive():
## Icon
-Examples showing icon-only buttons with varying sizes for compact UI elements.
-
```python
def button_icon():
return button(
- hi("Mail01Icon", class_name="size-4"), variant="outline", size="icon-sm"
+ hi("CircleArrowUp01Icon", class_name="size-4"),
+ variant="outline",
+ size="icon",
)
```
+
+## With Icon
+
+Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` attribute to the icon for the correct spacing.
+
+
+```python
+def button_with_icon() -> rx.Component:
+ return rx.el.div(
+ button(
+ hi("GitBranchIcon", custom_attrs={"data-icon": "inline-start"}),
+ "New Branch",
+ variant="outline",
+ ),
+ button(
+ "Fork",
+ hi("GitForkIcon", custom_attrs={"data-icon": "inline-end"}),
+ variant="outline",
+ ),
+ class_name="flex gap-2",
+ )
+```
+
+
+## Rounded
+
+Use the `rounded-full` class to make the button rounded.
+
+
+```python
+def button_rounded() -> rx.Component:
+ return rx.el.div(
+ button(
+ "Get Started",
+ class_name="rounded-full",
+ ),
+ button(
+ hi("ArrowUp02Icon"),
+ variant="outline",
+ size="icon",
+ class_name="rounded-full",
+ ),
+ class_name="flex gap-2",
+ )
+```
+
+
+## Spinner
+
+Render a `spinner()` component inside the button to show a loading state. Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` attribute to the spinner for the correct spacing.
+
+
+```python
+def button_loading() -> rx.Component:
+ return rx.el.div(
+ button(
+ spinner(custom_attrs={"data-icon": "inline-start"}),
+ "Generating",
+ variant="outline",
+ ),
+ button(
+ "Downloading",
+ spinner(custom_attrs={"data-icon": "inline-end"}),
+ variant="secondary",
+ disabled=True,
+ ),
+ class_name="flex gap-2",
+ )
+```
+
+
+## As Link
+
+You can use the `button_variants` helper function to make a link look like a button.
+
+Do **not** use `button(rx.el.a(...))` for links. The Base UI `Button` component always applies `role="button"`, which overrides the semantic link role on `` elements. Use `button_variants` with a plain `rx.el.a` tag instead to cleanly generate the necessary classes as a dynamic Reflex `Var`.
+
+
+```python
+def button_render() -> rx.Component:
+ return rx.el.a(
+ "Login",
+ href="#",
+ class_name=button_variants(variant="secondary", size="sm"),
+ )
+```
+
+
+
+# API Reference
+
+## Button
+
+The `Button` component is a wrapper around the `button` element that adds a variety of styles and functionality.
+
+| Prop | Type | Default |
+| --------- | ------------------------------------------------------------------------------------ | ----------- |
+| `variant` | `"default" \| "outline" \| "ghost" \| "destructive" \| "secondary" \| "link"` | `"default"` |
+| `size` | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg"` | `"default"` |
diff --git a/assets/docs/components/input.md b/assets/docs/components/input.md
index 75b32dc..ca8a43f 100644
--- a/assets/docs/components/input.md
+++ b/assets/docs/components/input.md
@@ -24,7 +24,7 @@ INPUT = (
"w-full file:text-foreground placeholder:text-muted-foreground "
"selection:bg-primary selection:text-primary-foreground "
"dark:bg-input/30 border-input "
- "h-9 w-full min-w-0 rounded-radius border bg-transparent px-3 py-1 text-base "
+ "h-9 w-full min-w-0 rounded-lg border bg-transparent px-3 py-1 text-base "
"transition-[color,box-shadow] outline-none "
"file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium "
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 "
diff --git a/assets/docs/components/menu.md b/assets/docs/components/menu.md
index 45fb364..edeb2dc 100644
--- a/assets/docs/components/menu.md
+++ b/assets/docs/components/menu.md
@@ -17,21 +17,15 @@ buridan add component menu
### Manual Installation
```python
-"""Custom menu component."""
-
from typing import Literal
from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventHandler, passthrough_event_spec
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var
-from reflex_components_core.core.foreach import foreach
from ..icons.hugeicon import hi
-from ..icons.others import select_arrow
-from ..utils.twmerge import cn
from .base_ui import PACKAGE_NAME, BaseUIComponent
-from .button import button
LiteralOpenChangeReason = Literal[
"arrowKey",
@@ -59,7 +53,7 @@ class ClassNames:
POPUP = (
"z-50 max-h-[var(--available-height)] w-[var(--anchor-width)] min-w-32 "
"origin-[var(--transform-origin)] overflow-x-hidden overflow-y-auto "
- "rounded-radius bg-popover p-1 text-popover-foreground shadow-md "
+ "rounded-lg bg-popover p-1 text-popover-foreground shadow-md "
"ring-1 ring-foreground/10 duration-100 outline-none "
"data-[side=bottom]:slide-in-from-top-2 "
"data-[side=left]:slide-in-from-right-2 "
@@ -70,7 +64,7 @@ class ClassNames:
)
ITEM = (
"group/menu-item relative flex cursor-default items-center gap-1.5 "
- "rounded-radius px-1.5 py-1 text-sm outline-hidden select-none "
+ "rounded-lg px-1.5 py-1 text-sm outline-hidden select-none "
"focus:bg-accent focus:text-accent-foreground "
"not-data-[variant=destructive]:focus:**:text-accent-foreground "
"data-inset:pl-7 "
@@ -86,7 +80,7 @@ class ClassNames:
POSITIONER_SUB = "isolate z-50 outline-none"
POPUP_SUB = (
"z-50 w-auto min-w-24 origin-(--transform-origin) overflow-x-hidden "
- "overflow-y-auto rounded-radius bg-popover p-1 text-popover-foreground "
+ "overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground "
"shadow-lg ring-1 ring-foreground/10 duration-100 "
"data-[side=bottom]:slide-in-from-top-2 "
"data-[side=left]:slide-in-from-right-2 "
@@ -99,7 +93,7 @@ class ClassNames:
"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7"
)
SUBMENU_TRIGGER = (
- "flex cursor-default items-center justify-between gap-1.5 rounded-radius px-1.5 py-1 "
+ "flex cursor-default items-center justify-between gap-1.5 rounded-lg px-1.5 py-1 "
"text-sm outline-hidden select-none "
"focus:bg-accent focus:text-accent-foreground "
"not-data-[variant=destructive]:focus:**:text-accent-foreground "
@@ -315,18 +309,6 @@ class MenuPopup(MenuBaseComponent):
return super().create(
*children,
**props,
- # style={
- # "keyframes": {
- # "animate-in": {
- # "from": {"opacity": "0", "transform": "scale(0.95)"},
- # "to": {"opacity": "1", "transform": "scale(1)"},
- # },
- # "animate-out": {
- # "from": {"opacity": "1", "transform": "scale(2)"},
- # "to": {"opacity": "0", "transform": "scale(0.95)"},
- # },
- # },
- # },
)
@@ -637,133 +619,6 @@ class MenuSeparator(MenuBaseComponent):
return super().create(*children, **props)
-class HighLevelMenu(MenuRoot):
- """High level wrapper for the Menu component."""
-
- # The trigger component to use for the menu
- trigger: Var[Component | None]
-
- # The list of items to display in the menu dropdown - can be strings or tuples of (label, on_click_handler)
- items: Var[list[str | tuple[str, EventHandler]]]
-
- # The placeholder text to display when no item is selected
- placeholder: Var[str]
-
- # The size of the menu. Defaults to "md".
- size: Var[LiteralMenuSize]
-
- # Whether to close the menu when the item is clicked. Defaults to True.
- close_on_click: Var[bool]
-
- # Props for different component parts
- _item_props = {"close_on_click"}
- _trigger_props = {"placeholder", "size", "close_on_click"}
- _positioner_props = {
- "align",
- "align_offset",
- "side",
- "arrow_padding",
- "collision_padding",
- "sticky",
- "position_method",
- "track_anchor",
- "side_offset",
- "collision_avoidance",
- }
- _portal_props = {"container"}
-
- @classmethod
- def create(cls, *children, **props) -> BaseUIComponent:
- """Create a menu component.
-
- Args:
- *children: Additional children to include in the menu.
- **props: Additional properties to apply to the menu component.
-
- Returns:
- The menu component.
- """
- # Extract props for different parts
- item_props = {k: props.pop(k) for k in cls._item_props & props.keys()}
- trigger_props = {k: props.pop(k) for k in cls._trigger_props & props.keys()}
- positioner_props = {
- k: props.pop(k) for k in cls._positioner_props & props.keys()
- }
- portal_props = {k: props.pop(k) for k in cls._portal_props & props.keys()}
-
- trigger = props.pop("trigger", None)
- items = props.pop("items", [])
- size = trigger_props.get("size", "md")
- trigger_label = trigger_props.get("placeholder", "Open Menu")
-
- def create_menu_item(item: str | tuple[str, EventHandler]) -> BaseUIComponent:
- if isinstance(item, tuple):
- label, on_click_handler = item
- return MenuItem.create(
- render_=button(
- label,
- variant="ghost",
- class_name=ClassNames.ITEM,
- disabled=props.get("disabled", False),
- on_click=on_click_handler,
- size=size,
- type="button",
- ),
- key=label,
- **item_props,
- )
- return MenuItem.create(
- render_=button(
- item,
- variant="ghost",
- class_name=ClassNames.ITEM,
- disabled=props.get("disabled", False),
- size=size,
- type="button",
- ),
- key=item,
- **item_props,
- )
-
- if isinstance(items, Var):
- items_children = foreach(items, create_menu_item)
- else:
- items_children = [create_menu_item(item) for item in items]
-
- return MenuRoot.create(
- MenuTrigger.create(
- render_=(
- trigger
- if trigger
- else button(
- trigger_label,
- select_arrow(class_name="size-4 text-secondary-9"),
- variant="outline",
- class_name=ClassNames.TRIGGER,
- disabled=props.get("disabled", False),
- size=size,
- type="button",
- )
- ),
- ),
- MenuPortal.create(
- MenuPositioner.create(
- MenuPopup.create(
- items_children,
- class_name=cn(
- ClassNames.POPUP,
- "",
- ),
- ),
- **positioner_props,
- ),
- **portal_props,
- ),
- *children,
- **props,
- )
-
-
class Menu(ComponentNamespace):
"""Namespace for Menu components."""
@@ -785,7 +640,6 @@ class Menu(ComponentNamespace):
submenu_root = staticmethod(MenuSubMenuRoot.create)
submenu_trigger = staticmethod(MenuSubMenuTrigger.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelMenu.create)
menu = Menu()
@@ -796,7 +650,7 @@ menu = Menu()
```python
-from components.ui.menu import menu
+from components.ui.menu import Menu
```
diff --git a/assets/docs/components/scroll-area.md b/assets/docs/components/scroll-area.md
index 08ac94b..0949ff0 100644
--- a/assets/docs/components/scroll-area.md
+++ b/assets/docs/components/scroll-area.md
@@ -310,7 +310,7 @@ def scroll_area_horizontal():
),
scroll_area.scrollbar(scroll_area.thumb(), orientation="horizontal"),
scroll_area.corner(),
- class_name="w-96 rounded-radius border border-input",
+ class_name="w-96 rounded-lg border border-input",
)
```
diff --git a/assets/docs/components/select.md b/assets/docs/components/select.md
index d5e9907..c383b36 100644
--- a/assets/docs/components/select.md
+++ b/assets/docs/components/select.md
@@ -41,7 +41,7 @@ LiteralOrientation = Literal["horizontal", "vertical"]
class ClassNames:
"""Class names for select components."""
- TRIGGER = "flex w-fit items-center justify-between gap-1.5 rounded-radius border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+ TRIGGER = "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
VALUE = "flex-1 text-left cursor-default"
ICON = "flex size-4 text-secondary-10 group-data-[disabled]/trigger:text-current"
POPUP = "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
@@ -590,7 +590,7 @@ class HighLevelSelect(SelectRoot):
class_name=cn(
ClassNames.POPUP,
"",
- "rounded-radius",
+ "rounded-lg",
),
),
**positioner_props,
diff --git a/assets/docs/components/skeleton.md b/assets/docs/components/skeleton.md
index 0ed4941..4bfdd36 100644
--- a/assets/docs/components/skeleton.md
+++ b/assets/docs/components/skeleton.md
@@ -84,7 +84,7 @@ def skeleton_card():
card.content(
skeleton_component(class_name="aspect-video w-full rounded-md"),
),
- class_name="w-full max-w-xs border border-input rounded-radius",
+ class_name="w-full max-w-xs border border-input rounded-lg",
)
```
diff --git a/assets/docs/components/slider.md b/assets/docs/components/slider.md
index 1647013..e9b49b1 100644
--- a/assets/docs/components/slider.md
+++ b/assets/docs/components/slider.md
@@ -57,19 +57,19 @@ class ClassNames:
)
TRACK = (
- "h-1 w-full rounded-radius bg-secondary select-none "
+ "h-1 w-full rounded-lg bg-secondary select-none "
"data-[orientation=vertical]:h-full "
"data-[orientation=vertical]:w-1"
)
INDICATOR = (
- "absolute h-full rounded-radius bg-primary select-none "
+ "absolute h-full rounded-lg bg-primary select-none "
"data-[orientation=vertical]:w-full "
"data-[orientation=vertical]:h-auto"
)
THUMB = (
- "size-3 rounded-radius bg-white outline-[1px] outline-black "
+ "size-3 rounded-lg bg-white outline-[1px] outline-black "
"select-none box-shadow:[0_0_0_1px_rgba(0,0,0,1),0_1px_2px_rgba(0,0,0,.04)] "
"data-[dragging]:h-5 transition-[height,scale] hover:h-4.5"
)
diff --git a/assets/docs/components/spinner.md b/assets/docs/components/spinner.md
index d859d03..ac09775 100644
--- a/assets/docs/components/spinner.md
+++ b/assets/docs/components/spinner.md
@@ -26,6 +26,12 @@ from ..utils.twmerge import cn
def spinner(class_name: str = "", **props) -> rx.Component:
+ incoming_attrs = props.pop("custom_attrs", {})
+
+ base_attrs = {"viewBox": "0 0 16 16"}
+
+ merged_attrs = {**base_attrs, **incoming_attrs}
+
return svg(
svg.path(
opacity="0.2",
@@ -39,7 +45,7 @@ def spinner(class_name: str = "", **props) -> rx.Component:
stroke_width="1.5",
),
xmlns="http://www.w3.org/2000/svg",
- custom_attrs={"viewBox": "0 0 16 16"},
+ custom_attrs=merged_attrs,
class_name=cn("size-4 animate-spin fill-none", class_name),
data_slot="spinner",
role="status",
diff --git a/assets/docs/components/tabs.md b/assets/docs/components/tabs.md
index 39c8a44..8d3b0e3 100644
--- a/assets/docs/components/tabs.md
+++ b/assets/docs/components/tabs.md
@@ -36,7 +36,7 @@ class ClassNames:
LIST = (
"relative bg-muted text-muted-foreground inline-flex h-9 w-full w-fit items-center "
- "justify-center rounded-radius p-[3px] "
+ "justify-center rounded-lg p-[3px] "
"data-[orientation=vertical]:flex data-[orientation=vertical]:flex-col "
"data-[orientation=vertical]:h-auto "
"data-[orientation=vertical]:p-1 data-[orientation=vertical]:gap-1"
@@ -45,7 +45,7 @@ class ClassNames:
"relative z-[1] flex items-center justify-center text-sm font-medium "
"text-foreground dark:text-muted-foreground "
"data-[selected]:text-foreground "
- "rounded-radius px-2 py-1 transition-all "
+ "rounded-lg px-2 py-1 transition-all "
"data-[orientation=vertical]:justify-start data-[orientation=vertical]:w-full "
"data-[orientation=vertical]:text-left"
)
@@ -55,7 +55,7 @@ class ClassNames:
)
INDICATOR = (
- "absolute z-[0] rounded-radius bg-background shadow-sm dark:border dark:border-input dark:bg-input/30 "
+ "absolute z-[0] rounded-lg bg-background shadow-sm dark:border dark:border-input dark:bg-input/30 "
"transition-all duration-200 ease-in-out "
"[left:var(--active-tab-left)] [top:var(--active-tab-top)] "
"[width:var(--active-tab-width)] [height:var(--active-tab-height)]"
diff --git a/assets/docs/components/textarea.md b/assets/docs/components/textarea.md
index 491c2be..139cba1 100644
--- a/assets/docs/components/textarea.md
+++ b/assets/docs/components/textarea.md
@@ -28,7 +28,7 @@ from .component import CoreComponent
class ClassNames:
"""Class names for textarea components."""
- ROOT = "flex field-sizing-content min-h-16 min-w-xs rounded-radius border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
+ ROOT = "flex field-sizing-content min-h-16 min-w-xs rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
class Textarea(TextareaComponent, CoreComponent):
diff --git a/assets/docs/components/toggle.md b/assets/docs/components/toggle.md
index b74459a..c38ce09 100644
--- a/assets/docs/components/toggle.md
+++ b/assets/docs/components/toggle.md
@@ -31,7 +31,7 @@ from .base_ui import PACKAGE_NAME, BaseUIComponent
class ClassNames:
"""Class names for toggle components."""
- ROOT = "group/toggle p-1 inline-flex items-center justify-center gap-1 rounded-radius border border-input text-sm font-medium whitespace-nowrap transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+ ROOT = "group/toggle p-1 inline-flex items-center justify-center gap-1 rounded-lg border border-input text-sm font-medium whitespace-nowrap transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
ICON_VARIANT_CLASSES = {
"fill": "data-[pressed]:[&_svg]:fill-primary",
diff --git a/assets/docs/components/tooltip.md b/assets/docs/components/tooltip.md
index bf94282..edf04f8 100644
--- a/assets/docs/components/tooltip.md
+++ b/assets/docs/components/tooltip.md
@@ -40,7 +40,7 @@ class ClassNames:
"""Class names for tooltip components."""
TRIGGER = "inline-flex items-center justify-center"
- POPUP = "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-radius bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
+ POPUP = "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
ARROW = "data-[side=bottom]:top-[-7.5px] data-[side=left]:right-[-12.5px] data-[side=left]:rotate-90 data-[side=right]:left-[-12.5px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-7.5px] data-[side=top]:rotate-180"
diff --git a/assets/docs/resources/theming.md b/assets/docs/resources/theming.md
index ffb4726..ff0dc26 100644
--- a/assets/docs/resources/theming.md
+++ b/assets/docs/resources/theming.md
@@ -69,7 +69,7 @@ Make sure to pull your CSS files where your tokens are defined in your `rx.App`
app = rx.App(stylesheets=["globals.css"])
```
-Tailwind maps these tokens into utilities like `bg-background`, `text-foreground`, `border-border`, and `rounded-radius`. Dark mode works by overriding the same tokens inside a `.dark` selector.
+Tailwind maps these tokens into utilities like `bg-background`, `text-foreground`, `border-border`, and `rounded-lg`. Dark mode works by overriding the same tokens inside a `.dark` selector.
# Theme Tokens
diff --git a/assets/globals.css b/assets/globals.css
index 9f21ccf..d3a7c77 100644
--- a/assets/globals.css
+++ b/assets/globals.css
@@ -83,6 +83,16 @@ body {
--token-keyword: oklch(0.58 0.22 27);
}
+@theme inline {
+ --radius-sm: calc(var(--radius) * 0.6);
+ --radius-md: calc(var(--radius) * 0.8);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) * 1.4);
+ --radius-2xl: calc(var(--radius) * 1.8);
+ --radius-3xl: calc(var(--radius) * 2.2);
+ --radius-4xl: calc(var(--radius) * 2.6);
+}
+
.toc-link[data-active="true"] {
color: var(--foreground);
font-weight: 500;
diff --git a/components/ui/accordion.py b/components/ui/accordion.py
index e926791..669abc9 100644
--- a/components/ui/accordion.py
+++ b/components/ui/accordion.py
@@ -1,5 +1,3 @@
-"""Custom Accordion component."""
-
from typing import Any, Literal
from reflex.components.component import Component, ComponentNamespace
@@ -32,8 +30,6 @@ class ClassNames:
class AccordionBaseComponent(BaseUIComponent):
- """Base component for accordion components."""
-
library = f"{PACKAGE_NAME}/accordion"
@property
@@ -47,34 +43,24 @@ class AccordionRoot(AccordionBaseComponent):
tag = "Accordion.Root"
- # The uncontrolled value of the item(s) that should be initially expanded. To render a controlled accordion, use the `value` prop instead.
default_value: Var[list[Any]]
- # The controlled value of the item(s) that should be expanded. To render an uncontrolled accordion, use the `default_value` prop instead.
value: Var[list[Any]]
- # Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.
on_value_change: EventHandler[passthrough_event_spec(list[str])]
- # Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False.
hidden_until_found: Var[bool]
- # Whether multiple items can be open at the same time. Defaults to True.
multiple: Var[bool]
- # Whether the component should ignore user interaction. Defaults to False.
disabled: Var[bool]
- # Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Defaults to True.
loop_focus: Var[bool]
- # The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. Defaults to 'vertical'.
orientation: Var[LiteralOrientation]
- # Whether to keep the element in the DOM while the panel is closed. This prop is ignored when hidden_until_found is used. Defaults to False.
keep_mounted: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -86,20 +72,14 @@ def create(cls, *children, **props) -> BaseUIComponent:
class AccordionItem(AccordionBaseComponent):
- """Groups an accordion header with the corresponding panel."""
-
tag = "Accordion.Item"
- # The value that identifies this item.
value: Var[str]
- # Event handler called when the panel is opened or closed.
on_open_change: EventHandler[passthrough_event_spec(bool)]
- # Whether the component should ignore user interaction. Defaults to False.
disabled: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -111,11 +91,8 @@ def create(cls, *children, **props) -> BaseUIComponent:
class AccordionHeader(AccordionBaseComponent):
- """A heading that labels the corresponding panel."""
-
tag = "Accordion.Header"
- # The render prop.
render_: Var[Component]
@classmethod
@@ -127,13 +104,13 @@ def create(cls, *children, **props) -> BaseUIComponent:
class AccordionTrigger(AccordionBaseComponent):
- """A button that opens and closes the corresponding panel."""
-
tag = "Accordion.Trigger"
title: Var[str]
+
native_button: Var[bool]
- render_: Var[Component] = None
+
+ render_: Var[Component] | None = None
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
@@ -172,17 +149,12 @@ def create(cls, *children, **props) -> BaseUIComponent:
class AccordionPanel(AccordionBaseComponent):
- """A collapsible panel with the accordion item contents."""
-
tag = "Accordion.Panel"
- # Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False.
hidden_until_found: Var[bool]
- # Whether to keep the element in the DOM while the panel is closed. This prop is ignored when `hidden_until_found` is used. Defaults to False.
keep_mounted: Var[bool]
- # The render prop.
render_: Var[Component]
@classmethod
@@ -193,132 +165,13 @@ def create(cls, *children, **props) -> BaseUIComponent:
return super().create(*children, **props)
-class HighLevelAccordion(AccordionRoot):
- """High level wrapper for the Accordion component."""
-
- items: Var[ITEMS_TYPE] | ITEMS_TYPE
-
- _item_props = {"on_open_change", "disabled"}
- _trigger_props = {"native_button"}
- _panel_props = {"hidden_until_found", "keep_mounted"}
-
- @classmethod
- def create(
- cls,
- items: Var[ITEMS_TYPE] | ITEMS_TYPE,
- **props,
- ) -> BaseUIComponent:
- """Create a high level accordion component.
-
- Args:
- items: List of dictionaries with 'trigger', 'content', and optional 'value' and 'disabled' keys.
- **props: Additional properties to apply to the accordion component.
-
- Returns:
- The accordion component with all necessary subcomponents.
- """
- # Extract props for different parts
- item_props = {k: props.pop(k) for k in cls._item_props & props.keys()}
- trigger_props = {k: props.pop(k) for k in cls._trigger_props & props.keys()}
- panel_props = {k: props.pop(k) for k in cls._panel_props & props.keys()}
-
- if isinstance(items, Var):
- accordion_items = foreach(
- items,
- lambda item: cls._create_accordion_item_dynamic(
- item, item_props, trigger_props, panel_props
- ),
- )
- return AccordionRoot.create(accordion_items, **props)
- accordion_items = [
- cls._create_accordion_item(
- item, index, item_props, trigger_props, panel_props
- )
- for index, item in enumerate(items)
- ]
- return AccordionRoot.create(*accordion_items, **props)
-
- @classmethod
- def _create_trigger_icon(cls) -> Component:
- """Create the accordion trigger icon."""
- return icon(
- "PlusSignIcon",
- class_name=ClassNames.TRIGGER_ICON,
- data_slot="accordion-trigger-icon",
- )
-
- @classmethod
- def _create_accordion_item(
- cls,
- item: dict[str, str | Component],
- index: int,
- item_props: dict,
- trigger_props: dict,
- panel_props: dict,
- ) -> BaseUIComponent:
- """Create a single accordion item from a dictionary (for normal lists)."""
- return AccordionItem.create(
- AccordionHeader.create(
- AccordionTrigger.create(
- item.get("trigger"),
- cls._create_trigger_icon(),
- **trigger_props,
- ),
- ),
- AccordionPanel.create(
- Div.create(
- item.get("content"),
- class_name=ClassNames.PANEL_DIV,
- data_slot="accordion-panel-div",
- ),
- **panel_props,
- ),
- value=item.get("value", f"item-{index + 1}"),
- disabled=item.get("disabled", False),
- **item_props,
- )
-
- @classmethod
- def _create_accordion_item_dynamic(
- cls,
- item: ObjectVar[dict[str, str | Component]],
- item_props: dict,
- trigger_props: dict,
- panel_props: dict,
- ) -> BaseUIComponent:
- """Create a single accordion item from a dictionary (for Var items)."""
- return AccordionItem.create(
- AccordionHeader.create(
- AccordionTrigger.create(
- item["trigger"],
- cls._create_trigger_icon(),
- **trigger_props,
- ),
- ),
- AccordionPanel.create(
- Div.create(
- item["content"],
- class_name=ClassNames.PANEL_DIV,
- data_slot="accordion-panel-div",
- ),
- **panel_props,
- ),
- value=item.get("value", ""),
- disabled=item.get("disabled", False).bool(),
- **item_props,
- )
-
-
class Accordion(ComponentNamespace):
- """Namespace for Accordion components."""
-
root = staticmethod(AccordionRoot.create)
item = staticmethod(AccordionItem.create)
header = staticmethod(AccordionHeader.create)
trigger = staticmethod(AccordionTrigger.create)
panel = staticmethod(AccordionPanel.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelAccordion.create)
accordion = Accordion()
diff --git a/components/ui/attachment.py b/components/ui/attachment.py
index c9c22d8..3704269 100644
--- a/components/ui/attachment.py
+++ b/components/ui/attachment.py
@@ -1,5 +1,3 @@
-"""Attachment component — file and image attachment previews for chat UI."""
-
from typing import Literal
import reflex as rx
@@ -24,7 +22,7 @@ class ClassNames:
"data-[state=idle]:border-dashed"
)
- SIZES: dict[str, str] = {
+ SIZES = {
"default": (
"gap-2 text-sm "
"has-data-[slot=attachment-content]:px-2.5 "
@@ -45,7 +43,7 @@ class ClassNames:
),
}
- ORIENTATIONS: dict[str, str] = {
+ ORIENTATIONS = {
"horizontal": "min-w-40 items-center",
"vertical": "w-24 flex-col has-data-[slot=attachment-content]:w-30",
}
@@ -65,7 +63,7 @@ class ClassNames:
"group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5"
)
- MEDIA_VARIANTS: dict[str, str] = {
+ MEDIA_VARIANTS = {
"icon": "",
"image": (
"opacity-60 "
@@ -117,13 +115,7 @@ def attachment_root(
class_name: str = "",
**props,
) -> rx.Component:
- """
- Root attachment container.
- orientation: horizontal (default) | vertical
- size: default | sm | xs
- state: idle | uploading | processing | error | done (default)
- """
return rx.el.div(
*children,
data_slot="attachment",
@@ -146,7 +138,7 @@ def attachment_media(
class_name: str = "",
**props,
) -> rx.Component:
- """Icon or image preview slot."""
+
return rx.el.div(
*children,
data_slot="attachment-media",
@@ -161,7 +153,7 @@ def attachment_media(
def attachment_content(*children, class_name: str = "", **props) -> rx.Component:
- """Text content area — holds title and description."""
+
return rx.el.div(
*children,
data_slot="attachment-content",
@@ -171,7 +163,7 @@ def attachment_content(*children, class_name: str = "", **props) -> rx.Component
def attachment_title(*children, class_name: str = "", **props) -> rx.Component:
- """Filename or attachment title. Shimmers during uploading/processing."""
+
return rx.el.span(
*children,
data_slot="attachment-title",
@@ -181,7 +173,7 @@ def attachment_title(*children, class_name: str = "", **props) -> rx.Component:
def attachment_description(*children, class_name: str = "", **props) -> rx.Component:
- """File type, size, or other metadata."""
+
return rx.el.span(
*children,
data_slot="attachment-description",
@@ -191,10 +183,7 @@ def attachment_description(*children, class_name: str = "", **props) -> rx.Compo
def attachment_actions(*children, class_name: str = "", **props) -> rx.Component:
- """
- Row of action buttons.
- In vertical orientation, absolutely positioned top-right.
- """
+
return rx.el.div(
*children,
data_slot="attachment-actions",
@@ -204,9 +193,10 @@ def attachment_actions(*children, class_name: str = "", **props) -> rx.Component
def attachment_action(*children, class_name: str = "", **props) -> rx.Component:
- """Individual action button."""
props.setdefault("variant", "ghost")
+
props.setdefault("size", "icon-xs")
+
return button(
*children,
data_slot="attachment-action",
@@ -231,10 +221,7 @@ def attachment_trigger(
def attachment_group(*children, class_name: str = "", **props) -> rx.Component:
- """
- Horizontal scrolling row of attachments.
- Snaps to each attachment on scroll.
- """
+
return rx.el.div(
*children,
data_slot="attachment-group",
@@ -244,8 +231,6 @@ def attachment_group(*children, class_name: str = "", **props) -> rx.Component:
class Attachment(ComponentNamespace):
- """Attachment namespace."""
-
root = staticmethod(attachment_root)
media = staticmethod(attachment_media)
content = staticmethod(attachment_content)
diff --git a/components/ui/autocomplete.py b/components/ui/autocomplete.py
index 0c932f8..bd09043 100644
--- a/components/ui/autocomplete.py
+++ b/components/ui/autocomplete.py
@@ -1,5 +1,3 @@
-"""Custom autocomplete component with flat and grouped item support."""
-
from typing import Any
from reflex.components.component import Component
@@ -396,23 +394,35 @@ class ClassNames:
class BuridanAutocomplete(Component):
tag = "BuridanAutocompleteRoot"
+
is_default = False
items: Var[Any]
+
placeholder: Var[str]
+
root_class: Var[str]
+
input_class: Var[str]
+
popup_class: Var[str]
+
list_class: Var[str]
+
item_class: Var[str]
+
item_highlighted_class: Var[str]
+
empty_class: Var[str]
+
group_label_class: Var[str]
+
separator_class: Var[str]
set_query_on_select: Var[bool] = True
on_change_query: EventHandler[passthrough_event_spec(str)]
+
on_select_item: EventHandler[passthrough_event_spec(str)]
def add_custom_code(self) -> list[str]:
diff --git a/components/ui/avatar.py b/components/ui/avatar.py
index 460bf5d..99460f2 100644
--- a/components/ui/avatar.py
+++ b/components/ui/avatar.py
@@ -1,126 +1,125 @@
-"""Custom avatar component."""
-
from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventHandler, passthrough_event_spec
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var
+from reflex_components_core.el import Div, Span
+from ..utils.twmerge import cn
from .base_ui import PACKAGE_NAME, BaseUIComponent
+from .component import CoreComponent
class ClassNames:
- """Class names for avatar components."""
-
- ROOT = "shrink-0 inline-flex size-8 items-center justify-center overflow-hidden rounded-full align-middle text-base font-medium select-none"
- IMAGE = "size-full object-cover shrink-0"
- FALLBACK = "flex size-full items-center justify-center text-sm bg-muted"
+ ROOT = "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=xl]:size-14 data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten"
+ IMAGE = "aspect-square size-full rounded-full object-cover"
+ FALLBACK = "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs"
+ BADGE = "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2"
+ GROUP = "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background"
+ GROUP_COUNT = "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3"
class AvatarBaseComponent(BaseUIComponent):
- """Base component for avatar components."""
-
library = f"{PACKAGE_NAME}/avatar"
@property
def import_var(self):
- """Return the import variable for the avatar component."""
return ImportVar(tag="Avatar", package_path="", install=False)
class AvatarRoot(AvatarBaseComponent):
- """Displays a user's profile picture, initials, or fallback icon."""
-
tag = "Avatar.Root"
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar root component."""
+ custom_classes = props.pop("class_name", "")
+ size = props.pop("size", "default")
props["data-slot"] = "avatar"
- cls.set_class_name(ClassNames.ROOT, props)
- return super().create(*children, **props)
+ props["data-size"] = size
+
+ return super().create(
+ *children, class_name=cn(ClassNames.ROOT, custom_classes), **props
+ )
class AvatarImage(AvatarBaseComponent):
- """The image to be displayed in the avatar."""
-
tag = "Avatar.Image"
-
- # The image source URL
src: Var[str]
-
- # Callback when loading status changes
on_loading_status_change: EventHandler[passthrough_event_spec(str)]
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar image component."""
+ custom_classes = props.pop("class_name", "")
props["data-slot"] = "avatar-image"
- cls.set_class_name(ClassNames.IMAGE, props)
- return super().create(*children, **props)
+ return super().create(
+ *children, class_name=cn(ClassNames.IMAGE, custom_classes), **props
+ )
-class AvatarFallback(AvatarBaseComponent):
- """Rendered when the image fails to load or when no image is provided."""
+class AvatarFallback(AvatarBaseComponent):
tag = "Avatar.Fallback"
-
- # How long to wait before showing the fallback. Specified in milliseconds
delay: Var[int]
-
- # The component to render
render_: Var[Component]
@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar fallback component."""
+ custom_classes = props.pop("class_name", "")
props["data-slot"] = "avatar-fallback"
- cls.set_class_name(ClassNames.FALLBACK, props)
- return super().create(*children, **props)
+ return super().create(
+ *children,
+ class_name=cn(ClassNames.FALLBACK, custom_classes),
+ **props,
+ )
-class HighLevelAvatar(AvatarRoot):
- """High level wrapper for the Avatar component."""
- # The image source URL
- src: Var[str]
+class AvatarBadge(Span, CoreComponent):
+ @classmethod
+ def create(cls, *children, **props) -> Span:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-badge"
+
+ return super().create(
+ *children, class_name=cn(ClassNames.BADGE, custom_classes), **props
+ )
- # Image props
- _image_props = {"src", "on_loading_status_change", "render_"}
- # Fallback props
- _fallback_props = {"delay"}
+class AvatarGroup(Div, CoreComponent):
+ """The flex container wrapper for grouping multiple avatars."""
@classmethod
- def create(cls, *children, **props) -> BaseUIComponent:
- """Create the avatar component."""
- # Extract props for each subcomponent
- image_props = {k: props.pop(k) for k in cls._image_props & props.keys()}
- fallback_props = {k: props.pop(k) for k in cls._fallback_props & props.keys()}
+ def create(cls, *children, **props) -> Div:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-group"
+
+ return super().create(
+ *children, class_name=cn(ClassNames.GROUP, custom_classes), **props
+ )
- fallback_content = props.pop("fallback", "")
- return AvatarRoot.create(
- AvatarImage.create(**image_props),
- AvatarFallback.create(fallback_content, **fallback_props),
+class AvatarGroupCount(Div, CoreComponent):
+ @classmethod
+ def create(cls, *children, **props) -> Div:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "avatar-group-count"
+
+ return super().create(
*children,
+ class_name=cn(ClassNames.GROUP_COUNT, custom_classes),
**props,
)
class Avatar(ComponentNamespace):
- """Namespace for Avatar components."""
-
root = staticmethod(AvatarRoot.create)
image = staticmethod(AvatarImage.create)
fallback = staticmethod(AvatarFallback.create)
+ badge = staticmethod(AvatarBadge.create)
+ group = staticmethod(AvatarGroup.create)
+ group_count = staticmethod(AvatarGroupCount.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelAvatar.create)
avatar = Avatar()
diff --git a/components/ui/badge.py b/components/ui/badge.py
index 5e1a13c..5c5496f 100644
--- a/components/ui/badge.py
+++ b/components/ui/badge.py
@@ -3,86 +3,54 @@
from reflex.vars.base import Var
from reflex_components_core.el import Span
+from ..utils.twmerge import cn
from .component import CoreComponent
-LiteralBadgeVariant = Literal["default", "secondary", "destructive", "outline"]
+LiteralBadgeVariant = Literal[
+ "default", "secondary", "destructive", "outline", "ghost", "link"
+]
DEFAULT_BASE_CLASSES = (
- "inline-flex items-center justify-center rounded-radius border px-2 py-0.5 text-xs font-medium "
- "w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none "
- "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] "
- "aria-invalid:ring-[var(--destructive)]/20 dark:aria-invalid:ring-[var(--destructive)]/40 "
- "aria-invalid:border-[var(--destructive)] transition-[color,box-shadow] overflow-hidden"
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden "
+ "rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap "
+ "transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive "
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!"
)
BADGE_VARIANTS = {
- "default": (
- "border-transparent bg-[var(--primary)] text-[var(--primary-foreground)] "
- "[a&]:hover:bg-[var(--primary)]/90"
- ),
- "secondary": (
- "border-transparent bg-[var(--secondary)] text-[var(--secondary-foreground)] "
- "[a&]:hover:bg-[var(--secondary)]/90"
- ),
+ "default": "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
+ "secondary": "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
"destructive": (
- "border-transparent bg-[var(--destructive)] text-white "
- "[a&]:hover:bg-[var(--destructive)]/90 "
- "focus-visible:ring-[var(--destructive)]/20 dark:focus-visible:ring-[var(--destructive)]/40 "
- "dark:bg-[var(--destructive)]/60"
- ),
- "outline": (
- "text-[var(--foreground)] border-[var(--input)] "
- "[a&]:hover:bg-[var(--accent)] [a&]:hover:text-[var(--accent-foreground)]"
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 "
+ "dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20"
),
+ "outline": "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
+ "ghost": "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ "link": "text-primary underline-offset-4 hover:underline",
}
-def get_badge_classes(variant: LiteralBadgeVariant) -> str:
- """Get the complete badge class string.
-
- Args:
- variant: The badge variant to apply
-
- Returns:
- The complete class string for the badge
- """
- variant_classes = BADGE_VARIANTS[variant]
- return f"{DEFAULT_BASE_CLASSES} {variant_classes}"
+def badge_variants(variant: str = "default") -> Var:
+ return cn(
+ DEFAULT_BASE_CLASSES,
+ BADGE_VARIANTS.get(variant, BADGE_VARIANTS["default"]),
+ )
class Badge(Span, CoreComponent):
- """A badge component that displays a label."""
-
- # Badge variant
- variant: Var[LiteralBadgeVariant]
-
@classmethod
def create(cls, *children, **props) -> Span:
- """Create the badge component.
-
- Args:
- *children: The badge content
- **props: Component properties including variant
-
- Returns:
- A configured Span component
- """
+ custom_classes = props.pop("class_name", "")
variant = props.pop("variant", "default")
-
- cls.set_class_name(get_badge_classes(variant), props)
-
- # Add data-slot attribute
- props.setdefault("data_slot", "badge")
-
- return super().create(*children, **props)
-
- def _exclude_props(self) -> list[str]:
- """Exclude component-specific props from being passed to the DOM.
-
- Returns:
- List of prop names to exclude
- """
- return [*super()._exclude_props(), "variant"]
+ props["data-slot"] = "badge"
+ props["data-variant"] = variant
+
+ return super().create(
+ *children,
+ class_name=cn(badge_variants(variant), custom_classes),
+ **props,
+ )
badge = Badge.create
diff --git a/components/ui/breadcrumb.py b/components/ui/breadcrumb.py
index 099a435..a4069b8 100644
--- a/components/ui/breadcrumb.py
+++ b/components/ui/breadcrumb.py
@@ -1,95 +1,135 @@
import reflex as rx
+from ..icons.hugeicon import hi
+from ..utils.twmerge import cn
-def breadcrumb(*children, **props):
- """Breadcrumb navigation container"""
- return rx.el.nav(
- *children, aria_label="breadcrumb", data_slot="breadcrumb", **props
- )
-
-
-def breadcrumb_list(*children, class_name: str = "", **props):
- """Ordered list container for breadcrumb items"""
- base_classes = (
- "text-[var(--muted-foreground)] flex flex-wrap items-center gap-1 text-sm "
- "break-words sm:gap-2.5"
- )
-
- return rx.el.ol(
- *children,
- data_slot="breadcrumb-list",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
-
-
-def breadcrumb_item(*children, class_name: str = "", **props):
- """Individual breadcrumb item"""
- base_classes = "inline-flex items-center gap-1.5"
-
- return rx.el.li(
- *children,
- data_slot="breadcrumb-item",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
-
-
-def breadcrumb_link(*children, href: str = "#", class_name: str = "", **props):
- """Breadcrumb link (clickable)"""
- base_classes = "hover:text-[var(--foreground)] transition-colors no-underline"
-
- return rx.el.a(
- *children,
- href=href,
- data_slot="breadcrumb-link",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
-
-
-def breadcrumb_page(*children, class_name: str = "", **props):
- """Current page breadcrumb (non-clickable)"""
- base_classes = "text-[var(--foreground)] font-normal"
-
- return rx.el.span(
- *children,
- role="link",
- aria_disabled="true",
- aria_current="page",
- data_slot="breadcrumb-page",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
-
-
-def breadcrumb_separator(*children, class_name: str = "", **props):
- """Separator between breadcrumb items"""
- base_classes = "[&>svg]:size-3.5"
-
- if not children:
- children = (rx.icon(tag="chevron-right", size=14),)
-
- return rx.el.li(
- *children,
- role="presentation",
- aria_hidden="true",
- data_slot="breadcrumb-separator",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
-
-
-def breadcrumb_ellipsis(class_name: str = "", **props):
- """Ellipsis for collapsed breadcrumb items"""
- base_classes = "flex size-9 items-center justify-center"
-
- return rx.el.span(
- rx.icon(tag="ellipsis", size=16),
- rx.el.span("More", class_name="sr-only"),
- role="presentation",
- aria_hidden="true",
- data_slot="breadcrumb-ellipsis",
- class_name=f"{base_classes} {class_name}".strip(),
- **props,
- )
+
+class Breadcrumb:
+ @classmethod
+ def root(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["aria-label"] = "breadcrumb"
+
+ props["data-slot"] = "breadcrumb"
+
+ return rx.el.nav(
+ *children,
+ class_name=cn(custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def list(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-list"
+
+ base_classes = (
+ "flex flex-wrap items-center gap-1.5 text-sm wrap-break-word "
+ "text-muted-foreground"
+ )
+
+ return rx.el.ol(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def item(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-item"
+
+ base_classes = "inline-flex items-center gap-1"
+
+ return rx.el.li(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def link(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-link"
+
+ props.setdefault("href", "#")
+
+ base_classes = "transition-colors hover:text-foreground no-underline"
+
+ return rx.el.a(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def page(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-page"
+
+ props["role"] = "link"
+
+ props["aria-disabled"] = "true"
+
+ props["aria-current"] = "page"
+
+ base_classes = "font-normal text-foreground"
+
+ return rx.el.span(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def separator(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-separator"
+
+ props["role"] = "presentation"
+
+ props["aria-hidden"] = "true"
+
+ base_classes = "[&>svg]:size-3.5"
+
+ if not children:
+ children = (hi("ArrowRight01Icon"),)
+
+ return rx.el.li(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+ @classmethod
+ def ellipsis(cls, *children, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ props["data-slot"] = "breadcrumb-ellipsis"
+
+ props["role"] = "presentation"
+
+ props["aria-hidden"] = "true"
+
+ base_classes = "flex size-5 items-center justify-center [&>svg]:size-4"
+
+ if not children:
+ children = (
+ rx.icon(tag="ellipsis"),
+ rx.el.span("More", class_name="sr-only"),
+ )
+
+ return rx.el.span(
+ *children,
+ class_name=cn(base_classes, custom_classes),
+ **props,
+ )
+
+
+breadcrumb = Breadcrumb
diff --git a/components/ui/button.py b/components/ui/button.py
index 9fa7a89..07b0f55 100644
--- a/components/ui/button.py
+++ b/components/ui/button.py
@@ -1,121 +1,139 @@
from typing import Literal
from reflex.vars.base import Var
-from reflex_components_core.core import cond
from reflex_components_core.el import Button as BaseButton
-from ..icons.others import spinner
+from ..utils.twmerge import cn
from .component import CoreComponent
LiteralButtonVariant = Literal[
- "primary", "destructive", "outline", "secondary", "ghost", "link", "dark"
+ "default",
+ "destructive",
+ "outline",
+ "secondary",
+ "ghost",
+ "link",
]
LiteralButtonSize = Literal[
- "default", "xs", "sm", "lg", "icon", "icon-xs", "icon-sm", "icon-lg"
+ "default",
+ "xs",
+ "sm",
+ "lg",
+ "icon",
+ "icon-xs",
+ "icon-sm",
+ "icon-lg",
]
DEFAULT_CLASS_NAME = (
- "font-theme inline-flex items-center justify-center gap-2 whitespace-nowrap "
- "rounded-radius text-sm font-medium transition-all "
- "disabled:pointer-events-none disabled:opacity-50 outline-none "
- "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 "
- "[&_svg]:shrink-0 shrink-0"
+ "group/button inline-flex shrink-0 items-center justify-center "
+ "rounded-lg border border-transparent bg-clip-padding "
+ "text-sm font-medium whitespace-nowrap transition-all outline-none select-none "
+ "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 "
+ "active:not-aria-[haspopup]:translate-y-px "
+ "disabled:pointer-events-none disabled:opacity-50 "
+ "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 "
+ "dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 "
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 "
+ "[&_svg:not([class*='size-'])]:size-4"
)
BUTTON_VARIANTS = {
"variant": {
- "default": "bg-primary text-primary-foreground hover:bg-primary/90",
+ "default": ("bg-primary text-primary-foreground hover:bg-primary/80"),
+ "outline": (
+ "border-border bg-background hover:bg-muted hover:text-foreground "
+ "aria-expanded:bg-muted aria-expanded:text-foreground "
+ "dark:border-input dark:bg-input/30 dark:hover:bg-input/50"
+ ),
+ "secondary": (
+ "bg-secondary text-secondary-foreground "
+ "hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] "
+ "aria-expanded:bg-secondary aria-expanded:text-secondary-foreground"
+ ),
+ "ghost": (
+ "hover:bg-muted hover:text-foreground "
+ "aria-expanded:bg-muted aria-expanded:text-foreground "
+ "dark:hover:bg-muted/50"
+ ),
"destructive": (
"bg-destructive/10 text-destructive hover:bg-destructive/20 "
"focus-visible:border-destructive/40 focus-visible:ring-destructive/20 "
- "dark:bg-destructive/20 dark:hover:bg-destructive/30"
- ),
- "outline": (
- "border border-input bg-background shadow-xs text-foreground "
- "hover:bg-accent hover:text-accent-foreground "
- "dark:bg-input/30 dark:hover:bg-input/50"
+ "dark:bg-destructive/20 dark:hover:bg-destructive/30 "
+ "dark:focus-visible:ring-destructive/40"
),
- "secondary": "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- "ghost": "hover:bg-accent hover:text-accent-foreground dark:hover:bg-muted/50",
"link": "text-primary underline-offset-4 hover:underline",
},
"size": {
- "default": "h-8 gap-1.5 px-2.5 has-[>svg]:px-2.5",
- "xs": "h-6 rounded-[min(calc(var(--radius) * 0.8),10px)] gap-1 px-2 text-xs",
- "sm": "h-7 rounded-[min(calc(var(--radius) * 0.8),12px)] gap-1 px-2.5 text-[0.8rem]",
- "lg": "h-9 gap-1.5 px-2.5",
+ "default": (
+ "h-8 gap-1.5 px-2.5 "
+ "has-data-[icon=inline-end]:pr-2 "
+ "has-data-[icon=inline-start]:pl-2"
+ ),
+ "xs": (
+ "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs "
+ "in-data-[slot=button-group]:rounded-lg "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 "
+ "[&_svg:not([class*='size-'])]:size-3"
+ ),
+ "sm": (
+ "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] "
+ "in-data-[slot=button-group]:rounded-lg "
+ "has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 "
+ "[&_svg:not([class*='size-'])]:size-3.5"
+ ),
+ "lg": (
+ "h-9 gap-1.5 px-2.5 "
+ "has-data-[icon=inline-end]:pr-2 "
+ "has-data-[icon=inline-start]:pl-2"
+ ),
"icon": "size-8",
- "icon-xs": "size-6 rounded-[min(calc(var(--radius) * 0.8),10px)]",
- "icon-sm": "size-7 rounded-[min(calc(var(--radius) * 0.8),12px)]",
+ "icon-xs": (
+ "size-6 rounded-[min(var(--radius-md),10px)] "
+ "in-data-[slot=button-group]:rounded-lg "
+ "[&_svg:not([class*='size-'])]:size-3"
+ ),
+ "icon-sm": (
+ "size-7 rounded-[min(var(--radius-md),12px)] "
+ "in-data-[slot=button-group]:rounded-lg"
+ ),
"icon-lg": "size-9",
},
}
class Button(BaseButton, CoreComponent):
- """A custom button component."""
-
- # Button variant. Defaults to "primary".
variant: Var[LiteralButtonVariant]
-
- # Button size. Defaults to "md".
size: Var[LiteralButtonSize]
- # The loading state of the button
- loading: Var[bool]
-
@classmethod
def create(cls, *children, **props) -> BaseButton:
- """Create the button component."""
variant = props.pop("variant", "default")
- cls.validate_variant(variant)
-
size = props.pop("size", "default")
- cls.validate_size(size)
-
- loading = props.pop("loading", False)
- disabled = props.pop("disabled", False)
-
- button_classes = f"{DEFAULT_CLASS_NAME} {BUTTON_VARIANTS['variant'][variant]} {BUTTON_VARIANTS['size'][size]}"
+ custom_classes = props.pop("class_name", "")
+
+ return super().create(
+ *children,
+ data_slot="button",
+ class_name=cn(
+ DEFAULT_CLASS_NAME,
+ BUTTON_VARIANTS["variant"].get(variant, ""),
+ BUTTON_VARIANTS["size"].get(size, ""),
+ custom_classes,
+ ),
+ **props,
+ )
- cls.set_class_name(button_classes, props)
-
- children_list = list(children)
-
- if isinstance(loading, Var):
- props["disabled"] = cond(loading, True, disabled)
- children_list.insert(0, cond(loading, spinner()))
- else:
- props["disabled"] = True if loading else disabled
- children_list.insert(0, spinner()) if loading else None
-
- return super().create(*children_list, **props)
-
- @staticmethod
- def validate_variant(variant: LiteralButtonVariant):
- """Validate the button variant."""
- if variant not in BUTTON_VARIANTS["variant"]:
- available_variants = ", ".join(BUTTON_VARIANTS["variant"].keys())
- message = (
- f"Invalid variant: {variant}. Available variants: {available_variants}"
- )
- raise ValueError(message)
+ def _exclude_props(self) -> list[str]:
+ return [*super()._exclude_props(), "size", "variant"]
- @staticmethod
- def validate_size(size: LiteralButtonSize):
- """Validate the button size."""
- if size not in BUTTON_VARIANTS["size"]:
- available_sizes = ", ".join(BUTTON_VARIANTS["size"].keys())
- message = f"Invalid size: {size}. Available sizes: {available_sizes}"
- raise ValueError(message)
- def _exclude_props(self) -> list[str]:
- return [
- *super()._exclude_props(),
- "size",
- "variant",
- "loading",
- ]
+def button_variants(variant: str = "default", size: str = "default") -> Var:
+ return cn(
+ DEFAULT_CLASS_NAME,
+ BUTTON_VARIANTS["variant"].get(variant, ""),
+ BUTTON_VARIANTS["size"].get(size, ""),
+ )
button = Button.create
diff --git a/components/ui/component.py b/components/ui/component.py
index 5fba2bc..2592910 100644
--- a/components/ui/component.py
+++ b/components/ui/component.py
@@ -11,7 +11,6 @@
class CoreComponent(Component):
"""Core component for all components."""
- # Whether the component should be unstyled
unstyled: Var[bool]
@classmethod
diff --git a/components/ui/input.py b/components/ui/input.py
index c0ac43a..b3a543e 100644
--- a/components/ui/input.py
+++ b/components/ui/input.py
@@ -5,7 +5,7 @@
"w-full file:text-foreground placeholder:text-muted-foreground "
"selection:bg-primary selection:text-primary-foreground "
"dark:bg-input/30 border-input "
- "h-9 w-full min-w-0 rounded-radius border bg-transparent px-3 py-1 text-base "
+ "h-9 w-full min-w-0 rounded-lg border bg-transparent px-3 py-1 text-base "
"transition-[color,box-shadow] outline-none "
"file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium "
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 "
diff --git a/components/ui/menu.py b/components/ui/menu.py
index 364ad3e..3138bbc 100644
--- a/components/ui/menu.py
+++ b/components/ui/menu.py
@@ -1,18 +1,12 @@
-"""Custom menu component."""
-
from typing import Literal
from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventHandler, passthrough_event_spec
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var
-from reflex_components_core.core.foreach import foreach
from ..icons.hugeicon import hi
-from ..icons.others import select_arrow
-from ..utils.twmerge import cn
from .base_ui import PACKAGE_NAME, BaseUIComponent
-from .button import button
LiteralOpenChangeReason = Literal[
"arrowKey",
@@ -40,7 +34,7 @@ class ClassNames:
POPUP = (
"z-50 max-h-[var(--available-height)] w-[var(--anchor-width)] min-w-32 "
"origin-[var(--transform-origin)] overflow-x-hidden overflow-y-auto "
- "rounded-radius bg-popover p-1 text-popover-foreground shadow-md "
+ "rounded-lg bg-popover p-1 text-popover-foreground shadow-md "
"ring-1 ring-foreground/10 duration-100 outline-none "
"data-[side=bottom]:slide-in-from-top-2 "
"data-[side=left]:slide-in-from-right-2 "
@@ -51,7 +45,7 @@ class ClassNames:
)
ITEM = (
"group/menu-item relative flex cursor-default items-center gap-1.5 "
- "rounded-radius px-1.5 py-1 text-sm outline-hidden select-none "
+ "rounded-lg px-1.5 py-1 text-sm outline-hidden select-none "
"focus:bg-accent focus:text-accent-foreground "
"not-data-[variant=destructive]:focus:**:text-accent-foreground "
"data-inset:pl-7 "
@@ -67,7 +61,7 @@ class ClassNames:
POSITIONER_SUB = "isolate z-50 outline-none"
POPUP_SUB = (
"z-50 w-auto min-w-24 origin-(--transform-origin) overflow-x-hidden "
- "overflow-y-auto rounded-radius bg-popover p-1 text-popover-foreground "
+ "overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground "
"shadow-lg ring-1 ring-foreground/10 duration-100 "
"data-[side=bottom]:slide-in-from-top-2 "
"data-[side=left]:slide-in-from-right-2 "
@@ -80,7 +74,7 @@ class ClassNames:
"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7"
)
SUBMENU_TRIGGER = (
- "flex cursor-default items-center justify-between gap-1.5 rounded-radius px-1.5 py-1 "
+ "flex cursor-default items-center justify-between gap-1.5 rounded-lg px-1.5 py-1 "
"text-sm outline-hidden select-none "
"focus:bg-accent focus:text-accent-foreground "
"not-data-[variant=destructive]:focus:**:text-accent-foreground "
@@ -296,18 +290,6 @@ def create(cls, *children, **props) -> BaseUIComponent:
return super().create(
*children,
**props,
- # style={
- # "keyframes": {
- # "animate-in": {
- # "from": {"opacity": "0", "transform": "scale(0.95)"},
- # "to": {"opacity": "1", "transform": "scale(1)"},
- # },
- # "animate-out": {
- # "from": {"opacity": "1", "transform": "scale(2)"},
- # "to": {"opacity": "0", "transform": "scale(0.95)"},
- # },
- # },
- # },
)
@@ -618,133 +600,6 @@ def create(cls, *children, **props) -> BaseUIComponent:
return super().create(*children, **props)
-class HighLevelMenu(MenuRoot):
- """High level wrapper for the Menu component."""
-
- # The trigger component to use for the menu
- trigger: Var[Component | None]
-
- # The list of items to display in the menu dropdown - can be strings or tuples of (label, on_click_handler)
- items: Var[list[str | tuple[str, EventHandler]]]
-
- # The placeholder text to display when no item is selected
- placeholder: Var[str]
-
- # The size of the menu. Defaults to "md".
- size: Var[LiteralMenuSize]
-
- # Whether to close the menu when the item is clicked. Defaults to True.
- close_on_click: Var[bool]
-
- # Props for different component parts
- _item_props = {"close_on_click"}
- _trigger_props = {"placeholder", "size", "close_on_click"}
- _positioner_props = {
- "align",
- "align_offset",
- "side",
- "arrow_padding",
- "collision_padding",
- "sticky",
- "position_method",
- "track_anchor",
- "side_offset",
- "collision_avoidance",
- }
- _portal_props = {"container"}
-
- @classmethod
- def create(cls, *children, **props) -> BaseUIComponent:
- """Create a menu component.
-
- Args:
- *children: Additional children to include in the menu.
- **props: Additional properties to apply to the menu component.
-
- Returns:
- The menu component.
- """
- # Extract props for different parts
- item_props = {k: props.pop(k) for k in cls._item_props & props.keys()}
- trigger_props = {k: props.pop(k) for k in cls._trigger_props & props.keys()}
- positioner_props = {
- k: props.pop(k) for k in cls._positioner_props & props.keys()
- }
- portal_props = {k: props.pop(k) for k in cls._portal_props & props.keys()}
-
- trigger = props.pop("trigger", None)
- items = props.pop("items", [])
- size = trigger_props.get("size", "md")
- trigger_label = trigger_props.get("placeholder", "Open Menu")
-
- def create_menu_item(item: str | tuple[str, EventHandler]) -> BaseUIComponent:
- if isinstance(item, tuple):
- label, on_click_handler = item
- return MenuItem.create(
- render_=button(
- label,
- variant="ghost",
- class_name=ClassNames.ITEM,
- disabled=props.get("disabled", False),
- on_click=on_click_handler,
- size=size,
- type="button",
- ),
- key=label,
- **item_props,
- )
- return MenuItem.create(
- render_=button(
- item,
- variant="ghost",
- class_name=ClassNames.ITEM,
- disabled=props.get("disabled", False),
- size=size,
- type="button",
- ),
- key=item,
- **item_props,
- )
-
- if isinstance(items, Var):
- items_children = foreach(items, create_menu_item)
- else:
- items_children = [create_menu_item(item) for item in items]
-
- return MenuRoot.create(
- MenuTrigger.create(
- render_=(
- trigger
- if trigger
- else button(
- trigger_label,
- select_arrow(class_name="size-4 text-secondary-9"),
- variant="outline",
- class_name=ClassNames.TRIGGER,
- disabled=props.get("disabled", False),
- size=size,
- type="button",
- )
- ),
- ),
- MenuPortal.create(
- MenuPositioner.create(
- MenuPopup.create(
- items_children,
- class_name=cn(
- ClassNames.POPUP,
- "",
- ),
- ),
- **positioner_props,
- ),
- **portal_props,
- ),
- *children,
- **props,
- )
-
-
class Menu(ComponentNamespace):
"""Namespace for Menu components."""
@@ -766,7 +621,6 @@ class Menu(ComponentNamespace):
submenu_root = staticmethod(MenuSubMenuRoot.create)
submenu_trigger = staticmethod(MenuSubMenuTrigger.create)
class_names = ClassNames
- __call__ = staticmethod(HighLevelMenu.create)
menu = Menu()
diff --git a/components/ui/metric.py b/components/ui/metric.py
index 8c31625..d8e0137 100644
--- a/components/ui/metric.py
+++ b/components/ui/metric.py
@@ -16,9 +16,7 @@
class ClassNames:
"""Class names for metric components."""
- ROOT = (
- "flex flex-col gap-y-2 p-4 rounded-radius border border-input bg-card shadow-sm"
- )
+ ROOT = "flex flex-col gap-y-2 p-4 rounded-lg border border-input bg-card shadow-sm"
LABEL = "text-sm font-medium text-muted-foreground"
VALUE = "text-3xl font-bold tracking-tight text-foreground"
TREND_CONTAINER = "flex flex-row items-center gap-x-1.5"
diff --git a/components/ui/select.py b/components/ui/select.py
index dac0d2a..044a963 100644
--- a/components/ui/select.py
+++ b/components/ui/select.py
@@ -24,7 +24,7 @@
class ClassNames:
"""Class names for select components."""
- TRIGGER = "flex w-fit items-center justify-between gap-1.5 rounded-radius border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+ TRIGGER = "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
VALUE = "flex-1 text-left cursor-default"
ICON = "flex size-4 text-secondary-10 group-data-[disabled]/trigger:text-current"
POPUP = "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
@@ -573,7 +573,7 @@ def create(cls, *children, **props) -> BaseUIComponent:
class_name=cn(
ClassNames.POPUP,
"",
- "rounded-radius",
+ "rounded-lg",
),
),
**positioner_props,
diff --git a/components/ui/slider.py b/components/ui/slider.py
index f639ec9..55a618a 100644
--- a/components/ui/slider.py
+++ b/components/ui/slider.py
@@ -38,19 +38,19 @@ class ClassNames:
)
TRACK = (
- "h-1 w-full rounded-radius bg-secondary select-none "
+ "h-1 w-full rounded-lg bg-secondary select-none "
"data-[orientation=vertical]:h-full "
"data-[orientation=vertical]:w-1"
)
INDICATOR = (
- "absolute h-full rounded-radius bg-primary select-none "
+ "absolute h-full rounded-lg bg-primary select-none "
"data-[orientation=vertical]:w-full "
"data-[orientation=vertical]:h-auto"
)
THUMB = (
- "size-3 rounded-radius bg-white outline-[1px] outline-black "
+ "size-3 rounded-lg bg-white outline-[1px] outline-black "
"select-none box-shadow:[0_0_0_1px_rgba(0,0,0,1),0_1px_2px_rgba(0,0,0,.04)] "
"data-[dragging]:h-5 transition-[height,scale] hover:h-4.5"
)
diff --git a/components/ui/spinner.py b/components/ui/spinner.py
index bc320de..a1ef560 100644
--- a/components/ui/spinner.py
+++ b/components/ui/spinner.py
@@ -7,6 +7,12 @@
def spinner(class_name: str = "", **props) -> rx.Component:
+ incoming_attrs = props.pop("custom_attrs", {})
+
+ base_attrs = {"viewBox": "0 0 16 16"}
+
+ merged_attrs = {**base_attrs, **incoming_attrs}
+
return svg(
svg.path(
opacity="0.2",
@@ -20,7 +26,7 @@ def spinner(class_name: str = "", **props) -> rx.Component:
stroke_width="1.5",
),
xmlns="http://www.w3.org/2000/svg",
- custom_attrs={"viewBox": "0 0 16 16"},
+ custom_attrs=merged_attrs,
class_name=cn("size-4 animate-spin fill-none", class_name),
data_slot="spinner",
role="status",
diff --git a/components/ui/switch.py b/components/ui/switch.py
index 74bc014..3881e1a 100644
--- a/components/ui/switch.py
+++ b/components/ui/switch.py
@@ -11,7 +11,7 @@
class ClassNames:
"""Class names for switch components."""
- ROOT = "relative flex h-5 w-8 rounded-full bg-secondary p-0.5 transition-colors duration-200 ease-out before:absolute before:rounded-radius before:outline-offset-2 before:outline-primary focus-visible:before:inset-0 data-[checked]:bg-primary disabled:opacity-50 disabled:cursor-not-allowed cursor-default"
+ ROOT = "relative flex h-5 w-8 rounded-full bg-secondary p-0.5 transition-colors duration-200 ease-out before:absolute before:rounded-lg before:outline-offset-2 before:outline-primary focus-visible:before:inset-0 data-[checked]:bg-primary disabled:opacity-50 disabled:cursor-not-allowed cursor-default"
THUMB = "aspect-square h-full rounded-full bg-white transition-transform duration-200 ease-out data-[checked]:translate-x-3 shadow-small"
diff --git a/components/ui/table.py b/components/ui/table.py
index 43cf9bf..8f90453 100644
--- a/components/ui/table.py
+++ b/components/ui/table.py
@@ -26,7 +26,7 @@
class ClassNames:
"""Class names for table components."""
- ROOT = "w-full overflow-auto rounded-radius border border-input bg-card shadow-sm"
+ ROOT = "w-full overflow-auto rounded-lg border border-input bg-card shadow-sm"
TABLE = "w-full caption-bottom text-sm border-collapse"
HEADER = "[&_tr]:border-b bg-secondary/50 backdrop-blur-sm sticky top-0"
BODY = "[&_tr:last-child]:border-0"
diff --git a/components/ui/tabs.py b/components/ui/tabs.py
index bc2a3d9..0efb1f0 100644
--- a/components/ui/tabs.py
+++ b/components/ui/tabs.py
@@ -17,7 +17,7 @@ class ClassNames:
LIST = (
"relative bg-muted text-muted-foreground inline-flex h-9 w-full w-fit items-center "
- "justify-center rounded-radius p-[3px] "
+ "justify-center rounded-lg p-[3px] "
"data-[orientation=vertical]:flex data-[orientation=vertical]:flex-col "
"data-[orientation=vertical]:h-auto "
"data-[orientation=vertical]:p-1 data-[orientation=vertical]:gap-1"
@@ -26,7 +26,7 @@ class ClassNames:
"relative z-[1] flex items-center justify-center text-sm font-medium "
"text-foreground dark:text-muted-foreground "
"data-[selected]:text-foreground "
- "rounded-radius px-2 py-1 transition-all "
+ "rounded-lg px-2 py-1 transition-all "
"data-[orientation=vertical]:justify-start data-[orientation=vertical]:w-full "
"data-[orientation=vertical]:text-left"
)
@@ -36,7 +36,7 @@ class ClassNames:
)
INDICATOR = (
- "absolute z-[0] rounded-radius bg-background shadow-sm dark:border dark:border-input dark:bg-input/30 "
+ "absolute z-[0] rounded-lg bg-background shadow-sm dark:border dark:border-input dark:bg-input/30 "
"transition-all duration-200 ease-in-out "
"[left:var(--active-tab-left)] [top:var(--active-tab-top)] "
"[width:var(--active-tab-width)] [height:var(--active-tab-height)]"
diff --git a/components/ui/textarea.py b/components/ui/textarea.py
index 428780b..93a1d83 100644
--- a/components/ui/textarea.py
+++ b/components/ui/textarea.py
@@ -9,7 +9,7 @@
class ClassNames:
"""Class names for textarea components."""
- ROOT = "flex field-sizing-content min-h-16 min-w-xs rounded-radius border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
+ ROOT = "flex field-sizing-content min-h-16 min-w-xs rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
class Textarea(TextareaComponent, CoreComponent):
diff --git a/components/ui/toggle.py b/components/ui/toggle.py
index cf1825c..965cf7f 100644
--- a/components/ui/toggle.py
+++ b/components/ui/toggle.py
@@ -12,7 +12,7 @@
class ClassNames:
"""Class names for toggle components."""
- ROOT = "group/toggle p-1 inline-flex items-center justify-center gap-1 rounded-radius border border-input text-sm font-medium whitespace-nowrap transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+ ROOT = "group/toggle p-1 inline-flex items-center justify-center gap-1 rounded-lg border border-input text-sm font-medium whitespace-nowrap transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
ICON_VARIANT_CLASSES = {
"fill": "data-[pressed]:[&_svg]:fill-primary",
diff --git a/components/ui/tooltip.py b/components/ui/tooltip.py
index b5e40df..27e9702 100644
--- a/components/ui/tooltip.py
+++ b/components/ui/tooltip.py
@@ -21,7 +21,7 @@ class ClassNames:
"""Class names for tooltip components."""
TRIGGER = "inline-flex items-center justify-center"
- POPUP = "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-radius bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
+ POPUP = "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
ARROW = "data-[side=bottom]:top-[-7.5px] data-[side=left]:right-[-12.5px] data-[side=left]:rotate-90 data-[side=right]:left-[-12.5px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-7.5px] data-[side=top]:rotate-180"
diff --git a/docs/components/avatar.md b/docs/components/avatar.md
index 048910c..f9a3796 100644
--- a/docs/components/avatar.md
+++ b/docs/components/avatar.md
@@ -23,24 +23,111 @@ Use the following composition to build an `Avatar` component.
--ANATOMY(avatar)--
-
# Examples
+## Basic
+
+A basic avatar component with an image and a fallback.
+
+--DEMO(avatar_basic)--
+
+## Badge
+
+Use the `avatar.badge` component to add a badge to the avatar. The badge is positioned at the bottom right of the avatar.
+
+--DEMO(avatar_with_badge)--
+
+Use the `class_Name` prop to add custom styles to the badge such as custom colors, sizes, etc.
+
+```reflex
+avatar.badge(class_name="bg-green-600 dark:bg-green-800")
+```
+
+## Badge with Icon
-## General
+You can also use an icon inside `avatar.badge`.
-Displays a basic avatar with either a user image or a fallback placeholder.
+--DEMO(avatar_badge_icon)--
---DEMO(avatar_general)--
+## Avatar Group
+
+Use the `avatar.group` component to add a group of avatars.
+
+--DEMO(avatar_as_group)--
+
+## Avatar Group Count
+
+Use `avatar.group_count` to add a count to the group.
+
+--DEMO(avatar_with_group_count)--
+
+## Avatar Group with Icon
+
+You can also use an icon inside `avatar.group_count`.
+
+--DEMO(avatar_group_count_icon)--
## Sizes
-Demonstrates how to scale the avatar component using Tailwind utility classes.
+Use the `size` prop to change the size of the avatar.
--DEMO(avatar_sizes)--
-## With Badge
+## Dropdown
-Shows how to combine an avatar with status or notification badges for added context.
+You can use the `Avatar` component as a trigger for a dropdown menu.
---DEMO(avatar_with_badge)--
+--DEMO(avatar_dropdown_menu)--
+
+# API Reference
+
+## avatar.root
+
+The `avatar.root` component is the root component that wraps the avatar image and fallback.
+
+| Prop | Type | Default |
+| ----------- | --------------------------- | ----------- |
+| `size` | `"default" \| "sm" \| "lg"` | `"default"` |
+| `class_name` | `string` | - |
+
+## avatar.image
+
+The `avatar.image` component displays the avatar image. It accepts all Base UI Avatar Image props.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `src` | `string` | - |
+| `alt` | `string` | - |
+| `class_name` | `string` | - |
+
+## avatar.fallback
+
+The `avatar.fallback` component displays a fallback when the image fails to load. It accepts all Base UI Avatar Fallback props.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.badge
+
+The `avatar.badge` component displays a badge indicator on the avatar, typically positioned at the bottom right.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.group
+
+The `avatar.group` component displays a group of avatars with overlapping styling.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## avatar.group_count
+
+The `avatar.group_count` component displays a count indicator in an avatar group, typically showing the number of additional avatars.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
diff --git a/docs/components/badge.md b/docs/components/badge.md
index 3bf64fb..6d843e1 100644
--- a/docs/components/badge.md
+++ b/docs/components/badge.md
@@ -23,30 +23,46 @@ Use the following composition to build a `Badge` component.
--ANATOMY(badge)--
-
# Examples
+## Variants
-## Default
-
-Displays a standard badge using the default variant, ideal for basic labeling.
+Use the `variant` prop to change the variant of the badge.
---DEMO(badge_default)--
+--DEMO(badge_with_variants)--
## With Icons
-Demonstrates how to include icons inside badges for visual context or emphasis.
+You can render an icon inside the badge. Use `data-icon="inline-start"` to render the icon on the left and `data-icon="inline-end"` to render the icon on the right.
+
+--DEMO(badge_with_icon)--
+
+## With Spinner
+
+You can render a spinner inside the badge. Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` prop to the spinner.
+
+--DEMO(badge_with_spinner)--
+
+## Link
+
+You can pass in `rx.el.a` to turn a badge into a link. The `badge` component accepts `*children` so any interactive element can be passed to it.
+
+--DEMO(badge_as_link)--
+
+## Custom Colors
---DEMO(badge_with_icons)--
+You can customize the colors of a badge by adding custom classes such as `bg-green-50 dark:bg-green-800` to the `badge` component.
-## Status
+--DEMO(badge_custom_colors)--
-Showcases how badges can represent different statuses, like success or error, using color.
---DEMO(badge_status)--
+# API Reference
-## Notification Count
+## badge
-Illustrates how to use badges for showing counts, such as unread notifications or messages.
+The `badge` component displays a badge or a component that looks like a badge.
---DEMO(badge_notification_count)--
+| Prop | Type | Default |
+| ----------- | ----------------------------------------------------------------------------- | ----------- |
+| `variant` | `"default" \| "secondary" \| "destructive" \| "outline" \| "ghost" \| "link"` | `"default"` |
+| `class_name` | `string` | - |
diff --git a/docs/components/breadcrumb.md b/docs/components/breadcrumb.md
index 0ceb9f7..68d14f7 100644
--- a/docs/components/breadcrumb.md
+++ b/docs/components/breadcrumb.md
@@ -22,22 +22,83 @@ Use the following composition to build a `Breadcrumb` component.
# Examples
-## Basic Demo
-A basic breadcrumb showing the default navigation structure.
+## Basic
+A basic breadcrumb with a home link and a components link.
---DEMO(breadcrumb_basic_demo)--
+--DEMO(breadcrumb_basic)--
-## Simple Breadcrumb
-A minimal breadcrumb with plain text links.
+## Custom Separator
+Use a custom component as `children` for `breadcrumb.separator` to create a custom separator.
---DEMO(breadcrumb_simple_breadcrumb)--
+--DEMO(breadcrumb_custom_separator)--
-## Icon Breadcrumb
-A breadcrumb that includes icons alongside link labels.
+## Dropdown
---DEMO(breadcrumb_icon_breadcrumb)--
+You can compose `breadcrumb.item` with a `menu.root` to create a dropdown in the breadcrumb.
-## Custom Separator
-A breadcrumb with a customized separator between items.
+--DEMO(breadcrumb_dropdown_demo)--
---DEMO(breadcrumb_custom_separator)--
+## Collapsed
+
+We provide a `breadcrumb.ellipsis` component to show a collapsed state when the breadcrumb is too long.
+
+--DEMO(breadcrumb_ellipsis)--
+
+# API Reference
+
+## breadcrumb.root
+
+The `breadcrumb.root` component is the root navigation element that wraps all breadcrumb components.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.list
+
+The `breadcrumb.list` component displays the ordered list of breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.item
+
+The `breadcrumb.item` component wraps individual breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.link
+
+The `breadcrumb.link` component displays a clickable link in the breadcrumb.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.page
+
+The `breadcrumb.page` component displays the current page in the breadcrumb (non-clickable).
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
+
+## breadcrumb.separator
+
+The `breadcrumb.separator` component displays a separator between breadcrumb items. You can pass custom children to override the default separator icon.
+
+| Prop | Type | Default |
+| ----------- | ----------------- | ------- |
+| `children` | `rx.Component` | - |
+| `class_name` | `string` | - |
+
+## breadcrumb.ellipsis
+
+The `breadcrumb.ellipsis` component displays an ellipsis indicator for collapsed breadcrumb items.
+
+| Prop | Type | Default |
+| ----------- | -------- | ------- |
+| `class_name` | `string` | - |
diff --git a/docs/components/button.md b/docs/components/button.md
index b3eda77..b089839 100644
--- a/docs/components/button.md
+++ b/docs/components/button.md
@@ -23,54 +23,76 @@ Use the following composition to build a `Button` component.
--ANATOMY(button)--
-
# Examples
-
## Sizes
-Showcases buttons in different predefined sizes (default, small, large, icon, etc).
+Use the `size` prop to change the size of the button.
---DEMO(button_sizes)--
+--DEMO(button_size)--
## Default
-The default visual style for buttons with standard background and hover effects.
-
--DEMO(button_default)--
## Secondary
-A more muted alternative to the default button, useful for less prominent actions.
-
--DEMO(button_secondary)--
## Outline
-Buttons with a bordered outline, blending well with minimal UIs or light themes.
-
--DEMO(button_outline)--
## Ghost
-A button style with no background or border, ideal for subtle UI actions.
-
--DEMO(button_ghost)--
## Link
-A button styled to look like a hyperlink — useful for inline actions or navigation.
-
--DEMO(button_link)--
## Destructive
-A bold style used for destructive or dangerous actions like “Delete”.
-
--DEMO(button_destructive)--
## Icon
-Examples showing icon-only buttons with varying sizes for compact UI elements.
-
--DEMO(button_icon)--
+
+## With Icon
+
+Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` attribute to the icon for the correct spacing.
+
+--DEMO(button_with_icon)--
+
+## Rounded
+
+Use the `rounded-full` class to make the button rounded.
+
+--DEMO(button_rounded)--
+
+## Spinner
+
+Render a `spinner()` component inside the button to show a loading state. Remember to add the `data-icon="inline-start"` or `data-icon="inline-end"` attribute to the spinner for the correct spacing.
+
+--DEMO(button_loading)--
+
+## As Link
+
+You can use the `button_variants` helper function to make a link look like a button.
+
+Do **not** use `button(rx.el.a(...))` for links. The Base UI `Button` component always applies `role="button"`, which overrides the semantic link role on `` elements. Use `button_variants` with a plain `rx.el.a` tag instead to cleanly generate the necessary classes as a dynamic Reflex `Var`.
+
+--DEMO(button_render)--
+
+
+# API Reference
+
+## Button
+
+The `Button` component is a wrapper around the `button` element that adds a variety of styles and functionality.
+
+| Prop | Type | Default |
+| --------- | ------------------------------------------------------------------------------------ | ----------- |
+| `variant` | `"default" \| "outline" \| "ghost" \| "destructive" \| "secondary" \| "link"` | `"default"` |
+| `size` | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg"` | `"default"` |
diff --git a/docs/resources/theming.md b/docs/resources/theming.md
index a899c2e..f669564 100644
--- a/docs/resources/theming.md
+++ b/docs/resources/theming.md
@@ -73,7 +73,7 @@ Make sure to pull your CSS files where your tokens are defined in your `rx.App`
app = rx.App(stylesheets=["globals.css"])
```
-Tailwind maps these tokens into utilities like `bg-background`, `text-foreground`, `border-border`, and `rounded-radius`. Dark mode works by overriding the same tokens inside a `.dark` selector.
+Tailwind maps these tokens into utilities like `bg-background`, `text-foreground`, `border-border`, and `rounded-lg`. Dark mode works by overriding the same tokens inside a `.dark` selector.
# Theme Tokens
diff --git a/rxconfig.py b/rxconfig.py
index 310a90c..2d96522 100644
--- a/rxconfig.py
+++ b/rxconfig.py
@@ -55,7 +55,13 @@
"theme": "var(--font-family)",
},
"borderRadius": {
- "radius": "var(--radius)",
+ "sm": "var(--radius-sm)",
+ "md": "var(--radius-md)",
+ "lg": "var(--radius-lg)",
+ "xl": "var(--radius-xl)",
+ "2xl": "var(--radius-2xl)",
+ "3xl": "var(--radius-3xl)",
+ "4xl": "var(--radius-4xl)",
},
"padding": {
"card": "var(--card-padding)",
diff --git a/uv.lock b/uv.lock
index 5122bc1..1e88538 100644
--- a/uv.lock
+++ b/uv.lock
@@ -47,7 +47,7 @@ wheels = [
[[package]]
name = "buridan-create"
-version = "0.1.17"
+version = "0.1.19"
source = { editable = "." }
[package.dev-dependencies]