diff --git a/app/app.py b/app/app.py index 0509151d..e280c4c2 100644 --- a/app/app.py +++ b/app/app.py @@ -3,7 +3,7 @@ from app.export import export_site app = rx.App( - enable_state=False, + # enable_state=False, head_components=[ rx.el.script(src="/fuse/fuse-init.js", type="module"), rx.el.script(src="/fuse/searchFunction.js"), diff --git a/app/examples/components.py b/app/examples/components.py index 051d7a8f..141edb6b 100644 --- a/app/examples/components.py +++ b/app/examples/components.py @@ -9,10 +9,7 @@ from components.ui.button import button from components.ui.checkbox import checkbox from components.ui.input import input -from components.ui.input_group import ( - input_with_addons, - textarea_with_footer, -) +from components.ui.input_group import input_group from components.ui.metric import metric from components.ui.slider import slider from components.ui.switch import switch @@ -42,11 +39,16 @@ def card_one() -> rx.Component: class_name="w-full grid grid-cols-3 items-center gap-x-2", ), rx.el.div( - input(placeholder="name", class_name="!bg-secondary"), - textarea_with_footer( - placeholder="Enter your message", - footer_text="120 characters left", - class_name="!bg-secondary", + input(placeholder="name"), + input_group.root( + input_group.textarea( + id="block-end-textarea", + placeholder="Write a comment...", + ), + input_group.addon( + input_group.text("0/280"), + align="block-end", + ), ), class_name="flex flex-col gap-y-2", ), @@ -358,11 +360,16 @@ def card_five() -> rx.Component: "Amount to Transfer", class_name="text-sm font-semibold text-foreground", ), - input_with_addons( - placeholder="1,200.00", - class_name="!bg-secondary px-1", - prefix="$", - suffix="USD", + input_group.root( + input_group.addon( + input_group.text("$"), + align="inline-start", + ), + input_group.input(placeholder="0.00"), + input_group.addon( + input_group.text("USD"), + align="inline-end", + ), ), class_name="w-full flex flex-col gap-y-2 relative", ), @@ -732,12 +739,15 @@ def card_ten() -> rx.Component: field["label"], class_name="text-sm font-semibold text-foreground", ), - input_with_addons( - placeholder=field["placeholder"], - prefix=hi( - field["icon"], class_name="size-4 text-muted-foreground" + input_group.root( + input_group.input( + id="inline-start-input", + placeholder=field["placeholder"], + ), + input_group.addon( + hi(field["icon"], class_name="text-muted-foreground"), + align="inline-start", ), - class_name="!bg-secondary pl-2", ), class_name="w-full flex flex-col gap-y-2", ) @@ -830,14 +840,20 @@ def local_panel() -> rx.Component: tabs.panel( rx.el.div( rx.el.div( - input_with_addons( - value="https://github.com/LineIndent/ui.git", - placeholder="", - suffix=hi( - "Copy01Icon", - class_name="size-4 text-muted-foreground cursor-pointer", + input_group.root( + input_group.input( + placeholder="https://github.com/LineIndent/ui.git", + read_only=True, + ), + input_group.addon( + input_group.button( + hi("Copy01Icon"), + aria_label="Copy", + title="Copy", + size="icon-xs", + ), + align="inline-end", ), - class_name="!bg-secondary text-xs pr-2", ), rx.el.p( "Clone using the web URL.", @@ -853,14 +869,20 @@ def local_panel() -> rx.Component: # SSH panel tabs.panel( rx.el.div( - input_with_addons( - value="git@github.com:LineIndent/ui.git", - placeholder="", - suffix=hi( - "Copy01Icon", - class_name="size-4 text-muted-foreground cursor-pointer", + input_group.root( + input_group.input( + placeholder="git@github.com:LineIndent/ui.git", + read_only=True, + ), + input_group.addon( + input_group.button( + hi("Copy01Icon"), + aria_label="Copy", + title="Copy", + size="icon-xs", + ), + align="inline-end", ), - class_name="!bg-secondary text-xs pr-2", ), class_name="w-full pt-3", ), @@ -870,14 +892,20 @@ def local_panel() -> rx.Component: # GitHub CLI panel tabs.panel( rx.el.div( - input_with_addons( - value="gh repo clone LineIndent/ui", - placeholder="", - suffix=hi( - "Copy01Icon", - class_name="size-4 text-muted-foreground cursor-pointer", + input_group.root( + input_group.input( + placeholder="gh repo clone LineIndent/ui", + read_only=True, + ), + input_group.addon( + input_group.button( + hi("Copy01Icon"), + aria_label="Copy", + title="Copy", + size="icon-xs", + ), + align="inline-end", ), - class_name="!bg-secondary text-xs pr-2", ), class_name="w-full pt-3", ), @@ -1017,7 +1045,7 @@ def card_twelve() -> rx.Component: ), class_name="flex flex-col gap-y-0.5 flex-1", ), - switch(default_checked=row["default"]), + switch.root(switch.thumb(), default_checked=row["default"]), class_name="w-full flex flex-row items-start justify-between gap-x-4 py-3 border-b border-input last:border-b-0", ) for row in toggle_rows @@ -1335,9 +1363,20 @@ def member_row(member: dict) -> rx.Component: "Or share invite link", class_name="text-sm font-semibold text-foreground", ), - input_with_addons( - default_value="https://app.co/invite/x8f2k", - suffix=hi("Copy01Icon", class_name="size-4 text-muted-foreground mr-2"), + input_group.root( + input_group.input( + placeholder="https://app.co/invite/x8f2k", + read_only=True, + ), + input_group.addon( + input_group.button( + hi("Copy01Icon"), + aria_label="Copy", + title="Copy", + size="icon-xs", + ), + align="inline-end", + ), ), class_name="w-full flex flex-col gap-y-2", ), @@ -1366,8 +1405,11 @@ def card_seventeen() -> rx.Component: ), # Search input rx.el.div( - input_with_addons( - placeholder="example.com", + input_group.root( + input_group.input( + placeholder="example.com", + read_only=True, + ), ), class_name="w-full max-w-md mx-auto", ), diff --git a/app/pages/blocks.py b/app/pages/blocks.py index 0624287f..a1e9fb26 100644 --- a/app/pages/blocks.py +++ b/app/pages/blocks.py @@ -15,7 +15,7 @@ from app.www.library.blocks.line_chart_02 import line_chart_02 from app.www.library.blocks.line_chart_03 import line_chart_03 from app.www.library.blocks.line_chart_04 import line_chart_04 -from components.ui.button import BUTTON_VARIANTS, button +from components.ui.button import button BLOCKS = [ {"name": "All", "value": "all"}, @@ -38,15 +38,13 @@ def blocks_page(): return rx.el.div( rx.el.div( *[ - button( + rx.el.button( item["name"], - size="sm", on_click=selected_blocks_category.set_value(item["value"]), - class_name="transition-none " - + rx.cond( + class_name=rx.cond( selected_blocks_category.value == item["value"], - BUTTON_VARIANTS["variant"]["default"], - BUTTON_VARIANTS["variant"]["outline"], + "text-foreground font-medium cursor-pointer", + "text-muted-foreground font-normal hover:text-foreground cursor-pointer", ).to(str), ) for item in BLOCKS diff --git a/app/pages/charts.py b/app/pages/charts.py index e74b6d35..5643d371 100644 --- a/app/pages/charts.py +++ b/app/pages/charts.py @@ -15,16 +15,7 @@ from app.www.library.charts.scatter.v1 import scatterchart_v1 from components.ui.button import button -GRID_LAYOUT = " ".join( - [ - "grid grid-cols-1 lg:grid-cols-3", - "divide-y lg:divide-y-0", - "lg:divide-x", - "[&>div]:p-7", - "divide-input/40", - "border-x border-input/40", - ] -) +GRID_LAYOUT = " ".join(["grid grid-cols-1 lg:grid-cols-3", "gap-10 sm:gap-7"]) @layout_decorator( @@ -39,26 +30,31 @@ ) def chart_page(): return rx.el.div( - rx.el.div(areachart_v5(), class_name="w-full p-7"), - rx.el.hr(class_name="border border-input/40"), + rx.el.div(areachart_v5(), class_name="w-full"), + rx.el.div( + barchart_v1(), + areachart_v9(), + doughnutchart_v1(), + class_name=GRID_LAYOUT, + ), + rx.el.div(barchart_v5(), class_name="w-full"), rx.el.div( - linechart_v8(), barchart_v1(), doughnutchart_v1(), class_name=GRID_LAYOUT + linechart_v8(), + barchart_v9(), + scatterchart_v1(), + class_name=GRID_LAYOUT, ), - rx.el.hr(class_name="border border-input/40"), - rx.el.div(barchart_v5(), class_name="w-full p-7"), - rx.el.hr(class_name="border border-input/40"), + rx.el.div(linechart_v7(), class_name="w-full"), rx.el.div( - areachart_v9(), radar_v6(), scatterchart_v1(), class_name=GRID_LAYOUT + linechart_v5(), + radar_v6(), + piechart_v1(), + class_name=GRID_LAYOUT, ), - rx.el.hr(class_name="border border-input/40"), - rx.el.div(linechart_v7(), class_name="w-full p-7"), - rx.el.hr(class_name="border border-input/40"), - rx.el.div(linechart_v5(), barchart_v9(), piechart_v1(), class_name=GRID_LAYOUT), - rx.el.hr(class_name="border border-input/40"), class_name=" ".join( [ - "max-w-[96rem] mx-auto px-0 md:px-8", - "py-6 space-y-10", + "flex flex-col max-w-[96rem] mx-auto px-4 md:px-8", + "py-6 gap-10 sm:gap-7", ] ), ) diff --git a/app/pages/components.py b/app/pages/components.py index 5202c491..043f9398 100644 --- a/app/pages/components.py +++ b/app/pages/components.py @@ -58,7 +58,7 @@ def components_page(): class_name=" ".join( [ "grid", - "grid-cols-1", + "grid-cols-2", "sm:grid-cols-2", "md:grid-cols-3", "lg:grid-cols-4", diff --git a/app/registry/components.py b/app/registry/components.py index c814738a..b3a9ecd4 100644 --- a/app/registry/components.py +++ b/app/registry/components.py @@ -65,6 +65,10 @@ "files": ["components/ui/button.py"], "dependencies": ["others_icons", "component"], }, + "button_group": { + "files": ["components/ui/button_group.py"], + "dependencies": ["separator", "component", "base_ui"], + }, "card": { "files": ["components/ui/card.py"], "dependencies": ["component"], @@ -79,7 +83,7 @@ }, "context_menu": { "files": ["components/ui/context_menu.py"], - "dependencies": ["twmerge", "base_ui", "button"], + "dependencies": ["twmerge", "base_ui", "component", "hugeicon"], }, "dialog": { "files": ["components/ui/dialog.py"], @@ -137,6 +141,10 @@ "files": ["components/ui/scroll_area.py"], "dependencies": ["twmerge", "base_ui"], }, + "separator": { + "files": ["components/ui/separator.py"], + "dependencies": ["base_ui"], + }, "select": { "files": ["components/ui/select.py"], "dependencies": ["hugeicon", "others_icons", "twmerge", "base_ui", "button"], diff --git a/app/templates/compiler.py b/app/templates/compiler.py index c7b4c5d1..d43e49d9 100644 --- a/app/templates/compiler.py +++ b/app/templates/compiler.py @@ -11,7 +11,6 @@ theme_preset_option, ) from app.templates.config import rxconfig -from app.www.wrapper import styled_tab_trigger from components.icons.hugeicon import hi from components.ui.button import button from components.ui.dialog import dialog @@ -240,8 +239,8 @@ def theme_cli_prompt() -> rx.Component: rx.el.code( rx.cond( theme_export_method.value == "local", - f"uv run buridan init --preset {seed.value} --include {theme_preset_option.value} ", - f"Run this in the project terminal: `pip install buridan-create && buridan init --preset {seed.value} --include {theme_preset_option.value}` Then add this to your main app file: `app = rx.App(stylesheets=['globals.css'])`", + f"uv run buridan apply --preset {seed.value}", + f"Run this in the project terminal: `pip install buridan-create && buridan init && buridan apply --preset {seed.value}` Then add this to the main app file: `app = rx.App(stylesheets=['globals.css'])`", ), style={ "white-space": "pre", @@ -299,7 +298,7 @@ def theme_export_compiler() -> rx.Component: button( "Get Code", variant="default", - class_name="w-full rounded-xl", + class_name="w-full", id="get-code-btn", on_click=[rx.call_script(FORMAT_CSS_JS), rx.call_script(open_script)], ), @@ -327,6 +326,7 @@ def theme_export_compiler() -> rx.Component: on_click=rx.call_script(ADD_SWATCHES_JS), ), class_name="relative z-0 flex gap-1 rounded-none bg-transparent w-full mb-4", + variant="line", ), tabs.panel( rx.el.div( @@ -378,10 +378,7 @@ def theme_export_compiler() -> rx.Component: ), class_name="w-full flex flex-col gap-y-0 py-3", ), - rx.el.div( - theme_cli_prompt(), - class_name="w-full flex flex-col gap-y-0 py-3", - ), + theme_cli_prompt(), class_name="w-full flex flex-col gap-y-0", ), value="project", @@ -541,7 +538,7 @@ def theme_export_compiler() -> rx.Component: ), class_name="flex flex-col gap-y-4 !w-full h-full", ), - class_name="!w-full max-w-md rounded-2xl dark bg-card h-[92vh] p-6 flex flex-col", + class_name="!w-full !max-w-md rounded-2xl dark bg-card h-[640px] p-6 flex flex-col", ), ), on_open_change=rx.call_script(open_script), diff --git a/app/templates/docsidebar.py b/app/templates/docsidebar.py index f00e496a..37061362 100644 --- a/app/templates/docsidebar.py +++ b/app/templates/docsidebar.py @@ -41,6 +41,15 @@ class SidebarSection: SIDEBAR_SECTIONS = [ SidebarSection(title="Getting Started", routes=routes.GET_STARTED_URLS), + SidebarSection( + title="@buridan/ui", + routes=[ + { + "title": "pypi 0.1.19", + "url": "https://pypi.org/project/buridan-create/", + } + ], + ), SidebarSection(title="Resources", routes=routes.RESOURCES_URLS), SidebarSection(title="Utilities", routes=routes.UTILITIES), SidebarSection(title="Charts", routes=routes.CHARTS_URLS), @@ -51,20 +60,28 @@ class SidebarSection: def create_menu_item(data: dict): """Create a single menu item.""" - link = ( - rx.el.a( + if data["title"].startswith("pypi"): + link = rx.el.a( rx.el.p(data["title"], class_name="cursor-pointer"), - to=f"/{data['url']}", + href=data["url"], text_decoration="none", + reload_document=True, ) - if data["url"] != "llms.txt" - else rx.el.a( + + elif data["url"] == "llms.txt": + link = rx.el.a( rx.el.p(data["title"], class_name="cursor-pointer"), href=f"/{data['url']}", text_decoration="none", reload_document=True, ) - ) + + else: + link = rx.el.a( + rx.el.p(data["title"], class_name="cursor-pointer"), + to=f"/{data['url']}", + text_decoration="none", + ) return button( link, diff --git a/app/templates/options.py b/app/templates/options.py index 08989f88..66e88811 100644 --- a/app/templates/options.py +++ b/app/templates/options.py @@ -99,7 +99,7 @@ def component_panel(desktop: bool = True) -> rx.Component: hi("KeyframesMultipleIcon", class_name="size-5"), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -177,7 +177,7 @@ def component_panel(desktop: bool = True) -> rx.Component: ), class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -196,7 +196,7 @@ def menu_panel(desktop: bool = True) -> rx.Component: select.trigger( rx.el.p("Menu", class_name="text-primary"), hi("EqualSignIcon", class_name="size-5"), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-10 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -248,7 +248,7 @@ def style_panel(desktop: bool = True) -> rx.Component: style_icon(), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="w-full !h-14 rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -270,9 +270,9 @@ def style_panel(desktop: bool = True) -> rx.Component: ], class_name="p-1", ), - class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + class_name="border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -300,7 +300,7 @@ def font_panel(desktop: bool = True) -> rx.Component: hi("TextFontIcon", class_name="size-4"), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -330,7 +330,7 @@ def font_panel(desktop: bool = True) -> rx.Component: ], class_name="h-[40vh] overflow-y-auto rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px] scrollbar-none" if desktop - else "h-[40vh] overflow-y-auto rounded-xl border-0 dark bg-card/90 backdrop-blur-xl scrollbar-none", + else "h-[40vh] overflow-y-auto rounded-xl border-0 dark bg-card/90 backdrop-blur-xl scrollbar-none w-full", ), side_offset=25, side="right" if desktop else "top", @@ -361,7 +361,7 @@ def base_color_panel(desktop: bool = True) -> rx.Component: ), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -390,7 +390,7 @@ def base_color_panel(desktop: bool = True) -> rx.Component: ), class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -436,7 +436,7 @@ def theme_panel(desktop: bool = True) -> rx.Component: ), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -479,7 +479,7 @@ def theme_panel(desktop: bool = True) -> rx.Component: ), class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -525,7 +525,7 @@ def chart_color_panel(desktop: bool = True) -> rx.Component: ), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -568,7 +568,7 @@ def chart_color_panel(desktop: bool = True) -> rx.Component: ), class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -594,7 +594,7 @@ def radius_panel(desktop: bool = True) -> rx.Component: radius_icon(), class_name="w-full flex flex-row justify-between items-center", ), - class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + class_name="!h-14 w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", ), select.portal( select.positioner( @@ -618,7 +618,7 @@ def radius_panel(desktop: bool = True) -> rx.Component: ), class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" if desktop - else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-full", ), side_offset=25, side="right" if desktop else "top", @@ -635,7 +635,7 @@ def shuffle_button() -> rx.Component: return button( "Shuffle", variant="outline", - class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + class_name="w-full !bg-transparent hover:!bg-secondary", on_click=rx.call_script(SHUFFLE_JS), ) @@ -648,7 +648,7 @@ def preset_copy_button() -> rx.Component: f"--preset {seed.value}", ), variant="outline", - class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + class_name="w-full !bg-transparent hover:!bg-secondary", on_click=[ rx.call_function(copy_preset_value.set_value(True)), rx.set_clipboard(f"{seed.value}"), @@ -665,7 +665,7 @@ def open_preset_menu() -> rx.Component: button( "Open Preset", variant="outline", - class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + class_name="w-full !bg-transparent hover:!bg-secondary", id="preset-trigger-btn", ), class_name="w-full", @@ -690,20 +690,24 @@ def open_preset_menu() -> rx.Component: id="seed-input-el", class_name="rounded-lg text-foreground", ), - rx.el.div( - dialog.close( - button("Cancel", variant="outline", class_name="w-full"), - class_name="flex-1", - ), - dialog.close( - button( - "Open", - class_name="w-full", - on_click=rx.call_script(APPLY_SEED_JS), + dialog.footer( + rx.el.div( + dialog.close( + render_=button( + "Cancel", variant="outline", class_name="w-full" + ), + class_name="flex-1", + ), + dialog.close( + render_=button( + "Open", + class_name="w-full", + on_click=rx.call_script(APPLY_SEED_JS), + ), + class_name="flex-1", ), - class_name="flex-1", + class_name="w-full flex flex-row gap-x-6", ), - class_name="flex flex-row gap-x-6", ), class_name="flex flex-col gap-y-4 w-full", ), diff --git a/app/templates/reset.py b/app/templates/reset.py index 04c49326..edd1a7e4 100644 --- a/app/templates/reset.py +++ b/app/templates/reset.py @@ -28,24 +28,28 @@ def reset_theme_button() -> rx.Component: ), class_name="w-full flex flex-col gap-y-1", ), - rx.el.div( - dialog.close( - button("Cancel", variant="outline", class_name="w-full"), - class_name="flex-1", - ), - dialog.close( - button( - "Reset", - variant="destructive", - class_name="w-full", - on_click=[ - rx.call_script(RESET_JS), - selected_component_category.set_value("All"), - ], + dialog.footer( + rx.el.div( + dialog.close( + render_=button( + "Cancel", variant="outline", class_name="w-full" + ), + class_name="flex-1", + ), + dialog.close( + render_=button( + "Reset", + variant="destructive", + class_name="w-full", + on_click=[ + rx.call_script(RESET_JS), + selected_component_category.set_value("All"), + ], + ), + class_name="flex-1", ), - class_name="flex-1", + class_name="w-full flex flex-row gap-x-6", ), - class_name="flex flex-row gap-x-6", ), class_name="flex flex-col gap-y-4 w-full", ), diff --git a/app/www/anatomy.py b/app/www/anatomy.py index 654b90ac..bf5ccbef 100644 --- a/app/www/anatomy.py +++ b/app/www/anatomy.py @@ -1,6 +1,7 @@ # app/www/anatomy.py ANATOMY = { + "separator": """separator()""", "bubble": """bubble.root( bubble.content(), bubble.reactions(), @@ -70,10 +71,15 @@ ) """, "button": """button()""", + "button_group": """button_group.root( + button(), + button_group.separator(), +)""", "card": """card.root( card.header( card.title(), card.description(), + card.action(), ), card.content(), card.footer(), @@ -104,12 +110,10 @@ context_menu.radio_item_indicator(), ), ), - context_menu.submenu_root( - context_menu.submenu_trigger(), - context_menu.portal( - context_menu.positioner( - context_menu.popup(), - ), + context_menu.sub( + context_menu.sub_trigger(), + context_menu.positioner( + context_menu.popup(), ), ), ), @@ -128,21 +132,13 @@ ), )""", "input": """input()""", - "input_group": """# Input with addons -input_with_addons( - prefix=..., - suffix=..., -) - -# Textarea with footer -textarea_with_footer( - footer_text=..., + "input_group": """input_group.root( + input_group.input(placeholder="Search..."), + input_group.addon(), )""", - "kbd": """kbd() -# or -kbd_group( - kbd(), - kbd(), + "kbd": """kbd.group( + kbd.root(), + kbd.root(), )""", "link": """link()""", "marker": """marker.root( @@ -268,8 +264,8 @@ "theme_switcher": """theme_switcher()""", "toggle": """toggle()""", "toggle_group": """toggle_group( - toggle(), - toggle(), + toggle_group.item("Option 1", value="1"), + toggle_group.item("Option 2", value="2"), )""", "tooltip": """tooltip.root( tooltip.trigger(), @@ -283,12 +279,12 @@ ), )""", "field": """field.root( - field.label(), - field.control(), - field.description(), - field.item(), + field.content( + field.label(), + field.title(), + field.description(), + ), field.error(), - field.validity(), ) """, "timeline": """timeline.root( diff --git a/app/www/library/blocks/area_chart_01.py b/app/www/library/blocks/area_chart_01.py index 2f62d5ac..f0a95d6f 100644 --- a/app/www/library/blocks/area_chart_01.py +++ b/app/www/library/blocks/area_chart_01.py @@ -114,5 +114,6 @@ def area_chart_01(): rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), ), - class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/area_chart_02.py b/app/www/library/blocks/area_chart_02.py index 076d3bcd..7893846c 100644 --- a/app/www/library/blocks/area_chart_02.py +++ b/app/www/library/blocks/area_chart_02.py @@ -113,5 +113,6 @@ def area_chart_02(): class_name="mt-4 w-full", ), ), - class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/bar_chart_01.py b/app/www/library/blocks/bar_chart_01.py index 85aaf721..fba5cd78 100644 --- a/app/www/library/blocks/bar_chart_01.py +++ b/app/www/library/blocks/bar_chart_01.py @@ -79,27 +79,26 @@ def bar_chart_01(): rx.cond( ShowComparison.value, rx.el.div( - rx.el.div(class_name="w-3 h-3 rounded-sm bg-chart-2"), + rx.el.div(class_name="w-3 h-3 bg-chart-2"), "Last Year", class_name="text-sm flex flex-row gap-x-2 items-center", ), ), rx.el.div( - rx.el.div(class_name="w-3 h-3 rounded-sm bg-chart-1"), + rx.el.div(class_name="w-3 h-3 bg-chart-1"), "This Year", class_name="text-sm flex flex-row gap-x-2 items-center", ), class_name="flex flex-row gap-x-2 justify-end items-center", ), card.content( - # Mobile: no y-axis rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), - # Desktop: with y-axis rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), ), card.footer( field.root( - checkbox( + checkbox.root( + checkbox.indicator(), id="terms-checkbox-basic", on_checked_change=ShowComparison.set_value(~ShowComparison.value), ), @@ -110,5 +109,6 @@ def bar_chart_01(): orientation="horizontal", ), ), - class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/bar_chart_02.py b/app/www/library/blocks/bar_chart_02.py index 8b8f64d0..98e2539c 100644 --- a/app/www/library/blocks/bar_chart_02.py +++ b/app/www/library/blocks/bar_chart_02.py @@ -83,17 +83,18 @@ def bar_chart_02(): ), rx.el.li( rx.el.div( - rx.el.div(class_name="w-3 h-3 rounded-sm bg-chart-2"), + rx.el.div(class_name="w-3 h-3 bg-chart-2"), rx.el.p("Last year", class_name="text-sm text-muted-foreground"), class_name="flex items-center gap-1.5", ), rx.el.p("$0.7M", class_name="mt-0.5 text-base font-semibold"), ), - class_name="flex gap-10", + class_name="flex gap-10 px-4", ), card.content( rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), ), - class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/bar_chart_03.py b/app/www/library/blocks/bar_chart_03.py index 431586d7..52154616 100644 --- a/app/www/library/blocks/bar_chart_03.py +++ b/app/www/library/blocks/bar_chart_03.py @@ -115,5 +115,6 @@ def bar_chart_03(): rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), ), - class_name=chart_tooltip_content([2, 3], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([2, 3], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/bar_chart_04.py b/app/www/library/blocks/bar_chart_04.py index 5af05061..881ef419 100644 --- a/app/www/library/blocks/bar_chart_04.py +++ b/app/www/library/blocks/bar_chart_04.py @@ -75,5 +75,6 @@ def bar_chart_04(): rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), ), - class_name=chart_tooltip_content([1], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/kpi_card_01.py b/app/www/library/blocks/kpi_card_01.py index 88066543..387558ef 100644 --- a/app/www/library/blocks/kpi_card_01.py +++ b/app/www/library/blocks/kpi_card_01.py @@ -38,13 +38,14 @@ def _kpi_card(name: str, stat: str, change: str, color: str) -> rx.Component: ), class_name="flex w-full items-center justify-between gap-3 truncate", ), - class_name="flex gap-3", + class_name="flex gap-3 px-4", ), rx.el.div( rx.el.p(stat, class_name="text-3xl font-semibold"), class_name="mt-2 pl-4", ), - class_name="w-full border border-input/80 rounded-2xl", + size="sm", + class_name="w-full border border-input/80 rounded-2xl !ring-0", ) diff --git a/app/www/library/blocks/kpi_card_02.py b/app/www/library/blocks/kpi_card_02.py index 4522df69..05b39ae0 100644 --- a/app/www/library/blocks/kpi_card_02.py +++ b/app/www/library/blocks/kpi_card_02.py @@ -105,8 +105,9 @@ def _kpi_mini_chart( ), _mini_chart(key, color), ), + size="sm", class_name=chart_tooltip_content([color], "square") - + " w-full px-4 pt-4 pb-0 border border-input/80 rounded-2xl", + + " w-full px-2 pt-4 pb-0 border border-input/80 rounded-2xl !ring-0", ) diff --git a/app/www/library/blocks/line_chart_01.py b/app/www/library/blocks/line_chart_01.py index 2a94c6e3..57ef7978 100644 --- a/app/www/library/blocks/line_chart_01.py +++ b/app/www/library/blocks/line_chart_01.py @@ -144,5 +144,6 @@ def line_chart_01(): class_name="mt-4 flex items-start gap-6", ), ), - class_name=chart_tooltip_content([1], "square") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1], "square") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/line_chart_02.py b/app/www/library/blocks/line_chart_02.py index 77d31dcc..05f0c53a 100644 --- a/app/www/library/blocks/line_chart_02.py +++ b/app/www/library/blocks/line_chart_02.py @@ -163,5 +163,6 @@ def line_chart_02(): class_name="mt-4 flex flex-col", ), ), - class_name=chart_tooltip_content([1, 2, 3], "line") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2, 3], "line") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/blocks/line_chart_03.py b/app/www/library/blocks/line_chart_03.py index 17cf82f3..a22c7a73 100644 --- a/app/www/library/blocks/line_chart_03.py +++ b/app/www/library/blocks/line_chart_03.py @@ -194,5 +194,6 @@ def line_chart_03(): class_name="flex gap-8", ), ), - class_name=(chart_tooltip_content([1, 2], "square") + " w-full p-0"), + size="sm", + class_name=(chart_tooltip_content([1, 2], "square") + " w-full p-0 !ring-0"), ) diff --git a/app/www/library/blocks/line_chart_04.py b/app/www/library/blocks/line_chart_04.py index 48b34b1f..a3d36873 100644 --- a/app/www/library/blocks/line_chart_04.py +++ b/app/www/library/blocks/line_chart_04.py @@ -70,12 +70,10 @@ def line_chart_04(): ), card.content( rx.el.div( - # Y-axis label rotated on the left rx.el.p( "Spend Category", class_name="-rotate-90 text-xs text-muted-foreground whitespace-nowrap self-center h-fit w-[1rem]", ), - # Chart rx.el.div( rx.el.div(_chart(show_y_axis=False), class_name="sm:hidden"), rx.el.div(_chart(show_y_axis=True), class_name="hidden sm:block"), @@ -83,12 +81,10 @@ def line_chart_04(): ), class_name="flex flex-row gap-2 w-full", ), - # X-axis label rx.el.p( "Month", class_name="text-xs text-muted-foreground text-center mt-1", ), - # Legend rx.el.div( *[ rx.el.div( @@ -102,5 +98,6 @@ def line_chart_04(): ), class_name="relative", ), - class_name=chart_tooltip_content([1, 2], "line") + " w-full p-0", + size="sm", + class_name=chart_tooltip_content([1, 2], "line") + " w-full p-0 !ring-0", ) diff --git a/app/www/library/charts/area/v10.py b/app/www/library/charts/area/v10.py index 118fb8dd..941f29e1 100644 --- a/app/www/library/charts/area/v10.py +++ b/app/www/library/charts/area/v10.py @@ -28,8 +28,11 @@ def areachart_v10(): rx.foreach( series, lambda s: rx.hstack( - rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), - rx.text( + rx.el.div( + class_name="h-2 w-2 shrink-0 rounded-[2px]", + bg=f"var({s[2]})", + ), + rx.el.p( s[1], class_name="text-xs font-medium", color=rx.color("slate", 11), diff --git a/app/www/library/charts/area/v5.py b/app/www/library/charts/area/v5.py index edfb8797..7193cabb 100644 --- a/app/www/library/charts/area/v5.py +++ b/app/www/library/charts/area/v5.py @@ -94,17 +94,14 @@ def area(data_key: str, color: str): ), data=SelectedRange.value, width="100%", - height=240, + height=250, ), - class_name="flex flex-col items-center h-[240px]", - ), - card.footer( rx.el.div( rx.foreach( ["Desktop", "Mobile"], lambda device, index: rx.el.div( rx.el.div( - class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + class_name=f"h-2 w-2 shrink-0 rounded-[2px] bg-chart-{index + 1}" ), rx.el.p(device, class_name="text-sm text-foreground"), class_name="flex flex-row items-center gap-x-2", @@ -112,6 +109,7 @@ def area(data_key: str, color: str): ), class_name="py-4 px-4 flex w-full flex justify-center gap-8", ), + class_name="flex flex-col items-center", ), class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", ) diff --git a/app/www/library/charts/area/v8.py b/app/www/library/charts/area/v8.py index d717f15b..5d1dec59 100644 --- a/app/www/library/charts/area/v8.py +++ b/app/www/library/charts/area/v8.py @@ -43,8 +43,11 @@ def create_gradient(var_name): rx.foreach( series, lambda s: rx.hstack( - rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), - rx.text( + rx.el.div( + class_name="h-2 w-2 shrink-0 rounded-[2px]", + bg=f"var({s[2]})", + ), + rx.el.p( s[1], class_name="text-xs font-medium", color=rx.color("slate", 11), diff --git a/app/www/library/examples/accordion_basic.py b/app/www/library/examples/accordion_basic.py index 918ad87f..48acbf3a 100644 --- a/app/www/library/examples/accordion_basic.py +++ b/app/www/library/examples/accordion_basic.py @@ -1,4 +1,5 @@ import reflex as rx + from components.ui.accordion import accordion @@ -6,88 +7,38 @@ def accordion_basic(): return rx.el.div( accordion.root( accordion.item( - accordion.header( - accordion.trigger("Models"), - ), + accordion.trigger("Models"), accordion.panel( - rx.el.div( - rx.el.p( - "- Genesis launched a new era of exploration.", - class_name="mb-2", - ), - rx.el.p( - "- Explorer uncovered new planets beyond our reach.", - class_name="mb-2", - ), - rx.el.p( - "- Voyager 1 ventured into interstellar space.", - class_name="mb-2", - ), - rx.el.p( - "- Apollo landed humans on the Moon.", - class_name="mb-2", - ), - class_name="py-2 text-sm", - ), + rx.el.p("- Genesis launched a new era of exploration."), + rx.el.p("- Explorer uncovered new planets beyond our reach."), + rx.el.p("- Voyager 1 ventured into interstellar space."), + rx.el.p("- Apollo landed humans on the Moon."), ), value="section-1", ), accordion.item( - accordion.header( - accordion.trigger("Spacecraft"), - ), + accordion.trigger("Spacecraft"), accordion.panel( - rx.el.div( - rx.el.p( - "- Curiosity sent back valuable data from Mars.", - class_name="mb-2", - ), - rx.el.p( - "- The Hubble Telescope captured distant galaxies.", - class_name="mb-2", - ), - rx.el.p( - "- James Webb will explore the universe's origins.", - class_name="mb-2", - ), - rx.el.p( - "- The ISS orbits Earth, conducting critical experiments.", - class_name="mb-2", - ), - class_name="py-2 text-sm", - ), + rx.el.p("- Curiosity sent back valuable data from Mars."), + rx.el.p("- The Hubble Telescope captured distant galaxies."), + rx.el.p("- James Webb will explore the universe's origins."), + rx.el.p("- The ISS orbits Earth, conducting critical experiments."), ), value="section-2", ), accordion.item( - accordion.header( - accordion.trigger("Space Discoveries"), - ), + accordion.trigger("Space Discoveries"), accordion.panel( - rx.el.div( - rx.el.p( - "- Saturn's rings have fascinated scientists for years.", - class_name="mb-2", - ), - rx.el.p( - "- The Mars Rover is studying the planet's surface.", - class_name="mb-2", - ), - rx.el.p( - "- NASA's Artemis program aims to return humans to the Moon.", - class_name="mb-2", - ), - rx.el.p( - "- Solar missions help us understand space weather.", - class_name="mb-2", - ), - class_name="py-2 text-sm", + rx.el.p("- Saturn's rings have fascinated scientists for years."), + rx.el.p("- The Mars Rover is studying the planet's surface."), + rx.el.p( + "- NASA's Artemis program aims to return humans to the Moon." ), + rx.el.p("- Solar missions help us understand space weather."), ), value="section-3", ), class_name="w-full max-w-md mx-auto", - open_multiple=False, default_value=["section-1"], ), class_name="h-[45vh] w-full justify-center pt-10 px-8", diff --git a/app/www/library/examples/accordion_borders.py b/app/www/library/examples/accordion_borders.py new file mode 100644 index 00000000..6471dceb --- /dev/null +++ b/app/www/library/examples/accordion_borders.py @@ -0,0 +1,50 @@ +import reflex as rx + +from components.ui.accordion import accordion + +items = [ + { + "value": "billing", + "trigger": "How does billing work?", + "content": ( + "We offer monthly and annual subscription plans. Billing is charged " + "at the beginning of each cycle, and you can cancel anytime. All " + "plans include automatic backups, 24/7 support, and unlimited team " + "members." + ), + }, + { + "value": "security", + "trigger": "Is my data secure?", + "content": ( + "Yes. We use end-to-end encryption, SOC 2 Type II compliance, and " + "regular third-party security audits. All data is encrypted at rest " + "and in transit using industry-standard protocols." + ), + }, + { + "value": "integration", + "trigger": "What integrations do you support?", + "content": ( + "We integrate with 500+ popular tools including Slack, Zapier, " + "Salesforce, HubSpot, and more. You can also build custom " + "integrations using our REST API and webhooks." + ), + }, +] + + +def accordion_borders() -> rx.Component: + return accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + class_name="border-b px-4 last:border-b-0", + ) + for item in items + ], + default_value=["billing"], + class_name="max-w-sm rounded-lg border border-input", + ) diff --git a/app/www/library/examples/accordion_card.py b/app/www/library/examples/accordion_card.py new file mode 100644 index 00000000..87cbe432 --- /dev/null +++ b/app/www/library/examples/accordion_card.py @@ -0,0 +1,63 @@ +import reflex as rx + +from components.ui.accordion import accordion +from components.ui.card import card + +items = [ + { + "value": "plans", + "trigger": "What subscription plans do you offer?", + "content": ( + "We offer three subscription tiers: Starter ($9/month), " + "Professional ($29/month), and Enterprise ($99/month). Each plan " + "includes increasing storage limits, API access, priority support, " + "and team collaboration features." + ), + }, + { + "value": "billing", + "trigger": "How does billing work?", + "content": ( + "Billing occurs automatically at the start of each billing cycle. " + "We accept all major credit cards, PayPal, and ACH transfers for " + "enterprise customers. You'll receive an invoice via email after " + "each payment." + ), + }, + { + "value": "cancel", + "trigger": "How do I cancel my subscription?", + "content": ( + "You can cancel your subscription anytime from your account " + "settings. There are no cancellation fees or penalties. Your " + "access will continue until the end of your current billing " + "period." + ), + }, +] + + +def accordion_card() -> rx.Component: + return card.root( + card.header( + card.title("Subscription & Billing"), + card.description( + "Common questions about your account, plans, payments and " + "cancellations." + ), + ), + card.content( + accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + ) + for item in items + ], + default_value=["plans"], + ), + ), + class_name="w-full max-w-sm", + ) diff --git a/app/www/library/examples/accordion_disabled.py b/app/www/library/examples/accordion_disabled.py new file mode 100644 index 00000000..c4a1a9f9 --- /dev/null +++ b/app/www/library/examples/accordion_disabled.py @@ -0,0 +1,36 @@ +import reflex as rx + +from components.ui.accordion import accordion + + +def accordion_disabled() -> rx.Component: + return accordion.root( + accordion.item( + accordion.trigger("Can I access my account history?"), + accordion.panel( + "Yes, you can view your complete account history including all " + "transactions, plan changes, and support tickets in the Account " + "History section of your dashboard." + ), + value="item-1", + ), + accordion.item( + accordion.trigger("Premium feature information"), + accordion.panel( + "This section contains information about premium features. " + "Upgrade your plan to access this content." + ), + value="item-2", + disabled=True, + ), + accordion.item( + accordion.trigger("How do I update my email address?"), + accordion.panel( + "You can update your email address in your account settings. " + "You'll receive a verification email at your new address to " + "confirm the change." + ), + value="item-3", + ), + class_name="max-w-sm", + ) diff --git a/app/www/library/examples/accordion_multiple.py b/app/www/library/examples/accordion_multiple.py new file mode 100644 index 00000000..e05379ed --- /dev/null +++ b/app/www/library/examples/accordion_multiple.py @@ -0,0 +1,49 @@ +import reflex as rx + +from components.ui.accordion import accordion + +items = [ + { + "value": "notifications", + "trigger": "Notification Settings", + "content": ( + "Manage how you receive notifications. You can enable email alerts " + "for updates or push notifications for mobile devices." + ), + }, + { + "value": "privacy", + "trigger": "Privacy & Security", + "content": ( + "Control your privacy settings and security preferences. Enable " + "two-factor authentication, manage connected devices, review active " + "sessions, and configure data sharing preferences. You can also " + "download your data or delete your account." + ), + }, + { + "value": "billing", + "trigger": "Billing & Subscription", + "content": ( + "View your current plan, payment history, and upcoming invoices. " + "Update your payment method, change your subscription tier, or " + "cancel your subscription." + ), + }, +] + + +def accordion_multiple() -> rx.Component: + return accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + ) + for item in items + ], + multiple=True, + default_value=["notifications"], + class_name="max-w-sm", + ) diff --git a/app/www/library/examples/button_group_dropdown.py b/app/www/library/examples/button_group_dropdown.py new file mode 100644 index 00000000..6b83a5d1 --- /dev/null +++ b/app/www/library/examples/button_group_dropdown.py @@ -0,0 +1,41 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group +from components.ui.menu import menu + + +def button_group_dropdown() -> rx.Component: + return button_group.root( + button("Follow", variant="outline"), + menu.root( + menu.trigger( + render_=button( + hi("ArrowDown01Icon"), + variant="outline", + class_name="pl-2!", + ), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item("Mute Conversation"), + menu.item("Mark as Read"), + menu.item("Report Conversation"), + menu.item("Block User"), + menu.item("Share Conversation"), + menu.item("Copy Conversation"), + ), + menu.separator(), + menu.group( + menu.item("Delete Conversation", variant="destructive"), + ), + class_name="w-44", + ), + align="end", + ), + ), + ), + ) diff --git a/app/www/library/examples/button_group_input.py b/app/www/library/examples/button_group_input.py new file mode 100644 index 00000000..1db3b098 --- /dev/null +++ b/app/www/library/examples/button_group_input.py @@ -0,0 +1,17 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group +from components.ui.input import input + + +def button_group_input() -> rx.Component: + return button_group.root( + input(placeholder="Search..."), + button( + hi("Search01Icon"), + variant="outline", + aria_label="Search", + ), + ) diff --git a/app/www/library/examples/button_group_orientation.py b/app/www/library/examples/button_group_orientation.py new file mode 100644 index 00000000..5b5b02c7 --- /dev/null +++ b/app/www/library/examples/button_group_orientation.py @@ -0,0 +1,23 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group + + +def button_group_orientation() -> rx.Component: + return button_group.root( + button( + hi("Add01Icon"), + variant="outline", + size="icon", + ), + button( + hi("MinusSignIcon"), + variant="outline", + size="icon", + ), + orientation="vertical", + aria_label="Media controls", + class_name="h-fit", + ) diff --git a/app/www/library/examples/button_group_select.py b/app/www/library/examples/button_group_select.py new file mode 100644 index 00000000..717c7c82 --- /dev/null +++ b/app/www/library/examples/button_group_select.py @@ -0,0 +1,70 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group +from components.ui.input import input +from components.ui.select import select + +selected_currency = ClientStateVar.create("selected_currency", "$") + +currencies = [ + {"label": "US Dollar", "value": "$"}, + {"label": "Euro", "value": "€"}, + {"label": "British Pound", "value": "£"}, +] + + +def button_group_select() -> rx.Component: + + return button_group.root( + button_group.root( + select.root( + select.trigger( + selected_currency.value, + select.icon(), + class_name="flex items-center gap-1", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text( + item["value"], + " ", + rx.el.span( + item["label"], + class_name="text-xs !text-muted-foreground", + ), + class_name="text-sm text-foreground", + ), + select.item_indicator(), + value=item["value"], + class_name="flex flex-row items-center justify-between", + ) + for item in currencies + ] + ), + ), + align="start", + ), + ), + items=currencies, + value=selected_currency.value, + on_value_change=selected_currency.set_value, + name="currency_select", + ), + input(placeholder="10.00", pattern="[0-9]*"), + ), + button_group.root( + button( + hi("ArrowRight01Icon"), + aria_label="Send", + size="icon", + variant="outline", + ) + ), + ) diff --git a/app/www/library/examples/button_group_separator.py b/app/www/library/examples/button_group_separator.py new file mode 100644 index 00000000..6b06f429 --- /dev/null +++ b/app/www/library/examples/button_group_separator.py @@ -0,0 +1,12 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.button_group import button_group + + +def button_group_separator() -> rx.Component: + return button_group.root( + button("Copy", variant="secondary", size="sm"), + button_group.separator(), + button("Paste", variant="secondary", size="sm"), + ) diff --git a/app/www/library/examples/button_group_size.py b/app/www/library/examples/button_group_size.py new file mode 100644 index 00000000..e01a0850 --- /dev/null +++ b/app/www/library/examples/button_group_size.py @@ -0,0 +1,29 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group + + +def button_group_size() -> rx.Component: + return rx.el.div( + button_group.root( + button("Small", variant="outline", size="sm"), + button("Button", variant="outline", size="sm"), + button("Group", variant="outline", size="sm"), + button(hi("Add01Icon"), variant="outline", size="icon-sm"), + ), + button_group.root( + button("Default", variant="outline"), + button("Button", variant="outline"), + button("Group", variant="outline"), + button(hi("Add01Icon"), variant="outline", size="icon"), + ), + button_group.root( + button("Large", variant="outline", size="lg"), + button("Button", variant="outline", size="lg"), + button("Group", variant="outline", size="lg"), + button(hi("Add01Icon"), variant="outline", size="icon-lg"), + ), + class_name="flex flex-col items-start gap-8", + ) diff --git a/app/www/library/examples/button_group_split.py b/app/www/library/examples/button_group_split.py new file mode 100644 index 00000000..3437b2d2 --- /dev/null +++ b/app/www/library/examples/button_group_split.py @@ -0,0 +1,17 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.button_group import button_group + + +def button_group_split() -> rx.Component: + return button_group.root( + button("Button", variant="secondary"), + button_group.separator(), + button( + hi("Add01Icon"), + variant="secondary", + size="icon", + ), + ) diff --git a/app/www/library/examples/card_edge_to_edge.py b/app/www/library/examples/card_edge_to_edge.py new file mode 100644 index 00000000..990ee5ad --- /dev/null +++ b/app/www/library/examples/card_edge_to_edge.py @@ -0,0 +1,37 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.card import card + + +def card_edge_to_edge() -> rx.Component: + return card.root( + card.header( + card.title("Terms of Service"), + card.description("Review the terms before accepting the agreement."), + ), + card.content( + rx.el.div( + rx.el.p( + "These terms govern your use of the workspace, including access to shared documents, project files, and collaboration tools." + ), + rx.el.p( + "You are responsible for the content you upload and for ensuring that your team has the appropriate permissions to view or edit it." + ), + rx.el.p( + "We may update features or limits as the service evolves. When those changes materially affect your workflow, we will notify your workspace administrators." + ), + rx.el.p( + "By continuing, you agree to keep your account credentials secure and to follow your organization's acceptable use policies." + ), + class_name="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-input border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed", + ), + class_name="-mb-(--card-spacing)", + ), + card.footer( + button("Decline", variant="outline"), + button("Accept"), + class_name="justify-end gap-2", + ), + class_name="mx-auto w-full max-w-sm", + ) diff --git a/app/www/library/examples/card_image.py b/app/www/library/examples/card_image.py new file mode 100644 index 00000000..fba5e043 --- /dev/null +++ b/app/www/library/examples/card_image.py @@ -0,0 +1,31 @@ +import reflex as rx + +from components.ui.badge import badge +from components.ui.button import button +from components.ui.card import card + + +def card_image() -> rx.Component: + return card.root( + rx.el.div( + class_name="absolute inset-0 z-30 aspect-video bg-black/35", + ), + rx.el.img( + src="https://avatar.vercel.sh/shadcn1", + alt="Event cover", + class_name="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40", + ), + card.header( + card.action( + badge("Featured", variant="secondary"), + ), + card.title("Design systems meetup"), + card.description( + "A practical talk on component APIs, accessibility, and shipping faster." + ), + ), + card.footer( + button("View Event", class_name="w-full"), + ), + class_name="relative mx-auto w-full max-w-sm pt-0", + ) diff --git a/app/www/library/examples/card_small.py b/app/www/library/examples/card_small.py new file mode 100644 index 00000000..25d1e9b8 --- /dev/null +++ b/app/www/library/examples/card_small.py @@ -0,0 +1,61 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.card import card + + +def card_small() -> rx.Component: + feature_name = "Scheduled reports" + + return card.root( + card.header( + card.title(feature_name), + card.description("Weekly snapshots. No more manual exports."), + ), + card.content( + rx.el.ul( + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Choose a schedule (daily, or weekly)."), + class_name="flex gap-2", + ), + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Send to channels or specific teammates."), + class_name="flex gap-2", + ), + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Include charts, tables, and key metrics."), + class_name="flex gap-2", + ), + class_name="grid gap-2 py-2 text-sm", + ) + ), + card.footer( + button( + "Set up scheduled reports", + size="sm", + class_name="w-full", + ), + button( + "See what's new", + variant="outline", + size="sm", + class_name="w-full", + ), + class_name="flex-col gap-2", + ), + size="sm", + class_name="mx-auto w-full max-w-xs my-10", + ) diff --git a/app/www/library/examples/card_spacing.py b/app/www/library/examples/card_spacing.py new file mode 100644 index 00000000..1d5c20ee --- /dev/null +++ b/app/www/library/examples/card_spacing.py @@ -0,0 +1,110 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.ui.button import button +from components.ui.card import card + +selected_card_spacing = ClientStateVar.create("selected_card_spacing", "4") + +spacing_options = [ + { + "class_name": "[--card-spacing:--spacing(4)]", + "label": "16px", + "value": "4", + }, + { + "class_name": "[--card-spacing:--spacing(5)]", + "label": "20px", + "value": "5", + }, + { + "class_name": "[--card-spacing:--spacing(6)]", + "label": "24px", + "value": "6", + }, + { + "class_name": "[--card-spacing:--spacing(8)]", + "label": "32px", + "value": "8", + }, +] + + +def card_spacing() -> rx.Component: + return rx.el.div( + rx.el.div( + *[ + button( + item["label"], + variant="outline", + size="sm", + class_name=rx.cond( + selected_card_spacing.value == item["value"], "!bg-muted", "" + ), + on_click=selected_card_spacing.set_value(item["value"]), + ) + for item in spacing_options + ], + class_name="flex gap-1 justify-start items-center w-full", + ), + card.root( + card.header( + card.title("Login to your account"), + card.description("Enter your email below to login to your account"), + card.action( + button("Sign Up", variant="link"), + ), + ), + card.content( + rx.el.form( + rx.el.div( + rx.el.div( + rx.el.label( + "Email", + html_for="email-spacing", + class_name="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + ), + rx.el.input( + id="email-spacing", + type="email", + placeholder="m@example.com", + required=True, + class_name="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", + ), + class_name="grid gap-2", + ), + rx.el.div( + rx.el.div( + rx.el.label( + "Password", + html_for="password-spacing", + class_name="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + ), + rx.el.a( + "Forgot your password?", + href="#", + class_name="ml-auto inline-block text-sm underline-offset-4 hover:underline", + ), + class_name="flex items-center", + ), + rx.el.input( + id="password-spacing", + type="password", + required=True, + class_name="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", + ), + class_name="grid gap-2", + ), + class_name="flex flex-col gap-6", + ) + ) + ), + card.footer( + button("Login", type="submit", class_name="w-full"), + button("Login with Google", variant="outline", class_name="w-full"), + class_name="flex-col gap-2", + ), + class_name=f"[--card-spacing:--spacing({selected_card_spacing.value})]", + ), + class_name="mx-auto grid w-full max-w-sm gap-4 my-10", + ) diff --git a/app/www/library/examples/checkbox_basic.py b/app/www/library/examples/checkbox_basic.py index d30509d6..1140fd10 100644 --- a/app/www/library/examples/checkbox_basic.py +++ b/app/www/library/examples/checkbox_basic.py @@ -7,12 +7,15 @@ def checkbox_basic(): return rx.el.div( field.root( - checkbox(id="terms-checkbox-basic"), + checkbox.root( + checkbox.indicator(), + id="terms-checkbox-basic", + ), field.label( "Accept terms and conditions", html_for="terms-checkbox-basic", ), orientation="horizontal", ), - class_name="mx-auto w-56", + class_name="mx-auto max-w-sm", ) diff --git a/app/www/library/examples/checkbox_description.py b/app/www/library/examples/checkbox_description.py index fc885370..704cbb91 100644 --- a/app/www/library/examples/checkbox_description.py +++ b/app/www/library/examples/checkbox_description.py @@ -5,14 +5,15 @@ def checkbox_description() -> rx.Component: - return rx.el.div( + return field.group( field.root( - checkbox( + checkbox.root( + checkbox.indicator(), id="terms-checkbox-desc", name="terms-checkbox-desc", default_checked=True, ), - rx.el.div( + field.content( field.label( "Accept terms and conditions", html_for="terms-checkbox-desc", @@ -20,7 +21,6 @@ def checkbox_description() -> rx.Component: field.description( "By clicking this checkbox, you agree to the terms and conditions." ), - class_name="flex flex-col", ), orientation="horizontal", ), diff --git a/app/www/library/examples/checkbox_disabled.py b/app/www/library/examples/checkbox_disabled.py index a403f9f8..06e9b8be 100644 --- a/app/www/library/examples/checkbox_disabled.py +++ b/app/www/library/examples/checkbox_disabled.py @@ -7,7 +7,8 @@ def checkbox_disabled() -> rx.Component: return rx.el.div( field.root( - checkbox( + checkbox.root( + checkbox.indicator(), id="toggle-checkbox-disabled", name="toggle-checkbox-disabled", disabled=True, @@ -17,7 +18,7 @@ def checkbox_disabled() -> rx.Component: html_for="toggle-checkbox-disabled", ), orientation="horizontal", - disabled=True, + data_disabled=True, ), class_name="mx-auto w-56", ) diff --git a/app/www/library/examples/checkbox_group.py b/app/www/library/examples/checkbox_group.py index e84ed236..013899e7 100644 --- a/app/www/library/examples/checkbox_group.py +++ b/app/www/library/examples/checkbox_group.py @@ -16,21 +16,32 @@ def checkbox_group() -> rx.Component: ), rx.el.div( field.root( - checkbox(id="hard-disks", default_checked=True), + checkbox.root( + checkbox.indicator(), + id="hard-disks", + default_checked=True, + ), field.label( "Hard disks", html_for="hard-disks", class_name="font-normal" ), orientation="horizontal", ), field.root( - checkbox(id="ext-disks", default_checked=True), + checkbox.root( + checkbox.indicator(), + id="ext-disks", + default_checked=True, + ), field.label( "External disks", html_for="ext-disks", class_name="font-normal" ), orientation="horizontal", ), field.root( - checkbox(id="cds-dvds"), + checkbox.root( + checkbox.indicator(), + id="cds-dvds", + ), field.label( "CDs, DVDs, and iPods", html_for="cds-dvds", @@ -39,7 +50,10 @@ def checkbox_group() -> rx.Component: orientation="horizontal", ), field.root( - checkbox(id="servers"), + checkbox.root( + checkbox.indicator(), + id="servers", + ), field.label( "Connected servers", html_for="servers", class_name="font-normal" ), diff --git a/app/www/library/examples/collapsible_basic.py b/app/www/library/examples/collapsible_basic.py new file mode 100644 index 00000000..1140773a --- /dev/null +++ b/app/www/library/examples/collapsible_basic.py @@ -0,0 +1,30 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.collapsible import collapsible + + +def collapsible_basic() -> rx.Component: + return rx.el.div( + collapsible.root( + collapsible.trigger( + render_=button( + "How do I update my billing information?", + hi( + "ArrowDown01Icon", + class_name="size-4 ml-auto group-data-panel-open/button:rotate-180", + ), + variant="ghost", + ), + class_name="w-full py-3 text-left border-b border-input", + ), + collapsible.panel( + rx.el.div( + "You can update your card details directly inside your account settings dashboard under the billing tab.", + class_name="py-3 text-sm text-muted-foreground leading-relaxed px-3", + ), + ), + ), + class_name="w-full max-w-sm", + ) diff --git a/app/www/library/examples/collapsible_high_level_demo.py b/app/www/library/examples/collapsible_high_level_demo.py deleted file mode 100644 index efec15f5..00000000 --- a/app/www/library/examples/collapsible_high_level_demo.py +++ /dev/null @@ -1,19 +0,0 @@ -import reflex as rx -from components.ui.button import button -from components.ui.collapsible import collapsible - - -def collapsible_high_level_demo(): - return collapsible( - trigger=button( - "Trigger", - varient="outline", - class_name="w-full", - ), - content=rx.el.p( - "This is the collapsible content. You can put anything here!", - class_name="py-2 text-center", - ), - default_open=False, - class_name="w-full max-w-xs", - ) diff --git a/app/www/library/examples/collapsible_interactive.py b/app/www/library/examples/collapsible_interactive.py new file mode 100644 index 00000000..86f0bd13 --- /dev/null +++ b/app/www/library/examples/collapsible_interactive.py @@ -0,0 +1,69 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.collapsible import collapsible + + +def collapsible_interactive() -> rx.Component: + return rx.el.div( + collapsible.root( + collapsible.trigger( + render_=button( + rx.el.div( + rx.el.div( + "JD", + class_name="flex size-8 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary", + ), + rx.el.div( + rx.el.p( + "John Doe", + class_name="text-sm font-semibold text-foreground text-left leading-none", + ), + rx.el.p( + "pro_plan_member", + class_name="text-[10px] font-mono text-muted-foreground mt-0.5 text-left", + ), + class_name="flex flex-col", + ), + class_name="flex items-center gap-3", + ), + hi( + "ArrowDown01Icon", + class_name="size-4 ml-auto text-muted-foreground transition-transform duration-200 group-data-panel-open/button:rotate-180", + ), + variant="ghost", + class_name="w-full h-14 px-3 hover:bg-muted/50 rounded-xl", + ), + class_name="w-full", + ), + collapsible.panel( + rx.el.div( + rx.el.a( + hi("UserIcon", class_name="size-4 text-muted-foreground"), + rx.el.span("Edit Profile", class_name="text-xs font-medium"), + href="#", + class_name="flex items-center gap-2.5 px-3 py-2 rounded-lg hover:bg-muted/70 text-foreground/80 transition-colors", + ), + rx.el.a( + hi("Settings01Icon", class_name="size-4 text-muted-foreground"), + rx.el.span( + "Security & API Keys", class_name="text-xs font-medium" + ), + href="#", + class_name="flex items-center gap-2.5 px-3 py-2 rounded-lg hover:bg-muted/70 text-foreground/80 transition-colors", + ), + rx.el.a( + hi("Logout01Icon", class_name="size-4 text-destructive/70"), + rx.el.span( + "Log Out", class_name="text-xs font-medium text-destructive" + ), + href="#", + class_name="flex items-center gap-2.5 px-3 py-2 rounded-lg hover:bg-destructive/10 text-destructive transition-colors", + ), + class_name="pt-2 px-1 flex flex-col gap-1 border-t border-border/40 mt-1", + ) + ), + ), + class_name="w-full max-w-xs p-2 bg-background border border-input rounded-2xl shadow-xs", + ) diff --git a/app/www/library/examples/collapsible_low_level_demo.py b/app/www/library/examples/collapsible_low_level_demo.py deleted file mode 100644 index 258b0e69..00000000 --- a/app/www/library/examples/collapsible_low_level_demo.py +++ /dev/null @@ -1,33 +0,0 @@ -import reflex as rx -from components.ui.button import button -from components.ui.collapsible import collapsible - - -def collapsible_low_level_demo(): - return collapsible.root( - collapsible.trigger( - button( - "Collapsible Trigger", - varient="outline", - class_name="w-full", - ), - ), - rx.el.div( - "@radix-ui/primitives", - class_name="rounded-md border border-input px-4 py-2 font-mono text-sm", - ), - collapsible.panel( - rx.el.div( - rx.el.div( - "@radix-ui/colors", - class_name="rounded-md border border-input px-4 py-2 font-mono text-sm", - ), - rx.el.div( - "@stitches/react", - class_name="rounded-md border border-input px-4 py-2 font-mono text-sm", - ), - class_name="flex flex-col gap-4", - ), - ), - class_name="w-full max-w-xs flex flex-col gap-2", - ) diff --git a/app/www/library/examples/collapsible_nested.py b/app/www/library/examples/collapsible_nested.py new file mode 100644 index 00000000..d7081a2e --- /dev/null +++ b/app/www/library/examples/collapsible_nested.py @@ -0,0 +1,81 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.collapsible import collapsible + + +def file_item(name: str, root_level: bool = False) -> rx.Component: + padding_class = "pl-2" if root_level else "pl-9" + + return rx.el.div( + hi("DocumentCodeIcon", class_name="size-4 text-muted-foreground/70 shrink-0"), + rx.el.span(name, class_name="text-sm tracking-tight text-foreground/80"), + class_name=f"flex items-center gap-2 {padding_class} py-1 hover:bg-muted/40 rounded-md transition-colors cursor-pointer", + ) + + +def folder_item(name: str, *children: rx.Component) -> rx.Component: + return collapsible.root( + collapsible.trigger( + render_=button( + hi("FolderIcon", class_name="size-4 text-muted-foreground shrink-0"), + rx.el.span(name, class_name="text-sm font-medium truncate"), + hi( + "ArrowRight01Icon", + class_name="size-3.5 ml-auto text-muted-foreground/70 transition-transform duration-200 group-data-panel-open/button:rotate-90", + ), + variant="ghost", + size="sm", + class_name="w-full justify-start gap-2 h-8 px-2 font-normal hover:bg-muted/60", + ), + class_name="w-full", + ), + collapsible.panel( + rx.el.div( + *children, + class_name="pl-4 border-l border-border/60 ml-4 mt-0.5 flex flex-col gap-0.5", + ), + ), + class_name="w-full", + ) + + +def collapsible_nested() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.span( + "WORKSPACE EXPLORER", + class_name="text-[10px] font-bold tracking-wider text-muted-foreground", + ), + class_name="px-2 pb-2 mb-1 border-b border-border/40", + ), + folder_item( + "src", + folder_item( + "components", + folder_item( + "ui", + file_item("button.py"), + file_item("card.py"), + file_item("collapsible.py"), + ), + file_item("navbar.py"), + file_item("sidebar.py"), + ), + folder_item( + "state", + file_item("base_state.py"), + file_item("auth_state.py"), + ), + file_item("main.py"), + ), + folder_item( + "public", + file_item("favicon.ico"), + file_item("logo.svg"), + ), + file_item("rxconfig.py", root_level=True), + file_item("requirements.txt", root_level=True), + class_name="w-full max-w-xs p-3 bg-background border border-input rounded-xl shadow-xs select-none", + ) diff --git a/app/www/library/examples/context_menu_basic.py b/app/www/library/examples/context_menu_basic.py new file mode 100644 index 00000000..beea05c8 --- /dev/null +++ b/app/www/library/examples/context_menu_basic.py @@ -0,0 +1,30 @@ +import reflex as rx + +from components.ui.context_menu import context_menu + + +def context_menu_basic() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-input border-dashed text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Back"), + context_menu.item("Forward", disabled=True), + context_menu.item("Reload"), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/context_menu_checkboxes.py b/app/www/library/examples/context_menu_checkboxes.py new file mode 100644 index 00000000..29e6c461 --- /dev/null +++ b/app/www/library/examples/context_menu_checkboxes.py @@ -0,0 +1,48 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.context_menu import context_menu + + +def context_menu_checkboxes() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.checkbox_item( + context_menu.checkbox_item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + "Show Bookmarks Bar", + default_checked=True, + ), + context_menu.checkbox_item( + context_menu.checkbox_item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + "Show Full URLs", + ), + context_menu.checkbox_item( + context_menu.checkbox_item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + "Show Developer Tools", + default_checked=True, + ), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/context_menu_groups.py b/app/www/library/examples/context_menu_groups.py new file mode 100644 index 00000000..a1c0f030 --- /dev/null +++ b/app/www/library/examples/context_menu_groups.py @@ -0,0 +1,75 @@ +import reflex as rx + +from components.ui.context_menu import context_menu + + +def context_menu_groups() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.group_label("File"), + context_menu.item( + "New File", + context_menu.shortcut("⌘N"), + ), + context_menu.item( + "Open File", + context_menu.shortcut("⌘O"), + ), + context_menu.item( + "Save", + context_menu.shortcut("⌘S"), + ), + ), + context_menu.separator(), + context_menu.group( + context_menu.group_label("Edit"), + context_menu.item( + "Undo", + context_menu.shortcut("⌘Z"), + ), + context_menu.item( + "Redo", + context_menu.shortcut("⇧⌘Z"), + ), + ), + context_menu.separator(), + context_menu.group( + context_menu.item( + "Cut", + context_menu.shortcut("⌘X"), + ), + context_menu.item( + "Copy", + context_menu.shortcut("⌘C"), + ), + context_menu.item( + "Paste", + context_menu.shortcut("⌘V"), + ), + ), + context_menu.separator(), + context_menu.group( + context_menu.item( + "Delete", + context_menu.shortcut("⌫"), + variant="destructive", + ), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/context_menu_low_level_demo.py b/app/www/library/examples/context_menu_low_level_demo.py deleted file mode 100644 index a5d149a5..00000000 --- a/app/www/library/examples/context_menu_low_level_demo.py +++ /dev/null @@ -1,123 +0,0 @@ -import reflex as rx -from components.ui.context_menu import context_menu - - -def context_menu_low_level_demo(): - return context_menu.root( - context_menu.trigger( - "Right click here", - class_name="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-input text-sm", - ), - context_menu.portal( - context_menu.positioner( - context_menu.popup( - context_menu.item( - rx.flex( - "Back", - rx.text( - "⌘[", - class_name="ml-auto text-xs tracking-widest text-muted-foreground", - ), - class_name="w-full justify-between items-center", - ), - class_name="pl-8", - ), - context_menu.item( - rx.flex( - "Forward", - rx.text( - "⌘]", - class_name="ml-auto text-xs tracking-widest text-muted-foreground", - ), - class_name="w-full justify-between items-center", - ), - disabled=True, - class_name="pl-8", - ), - context_menu.item( - rx.flex( - "Reload", - rx.text( - "⌘R", - class_name="ml-auto text-xs tracking-widest text-muted-foreground", - ), - class_name="w-full justify-between items-center", - ), - class_name="pl-8", - ), - context_menu.submenu_root( - context_menu.submenu_trigger( - rx.flex( - "More Tools", - rx.icon( - "chevron-right", - class_name="ml-auto text-xs tracking-widest text-muted-foreground", - ), - class_name="w-full justify-between items-center", - ), - class_name="pl-8", - ), - context_menu.portal( - context_menu.positioner( - context_menu.popup( - context_menu.item("Save Page..."), - context_menu.item("Create Shortcut..."), - context_menu.item("Name Window..."), - context_menu.separator(), - context_menu.item("Developer Tools"), - context_menu.separator(), - context_menu.item( - "Delete", - class_name="text-destructive focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive", - ), - class_name="w-44", - ), - ), - ), - ), - context_menu.separator(), - context_menu.checkbox_item( - context_menu.checkbox_item_indicator( - rx.icon(tag="check", class_name="size-4"), - ), - "Show Bookmarks", - ), - context_menu.checkbox_item( - context_menu.checkbox_item_indicator( - rx.icon(tag="check", class_name="size-4"), - ), - "Show Full URLs", - ), - context_menu.separator(), - context_menu.radio_group( - context_menu.group( - context_menu.group_label( - "People", - class_name="pl-8", - ), - context_menu.radio_item( - context_menu.radio_item_indicator( - rx.icon( - tag="circle", class_name="size-2 fill-current" - ), - ), - "Pedro Duarte", - value="pedro", - ), - context_menu.radio_item( - context_menu.radio_item_indicator( - rx.icon( - tag="circle", class_name="size-2 fill-current" - ), - ), - "Colm Tuite", - value="colm", - ), - ), - value="pedro", - ), - class_name="w-52", - ), - ), - ), - ) diff --git a/app/www/library/examples/context_menu_radio.py b/app/www/library/examples/context_menu_radio.py new file mode 100644 index 00000000..a6b19244 --- /dev/null +++ b/app/www/library/examples/context_menu_radio.py @@ -0,0 +1,80 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.icons.hugeicon import hi +from components.ui.context_menu import context_menu + +selected_user = ClientStateVar.create("selected_user", "pedro") +selected_theme = ClientStateVar.create("selected_theme", "light") + + +def context_menu_radio() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.group_label("People"), + context_menu.radio_group( + context_menu.radio_item( + context_menu.radio_item_indicator( + hi("CheckIcon", class_name="size-4") + ), + "Pedro Duarte", + value="pedro", + ), + context_menu.radio_item( + context_menu.radio_item_indicator( + hi("CheckIcon", class_name="size-4") + ), + "Colm Tuite", + value="colm", + ), + value=selected_user.value, + on_value_change=selected_user.set_value, + ), + ), + context_menu.separator(), + context_menu.group( + context_menu.group_label("Theme"), + context_menu.radio_group( + context_menu.radio_item( + context_menu.radio_item_indicator( + hi("CheckIcon", class_name="size-4") + ), + "Light", + value="light", + ), + context_menu.radio_item( + context_menu.radio_item_indicator( + hi("CheckIcon", class_name="size-4") + ), + "Dark", + value="dark", + ), + context_menu.radio_item( + context_menu.radio_item_indicator( + hi("CheckIcon", class_name="size-4") + ), + "System", + value="system", + ), + value=selected_theme.value, + on_value_change=selected_theme.set_value, + ), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/context_menu_shortcuts.py b/app/www/library/examples/context_menu_shortcuts.py new file mode 100644 index 00000000..c90fcf5c --- /dev/null +++ b/app/www/library/examples/context_menu_shortcuts.py @@ -0,0 +1,51 @@ +import reflex as rx + +from components.ui.context_menu import context_menu + + +def context_menu_shortcuts() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item( + "Back", + context_menu.shortcut("⌘["), + ), + context_menu.item( + "Forward", + context_menu.shortcut("⌘]"), + disabled=True, + ), + context_menu.item( + "Reload", + context_menu.shortcut("⌘R"), + ), + ), + context_menu.separator(), + context_menu.group( + context_menu.item( + "Save", + context_menu.shortcut("⌘S"), + ), + context_menu.item( + "Save As...", + context_menu.shortcut("⇧⌘S"), + ), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/context_menu_sides.py b/app/www/library/examples/context_menu_sides.py new file mode 100644 index 00000000..99c70cc9 --- /dev/null +++ b/app/www/library/examples/context_menu_sides.py @@ -0,0 +1,109 @@ +import reflex as rx + +from components.ui.context_menu import context_menu + + +def context_menu_sides() -> rx.Component: + return rx.el.div( + context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click (top)", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press (top)", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Back"), + context_menu.item("Forward"), + context_menu.item("Reload"), + ), + ), + side="top", + ), + ), + ), + context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click (right)", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press (right)", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Back"), + context_menu.item("Forward"), + context_menu.item("Reload"), + ), + ), + side="right", + ), + ), + ), + context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click (bottom)", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press (bottom)", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Back"), + context_menu.item("Forward"), + context_menu.item("Reload"), + ), + ), + side="bottom", + ), + ), + ), + context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click (left)", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press (left)", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Back"), + context_menu.item("Forward"), + context_menu.item("Reload"), + ), + ), + side="left", + ), + ), + ), + class_name="grid w-full max-w-sm grid-cols-2 gap-4", + ) diff --git a/app/www/library/examples/context_menu_submenu.py b/app/www/library/examples/context_menu_submenu.py new file mode 100644 index 00000000..34206abb --- /dev/null +++ b/app/www/library/examples/context_menu_submenu.py @@ -0,0 +1,57 @@ +import reflex as rx + +from components.ui.context_menu import context_menu + + +def context_menu_submenu() -> rx.Component: + return context_menu.root( + context_menu.trigger( + rx.el.span( + "Right click here", + class_name="hidden pointer-fine:inline-block", + ), + rx.el.span( + "Long press here", + class_name="hidden pointer-coarse:inline-block", + ), + class_name="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed border-input text-sm", + ), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item( + "Copy", + context_menu.shortcut("⌘C"), + ), + context_menu.item( + "Cut", + context_menu.shortcut("⌘X"), + ), + ), + context_menu.sub( + context_menu.sub_trigger("More Tools"), + context_menu.positioner( + context_menu.popup( + context_menu.group( + context_menu.item("Save Page..."), + context_menu.item("Create Shortcut..."), + context_menu.item("Name Window..."), + ), + context_menu.separator(), + context_menu.group( + context_menu.item("Developer Tools"), + ), + context_menu.separator(), + context_menu.group( + context_menu.item("Delete", variant="destructive"), + ), + class_name="shadow-lg", + ), + side="right", + ), + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/dialog_close_button.py b/app/www/library/examples/dialog_close_button.py new file mode 100644 index 00000000..3b59ee7f --- /dev/null +++ b/app/www/library/examples/dialog_close_button.py @@ -0,0 +1,48 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.dialog import dialog +from components.ui.input import input + + +def dialog_close_button() -> rx.Component: + return dialog.root( + dialog.trigger(render_=button("Share", variant="outline")), + dialog.portal( + dialog.backdrop(), + dialog.popup( + dialog.header( + dialog.title("Share link"), + dialog.description( + "Anyone who has this link will be able to view this." + ), + ), + rx.el.div( + rx.el.div( + rx.el.label("Link", html_for="link", class_name="sr-only"), + input( + id="link", + default_value="https://ui.buridan.com/docs/getting-started/installation", + read_only=True, + ), + class_name="grid flex-1 gap-2", + ), + class_name="flex items-center gap-2", + ), + dialog.footer( + dialog.close(render_=button("Close", type="button")), + class_name="sm:justify-start", + ), + dialog.close( + render_=button( + hi("Cancel01Icon", class_name="size-4"), + variant="ghost", + size="icon-sm", + class_name=dialog.class_names.CLOSE, + ) + ), + class_name="sm:max-w-md", + ), + ), + ) diff --git a/app/www/library/examples/dialog_high_level.py b/app/www/library/examples/dialog_high_level.py deleted file mode 100644 index 505441a6..00000000 --- a/app/www/library/examples/dialog_high_level.py +++ /dev/null @@ -1,18 +0,0 @@ -import reflex as rx - -from components.ui.button import button -from components.ui.dialog import dialog - - -def dialog_high_level(): - return dialog( - trigger=button("Open Dialog", variant="outline"), - title="Are you absolutely sure?", - description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.", - content=rx.flex( - button("Cancel", variant="outline", class_name="flex-1"), - button("Continue", class_name="flex-1"), - class_name="flex gap-2 w-full", - ), - class_name="!w-full max-w-md", - ) diff --git a/app/www/library/examples/dialog_low_level.py b/app/www/library/examples/dialog_low_level.py deleted file mode 100644 index c2f16e84..00000000 --- a/app/www/library/examples/dialog_low_level.py +++ /dev/null @@ -1,56 +0,0 @@ -import reflex as rx - -from components.ui.button import button -from components.ui.dialog import dialog -from components.ui.input import input - - -def dialog_low_level(): - return dialog.root( - dialog.trigger(render_=button("Open Dialog")), - dialog.portal( - dialog.backdrop(class_name="backdrop-blur-[5px]"), - dialog.popup( - rx.el.div( - rx.el.div( - dialog.title("Edit Profile"), - dialog.close( - render_=button( - rx.icon("x", class_name="size-4"), - variant="ghost", - size="icon-sm", - class_name="text-secondary-11", - ), - ), - class_name="flex justify-between items-baseline gap-1", - ), - dialog.description( - "Make changes to your profile here. Click save when you're done." - ), - class_name="flex flex-col gap-2", - ), - # Content section - rx.el.div( - rx.el.div( - rx.el.p("Name", class_name="text-sm font-medium mb-2"), - input(placeholder="Enter your name"), - ), - rx.el.div( - rx.el.p("Email", class_name="text-sm font-medium mb-2"), - input(placeholder="Enter your email", type="email"), - ), - rx.el.div( - dialog.close( - render_=button( - "Cancel", variant="outline", class_name="flex-1" - ), - ), - button("Save Changes", class_name="flex-1"), - class_name="flex gap-2 w-full", - ), - class_name="flex flex-col gap-4", - ), - class_name="!w-full max-w-lg", - ), - ), - ) diff --git a/app/www/library/examples/dialog_no_close_button.py b/app/www/library/examples/dialog_no_close_button.py new file mode 100644 index 00000000..f6aa420c --- /dev/null +++ b/app/www/library/examples/dialog_no_close_button.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.dialog import dialog + + +def dialog_no_close_button() -> rx.Component: + return dialog.root( + dialog.trigger(render_=button("No Close Button", variant="outline")), + dialog.portal( + dialog.backdrop(), + dialog.popup( + dialog.header( + dialog.title("No Close Button"), + dialog.description( + "This dialog doesn't have a close button in the top-right corner." + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/dialog_sticky_footer.py b/app/www/library/examples/dialog_sticky_footer.py new file mode 100644 index 00000000..c32eb4d4 --- /dev/null +++ b/app/www/library/examples/dialog_sticky_footer.py @@ -0,0 +1,49 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.dialog import dialog + + +def dialog_sticky_footer() -> rx.Component: + return dialog.root( + dialog.trigger(render_=button("Sticky Footer", variant="outline")), + dialog.portal( + dialog.backdrop(), + dialog.popup( + dialog.header( + dialog.title("Sticky Footer"), + dialog.description( + "This dialog has a sticky footer that stays visible while the content scrolls." + ), + ), + rx.el.div( + *[ + rx.el.p( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat " + "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " + "sunt in culpa qui officia deserunt mollit anim id est laborum.", + class_name="mb-4 leading-normal", + ) + for _ in range(10) + ], + class_name="-mx-4 no-scrollbar max-h-[50vh] overflow-y-auto px-4", + ), + dialog.footer( + dialog.close(render_=button("Close", variant="outline")), + ), + dialog.close( + render_=button( + hi("Cancel01Icon", class_name="size-4"), + variant="ghost", + size="icon-sm", + class_name=dialog.class_names.CLOSE, + ) + ), + ), + ), + ) diff --git a/app/www/library/examples/field_general.py b/app/www/library/examples/field_general.py index 98b49fe2..44eca5c6 100644 --- a/app/www/library/examples/field_general.py +++ b/app/www/library/examples/field_general.py @@ -4,89 +4,174 @@ from components.ui.checkbox import checkbox from components.ui.field import field from components.ui.input import input +from components.ui.select import select from components.ui.textarea import textarea +months = [ + {"label": "MM", "value": ""}, + {"label": "01", "value": "01"}, + {"label": "02", "value": "02"}, + {"label": "03", "value": "03"}, + {"label": "04", "value": "04"}, + {"label": "05", "value": "05"}, + {"label": "06", "value": "06"}, + {"label": "07", "value": "07"}, + {"label": "08", "value": "08"}, + {"label": "09", "value": "09"}, + {"label": "10", "value": "10"}, + {"label": "11", "value": "11"}, + {"label": "12", "value": "12"}, +] + +years = [ + {"label": "YYYY", "value": ""}, + {"label": "2024", "value": "2024"}, + {"label": "2025", "value": "2025"}, + {"label": "2026", "value": "2026"}, + {"label": "2027", "value": "2027"}, + {"label": "2028", "value": "2028"}, + {"label": "2029", "value": "2029"}, +] + def field_demo() -> rx.Component: return rx.el.div( rx.el.form( - rx.el.div( - rx.el.fieldset( - rx.el.legend("Payment Method", class_name="font-medium mb-1"), - rx.el.p( - "All transactions are secure and encrypted", - class_name="text-sm text-muted-foreground mb-4", - ), - rx.el.div( + field.group( + field.set( + field.legend("Payment Method"), + field.description("All transactions are secure and encrypted"), + field.group( field.root( - field.label("Name on Card", html_for="name"), - field.control( - render_=input( - id="name", - placeholder="Evil Rabbit", - class_name="!w-full", - ), - class_name="w-full", + field.label("Name on Card", html_for="checkout-card-name"), + input( + id="checkout-card-name", + placeholder="Evil Rabbit", + required=True, ), - orientation="vertical", ), field.root( - field.label("Card Number", html_for="card"), - field.control( - render_=rx.el.div( - input( - id="card", - placeholder="1234 5678 9012 3456", - class_name="!w-full", + field.label("Card Number", html_for="checkout-card-number"), + input( + id="checkout-card-number", + placeholder="1234 5678 9012 3456", + required=True, + ), + field.description("Enter your 16-digit card number"), + ), + rx.el.div( + field.root( + field.label("Month", html_for="checkout-exp-month"), + select.root( + select.trigger( + select.value(), + select.icon(), + id="checkout-exp-month", ), - class_name="w-full flex-1", + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text( + item["label"] + ), + select.item_indicator(), + value=item["value"], + ) + for item in months + ] + ) + ) + ) + ), + items=months, + default_value="MM", ), - class_name="w-full", ), - field.description("Enter your 16-digit card number"), - orientation="vertical", + field.root( + field.label("Year", html_for="checkout-exp-year"), + select.root( + select.trigger( + select.value(), + select.icon(), + id="checkout-exp-year", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text( + item["label"] + ), + select.item_indicator(), + value=item["value"], + ) + for item in years + ] + ) + ) + ) + ), + items=years, + default_value="YYYY", + ), + ), + field.root( + field.label("CVV", html_for="checkout-cvv"), + input( + id="checkout-cvv", + placeholder="123", + required=True, + ), + ), + class_name="grid grid-cols-3 gap-4", ), - class_name="flex flex-col w-full gap-y-2", ), ), - rx.el.fieldset( - rx.el.legend("Billing Address", class_name="font-medium mb-2"), - rx.el.p( - "The billing address associated with your payment method", - class_name="text-sm text-muted-foreground mb-3", + field.separator(), + field.set( + field.legend("Billing Address"), + field.description( + "The billing address associated with your payment method" ), - field.root( - field.control(render_=checkbox()), - field.label( - "Same as shipping address", - html_for="shipping", - class_name="text-sm", - ), - orientation="horizontal", + field.group( + field.root( + checkbox.root( + checkbox.indicator(), + id="checkout-same-as-shipping", + default_checked=True, + ), + field.label( + "Same as shipping address", + html_for="checkout-same-as-shipping", + class_name="font-normal", + ), + orientation="horizontal", + ) ), ), - field.root( - field.label("Comments", html_for="comments"), - field.control( - render_=textarea( - id="comments", - placeholder="Add any additional comments", - class_name="resize-none", + field.set( + field.group( + field.root( + field.label("Comments", html_for="checkout-comments"), + textarea( + id="checkout-comments", + placeholder="Add any additional comments", + class_name="resize-none", + ), ) - ), - orientation="vertical", + ) ), field.root( - rx.el.div( - button("Submit", type="submit"), - button("Cancel", variant="outline", type="button"), - class_name="flex flex-row gap-x-4", - ), + button("Submit", type="submit"), + button("Cancel", variant="outline", type="button"), orientation="horizontal", ), - class_name="w-full flex flex-col gap-y-4", - ), - class_name="w-full flex", + ) ), - class_name="w-full max-w-md py-4", + class_name="w-full max-w-md my-10", ) diff --git a/app/www/library/examples/input_group_block_end.py b/app/www/library/examples/input_group_block_end.py new file mode 100644 index 00000000..a979eca0 --- /dev/null +++ b/app/www/library/examples/input_group_block_end.py @@ -0,0 +1,51 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.input_group import input_group + + +def input_group_block_end() -> rx.Component: + return rx.el.div( + field.root( + field.label("Input", html_for="block-end-input"), + field.content( + input_group.root( + input_group.input( + id="block-end-input", + placeholder="Enter amount", + ), + input_group.addon( + input_group.text("USD"), + align="block-end", + ), + class_name="h-auto", + ) + ), + field.description("Footer positioned below the input."), + orientation="vertical", + ), + field.root( + field.label("Textarea", html_for="block-end-textarea"), + field.content( + input_group.root( + input_group.textarea( + id="block-end-textarea", + placeholder="Write a comment...", + ), + input_group.addon( + input_group.text("0/280"), + input_group.button( + "Post", + variant="default", + size="sm", + class_name="ml-auto", + ), + align="block-end", + ), + ) + ), + field.description("Footer positioned below the textarea."), + orientation="vertical", + ), + class_name="flex flex-col gap-y-6 max-w-sm w-full", + ) diff --git a/app/www/library/examples/input_group_block_start.py b/app/www/library/examples/input_group_block_start.py new file mode 100644 index 00000000..79742cd4 --- /dev/null +++ b/app/www/library/examples/input_group_block_start.py @@ -0,0 +1,54 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.field import field +from components.ui.input_group import input_group + + +def input_group_block_start() -> rx.Component: + return rx.el.div( + field.root( + field.label("Input", html_for="block-start-input"), + field.content( + input_group.root( + input_group.input( + id="block-start-input", + placeholder="Enter your name", + ), + input_group.addon( + input_group.text("Full Name"), + align="block-start", + ), + class_name="h-auto", + ) + ), + field.description("Header positioned above the input."), + orientation="vertical", + ), + field.root( + field.label("Textarea", html_for="block-start-textarea"), + field.content( + input_group.root( + input_group.textarea( + id="block-start-textarea", + placeholder="console.log('Hello, world!');", + class_name="font-mono text-sm", + ), + input_group.addon( + hi("FileCodeIcon", class_name="text-muted-foreground"), + input_group.text("script.py", class_name="font-mono"), + input_group.button( + hi("CopyIcon"), + rx.el.span("Copy", class_name="sr-only"), + size="icon-xs", + class_name="ml-auto", + ), + align="block-start", + ), + ) + ), + field.description("Header positioned above the textarea."), + orientation="vertical", + ), + class_name="flex flex-col gap-y-6 max-w-sm w-full", + ) diff --git a/app/www/library/examples/input_group_button.py b/app/www/library/examples/input_group_button.py new file mode 100644 index 00000000..de82b599 --- /dev/null +++ b/app/www/library/examples/input_group_button.py @@ -0,0 +1,82 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group + +_input_copy = ClientStateVar.create("_input_copy", False) +_input_star = ClientStateVar.create("_input_star", False) + + +def input_group_button() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.input( + placeholder="https://ui.buridan.dev/", + read_only=True, + ), + input_group.addon( + input_group.button( + rx.cond( + _input_copy.value, + hi("Tick02Icon"), + hi("Copy01Icon"), + ), + aria_label="Copy", + title="Copy", + size="icon-xs", + on_click=[ + _input_copy.set_value(True), + rx.set_clipboard("https://ui.buridan.dev/"), + ], + on_mouse_down=rx.call_function( + _input_copy.set_value(False) + ).debounce(1500), + ), + align="inline-end", + ), + ), + input_group.root( + input_group.addon( + input_group.button( + hi("InformationCircleIcon"), + variant="secondary", + size="icon-xs", + ), + align="inline-start", + ), + input_group.addon( + "https://", + class_name="pl-1.5 text-muted-foreground", + align="inline-start", + ), + input_group.input(id="input-secure-19"), + input_group.addon( + input_group.button( + hi( + "StarIcon", + class_name=rx.cond( + _input_star.value, + "text-blue-600 fill-blue-600", + "", + ), + ), + size="icon-xs", + on_click=_input_star.set_value(~_input_star.value), + ), + align="inline-end", + ), + class_name="[--radius:9999px]", + ), + input_group.root( + input_group.input(placeholder="Type to search..."), + input_group.addon( + input_group.button( + "Search", + variant="secondary", + ), + align="inline-end", + ), + ), + class_name="grid w-full max-w-sm gap-6", + ) diff --git a/app/www/library/examples/input_group_dropdown.py b/app/www/library/examples/input_group_dropdown.py new file mode 100644 index 00000000..2a0e9d59 --- /dev/null +++ b/app/www/library/examples/input_group_dropdown.py @@ -0,0 +1,67 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group +from components.ui.menu import menu + + +def input_group_dropdown() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.input(placeholder="Enter file name"), + input_group.addon( + menu.root( + menu.trigger( + render_=input_group.button( + hi("MoreHorizontal"), + variant="ghost", + aria_label="More", + size="icon-xs", + ) + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item("Settings"), + menu.item("Copy path"), + menu.item("Open location"), + ) + ), + align="end", + ) + ), + ), + align="inline-end", + ), + ), + input_group.root( + input_group.input(placeholder="Enter search query"), + input_group.addon( + menu.root( + menu.trigger( + render_=input_group.button( + "Search In... ", + hi("ChevronDownIcon", class_name="size-3 ml-1 inline"), + variant="ghost", + class_name="!pr-1.5 text-xs", + ) + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item("Documentation"), + menu.item("Blog Posts"), + menu.item("Changelog"), + ) + ), + align="end", + ) + ), + ), + align="inline-end", + ), + ), + class_name="grid w-full max-w-sm gap-4", + ) diff --git a/app/www/library/examples/input_group_email_input.py b/app/www/library/examples/input_group_email_input.py deleted file mode 100644 index 101dae12..00000000 --- a/app/www/library/examples/input_group_email_input.py +++ /dev/null @@ -1,14 +0,0 @@ -import reflex as rx -from components.ui.input import input - - -def input_group_email_input(): - return rx.el.div( - rx.el.p("Email Input", class_name="text-sm font-medium mb-2"), - input( - type="email", - placeholder="name@example.com", - ), - class_name="w-full max-w-md p-8", - ) - diff --git a/app/www/library/examples/input_group_icons.py b/app/www/library/examples/input_group_icons.py new file mode 100644 index 00000000..ea18fe69 --- /dev/null +++ b/app/www/library/examples/input_group_icons.py @@ -0,0 +1,43 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group + + +def input_group_icons() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.input(placeholder="Search..."), + input_group.addon( + hi("Search01Icon", class_name="text-muted-foreground"), + align="inline-start", + ), + ), + input_group.root( + input_group.input(type="email", placeholder="Enter your email"), + input_group.addon( + hi("Mail01Icon", class_name="text-muted-foreground"), + align="inline-start", + ), + ), + input_group.root( + input_group.input(placeholder="Card number"), + input_group.addon( + hi("CreditCardIcon", class_name="text-muted-foreground"), + align="inline-start", + ), + input_group.addon( + hi("Tick02Icon", class_name="text-muted-foreground"), + align="inline-end", + ), + ), + input_group.root( + input_group.input(placeholder="Card number"), + input_group.addon( + hi("StarIcon", class_name="text-muted-foreground"), + hi("InformationCircleIcon", class_name="text-muted-foreground"), + align="inline-end", + ), + ), + class_name="grid w-full max-w-sm gap-6", + ) diff --git a/app/www/library/examples/input_group_inline_end.py b/app/www/library/examples/input_group_inline_end.py new file mode 100644 index 00000000..41567e85 --- /dev/null +++ b/app/www/library/examples/input_group_inline_end.py @@ -0,0 +1,19 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group + + +def input_group_inline_end() -> rx.Component: + return input_group.root( + input_group.input( + id="inline-end-input", + type="password", + placeholder="Enter password", + ), + input_group.addon( + hi("EyeOffIcon", class_name="text-muted-foreground"), + align="inline-end", + ), + class_name="max-w-sm", + ) diff --git a/app/www/library/examples/input_group_inline_start.py b/app/www/library/examples/input_group_inline_start.py new file mode 100644 index 00000000..213fad12 --- /dev/null +++ b/app/www/library/examples/input_group_inline_start.py @@ -0,0 +1,18 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group + + +def input_group_inline_start() -> rx.Component: + return input_group.root( + input_group.input( + id="inline-start-input", + placeholder="Search...", + ), + input_group.addon( + hi("SearchIcon", class_name="text-muted-foreground"), + align="inline-start", + ), + class_name="max-w-sm", + ) diff --git a/app/www/library/examples/input_group_price_input.py b/app/www/library/examples/input_group_price_input.py deleted file mode 100644 index e6f38b09..00000000 --- a/app/www/library/examples/input_group_price_input.py +++ /dev/null @@ -1,14 +0,0 @@ -import reflex as rx - -from components.ui.input_group import input_with_addons - - -def input_group_price_input(): - return rx.el.div( - input_with_addons( - placeholder="0.00", - prefix="$", - suffix="USD", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) diff --git a/app/www/library/examples/input_group_spinner.py b/app/www/library/examples/input_group_spinner.py new file mode 100644 index 00000000..86bdcafa --- /dev/null +++ b/app/www/library/examples/input_group_spinner.py @@ -0,0 +1,46 @@ +import reflex as rx + +from components.ui.input_group import input_group +from components.ui.spinner import spinner + + +def input_group_spinner() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.input(placeholder="Searching..."), + input_group.addon( + spinner(), + align="inline-end", + ), + ), + input_group.root( + input_group.input(placeholder="Processing..."), + input_group.addon( + spinner(), + align="inline-start", + ), + ), + input_group.root( + input_group.input(placeholder="Saving changes..."), + input_group.addon( + input_group.text("Saving..."), + spinner(), + align="inline-end", + ), + ), + input_group.root( + input_group.input(placeholder="Refreshing data..."), + input_group.addon( + spinner(), + align="inline-start", + ), + input_group.addon( + input_group.text( + "Please wait...", + class_name="text-muted-foreground", + ), + align="inline-end", + ), + ), + class_name="grid w-full max-w-sm gap-4", + ) diff --git a/app/www/library/examples/input_group_text.py b/app/www/library/examples/input_group_text.py new file mode 100644 index 00000000..c41b2185 --- /dev/null +++ b/app/www/library/examples/input_group_text.py @@ -0,0 +1,51 @@ +import reflex as rx + +from components.ui.input_group import input_group + + +def input_group_text() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.addon( + input_group.text("$"), + align="inline-start", + ), + input_group.input(placeholder="0.00"), + input_group.addon( + input_group.text("USD"), + align="inline-end", + ), + ), + input_group.root( + input_group.addon( + input_group.text("https://"), + align="inline-start", + ), + input_group.input( + placeholder="example.com", + class_name="!pl-0.5", + ), + input_group.addon( + input_group.text(".com"), + align="inline-end", + ), + ), + input_group.root( + input_group.input(placeholder="Enter your username"), + input_group.addon( + input_group.text("@company.com"), + align="inline-end", + ), + ), + input_group.root( + input_group.textarea(placeholder="Enter your message"), + input_group.addon( + input_group.text( + "120 characters left", + class_name="text-xs text-muted-foreground", + ), + align="block-end", + ), + ), + class_name="grid w-full max-w-sm gap-6", + ) diff --git a/app/www/library/examples/input_group_textarea_with_footer.py b/app/www/library/examples/input_group_textarea_with_footer.py deleted file mode 100644 index 0fadd352..00000000 --- a/app/www/library/examples/input_group_textarea_with_footer.py +++ /dev/null @@ -1,13 +0,0 @@ -import reflex as rx - -from components.ui.input_group import textarea_with_footer - - -def input_group_textarea_with_footer(): - return rx.el.div( - textarea_with_footer( - placeholder="Enter your message", - footer_text="120 characters left", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) diff --git a/app/www/library/examples/input_group_url_input.py b/app/www/library/examples/input_group_url_input.py deleted file mode 100644 index 5985d9af..00000000 --- a/app/www/library/examples/input_group_url_input.py +++ /dev/null @@ -1,14 +0,0 @@ -import reflex as rx - -from components.ui.input_group import input_with_addons - - -def input_group_url_input(): - return rx.el.div( - input_with_addons( - placeholder="example.com", - prefix="https://", - suffix=".com", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) diff --git a/app/www/library/examples/kbd_button.py b/app/www/library/examples/kbd_button.py new file mode 100644 index 00000000..b3361657 --- /dev/null +++ b/app/www/library/examples/kbd_button.py @@ -0,0 +1,16 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.kbd import kbd + + +def kbd_button() -> rx.Component: + return button( + "Accept ", + kbd.root( + "⏎", + data_icon="inline-end", + class_name="translate-x-0.5", + ), + variant="outline", + ) diff --git a/app/www/library/examples/kbd_common_shortcuts.py b/app/www/library/examples/kbd_common_shortcuts.py deleted file mode 100644 index f2577b15..00000000 --- a/app/www/library/examples/kbd_common_shortcuts.py +++ /dev/null @@ -1,49 +0,0 @@ -import reflex as rx -from components.ui.kbd import ( - kbd, - kbd_group, -) - - -def kbd_common_shortcuts(): - """Common keyboard shortcuts""" - return rx.box( - rx.box( - rx.text("Save:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("S"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Copy:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("C"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Paste:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("V"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Undo:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("Z"), - ), - class_name="flex items-center", - ), - class_name="flex flex-col gap-3 p-8", - ) - diff --git a/app/www/library/examples/kbd_complex_shortcuts.py b/app/www/library/examples/kbd_complex_shortcuts.py deleted file mode 100644 index 532b4f48..00000000 --- a/app/www/library/examples/kbd_complex_shortcuts.py +++ /dev/null @@ -1,41 +0,0 @@ -import reflex as rx -from components.ui.kbd import ( - kbd, - kbd_group, -) - - -def kbd_complex_shortcuts(): - """Complex multi-key shortcuts""" - return rx.box( - # Three modifier keys - rx.box( - rx.text("Screenshot:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("Shift"), - rx.el.span("+"), - kbd("S"), - ), - class_name="flex items-center mb-3", - ), - # Mac command - rx.box( - rx.text("Quit:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("⌘"), - rx.el.span("+"), - kbd("Q"), - ), - class_name="flex items-center mb-3", - ), - # Function key - rx.box( - rx.text("Full Screen:", class_name="text-sm font-medium mr-2"), - kbd("F11"), - class_name="flex items-center", - ), - class_name="p-8", - ) - diff --git a/app/www/library/examples/kbd_default.py b/app/www/library/examples/kbd_default.py deleted file mode 100644 index c78b33d3..00000000 --- a/app/www/library/examples/kbd_default.py +++ /dev/null @@ -1,29 +0,0 @@ -import reflex as rx -from components.ui.kbd import ( - kbd, - kbd_group, -) - - -def kbd_default(): - """ - Example matching the shadcn KbdDemo component. - Shows keyboard shortcuts with modifier keys. - """ - return rx.box( - # Mac modifier keys - kbd_group( - kbd("⌘"), - kbd("⇧"), - kbd("⌥"), - kbd("⌃"), - ), - # Keyboard shortcut combination - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("B"), - ), - class_name="flex flex-col items-center gap-4 p-8", - ) - diff --git a/app/www/library/examples/kbd_group.py b/app/www/library/examples/kbd_group.py new file mode 100644 index 00000000..3b7caa7d --- /dev/null +++ b/app/www/library/examples/kbd_group.py @@ -0,0 +1,18 @@ +import reflex as rx + +from components.ui.kbd import kbd + + +def kbd_as_group() -> rx.Component: + return rx.el.div( + rx.el.p( + "Use ", + kbd.group( + kbd.root("Ctrl + B"), + kbd.root("Ctrl + K"), + ), + " to open the command palette", + class_name="text-sm text-muted-foreground", + ), + class_name="flex flex-col items-center gap-4", + ) diff --git a/app/www/library/examples/kbd_input_group.py b/app/www/library/examples/kbd_input_group.py new file mode 100644 index 00000000..238ac4bd --- /dev/null +++ b/app/www/library/examples/kbd_input_group.py @@ -0,0 +1,23 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.input_group import input_group +from components.ui.kbd import kbd + + +def kbd_input_group() -> rx.Component: + return rx.el.div( + input_group.root( + input_group.input(placeholder="Search..."), + input_group.addon( + hi("Search01Icon"), + align="inline-start", + ), + input_group.addon( + kbd.root("⌘"), + kbd.root("K"), + align="inline-end", + ), + ), + class_name="flex w-full max-w-xs flex-col gap-6", + ) diff --git a/app/www/library/examples/kbd_special_keys.py b/app/www/library/examples/kbd_special_keys.py deleted file mode 100644 index b0f26104..00000000 --- a/app/www/library/examples/kbd_special_keys.py +++ /dev/null @@ -1,22 +0,0 @@ -import reflex as rx -from components.ui.kbd import ( - kbd, -) - - -def kbd_special_keys(): - """Special key examples""" - return rx.box( - kbd("Enter"), - kbd("Esc"), - kbd("Tab"), - kbd("Space"), - kbd("←"), - kbd("→"), - kbd("↑"), - kbd("↓"), - kbd("Delete"), - kbd("Backspace"), - class_name="flex flex-wrap gap-2 p-8", - ) - diff --git a/app/www/library/examples/kbd_tooltip.py b/app/www/library/examples/kbd_tooltip.py new file mode 100644 index 00000000..3707f48a --- /dev/null +++ b/app/www/library/examples/kbd_tooltip.py @@ -0,0 +1,51 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.button_group import button_group +from components.ui.kbd import kbd +from components.ui.tooltip import tooltip + + +def kbd_tooltip() -> rx.Component: + return rx.el.div( + button_group.root( + tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=button("Save", variant="outline"), + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup( + tooltip.arrow(), + "Save Changes ", + kbd.root("S"), + ), + ), + ), + ), + delay=0, + ), + tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=button("Print", variant="outline"), + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup( + tooltip.arrow(), + "Print Document ", + kbd.group( + kbd.root("Ctrl"), + kbd.root("P"), + ), + ), + ), + ), + ), + delay=0, + ), + ), + class_name="flex flex-wrap gap-4", + ) diff --git a/app/www/library/examples/kbd_with_icons.py b/app/www/library/examples/kbd_with_icons.py deleted file mode 100644 index 5213971a..00000000 --- a/app/www/library/examples/kbd_with_icons.py +++ /dev/null @@ -1,27 +0,0 @@ -import reflex as rx -from components.ui.kbd import ( - kbd, - kbd_group, -) - - -def kbd_with_icons(): - """Kbd with icons""" - return rx.box( - kbd_group( - kbd( - rx.icon(tag="command", size=12), - ), - rx.el.span("+"), - kbd("K"), - ), - kbd_group( - kbd( - rx.icon(tag="arrow-left", size=12), - ), - kbd( - rx.icon(tag="arrow-right", size=12), - ), - ), - class_name="flex flex-col items-center gap-4 p-8", - ) diff --git a/app/www/library/examples/link_general.py b/app/www/library/examples/link_general.py deleted file mode 100644 index 876689dc..00000000 --- a/app/www/library/examples/link_general.py +++ /dev/null @@ -1,8 +0,0 @@ -import reflex as rx -from components.ui.link import link - - -def link_general(): - """A basic link example.""" - return link("This is a link", href="#") - diff --git a/app/www/library/examples/link_sizes.py b/app/www/library/examples/link_sizes.py deleted file mode 100644 index 808224c6..00000000 --- a/app/www/library/examples/link_sizes.py +++ /dev/null @@ -1,15 +0,0 @@ -import reflex as rx -from components.ui.link import link - - -def link_sizes(): - """Link examples with different sizes.""" - return rx.box( - link("X-Small Link", href="#", size="xs"), - link("Small Link", href="#", size="sm"), - link("Medium Link", href="#", size="md"), - link("Large Link", href="#", size="lg"), - link("X-Large Link", href="#", size="xl"), - class_name="flex flex-col items-start gap-4", - ) - diff --git a/app/www/library/examples/link_variants.py b/app/www/library/examples/link_variants.py deleted file mode 100644 index f16a2f51..00000000 --- a/app/www/library/examples/link_variants.py +++ /dev/null @@ -1,12 +0,0 @@ -import reflex as rx -from components.ui.link import link - - -def link_variants(): - """Link examples with different variants.""" - return rx.box( - link("Primary Link", href="#", variant="primary"), - link("Secondary Link", href="#", variant="secondary"), - class_name="flex flex-col items-start gap-4", - ) - diff --git a/app/www/library/examples/link_with_icon.py b/app/www/library/examples/link_with_icon.py deleted file mode 100644 index 061a112f..00000000 --- a/app/www/library/examples/link_with_icon.py +++ /dev/null @@ -1,7 +0,0 @@ -import reflex as rx -from components.ui.link import link - - -def link_with_icon(): - """Link example with an icon.""" - return link("Link with icon", href="#", show_icon=True) diff --git a/app/www/library/examples/menu_avatar.py b/app/www/library/examples/menu_avatar.py new file mode 100644 index 00000000..0864ee76 --- /dev/null +++ b/app/www/library/examples/menu_avatar.py @@ -0,0 +1,39 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.avatar import avatar +from components.ui.button import button +from components.ui.menu import menu + + +def menu_avatar() -> rx.Component: + return menu.root( + menu.trigger( + render_=button( + avatar.root( + avatar.image(src="https://github.com/shadcn.png", alt="shadcn"), + avatar.fallback("LR"), + ), + variant="ghost", + size="icon", + class_name="rounded-full", + ), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item(hi("UserIcon"), "Account"), + menu.item(hi("CreditCardIcon"), "Billing"), + menu.item(hi("Notification01Icon"), "Notifications"), + ), + menu.separator(), + menu.item( + hi("LogoutSquare01Icon"), + "Sign Out", + ), + ), + align="end", + ), + ), + ) diff --git a/app/www/library/examples/menu_high_level.py b/app/www/library/examples/menu_basic.py similarity index 98% rename from app/www/library/examples/menu_high_level.py rename to app/www/library/examples/menu_basic.py index af132ab6..47272b21 100644 --- a/app/www/library/examples/menu_high_level.py +++ b/app/www/library/examples/menu_basic.py @@ -2,7 +2,7 @@ from components.ui.menu import menu -def menu_high_level(): +def menu_basic(): return menu.root( menu.trigger(render_=button("Open", variant="outline")), menu.portal( diff --git a/app/www/library/examples/menu_checkboxes_icons.py b/app/www/library/examples/menu_checkboxes_icons.py new file mode 100644 index 00000000..f1275684 --- /dev/null +++ b/app/www/library/examples/menu_checkboxes_icons.py @@ -0,0 +1,52 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.menu import menu + +email_notif = ClientStateVar.create("email_notif", True) +sms_notif = ClientStateVar.create("sms_notif", False) +push_notif = ClientStateVar.create("push_notif", True) + + +def menu_checkboxes_icons() -> rx.Component: + return menu.root( + email_notif, + sms_notif, + push_notif, + menu.trigger( + render_=button("Notifications", variant="outline"), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.group_label("Notification Preferences"), + menu.checkbox_item( + hi("Mail01Icon"), + "Email notifications", + menu.checkbox_item_indicator(), + default_checked=email_notif.value, + on_checked_change=email_notif.set_value, + ), + menu.checkbox_item( + hi("Message01Icon"), + "SMS notifications", + menu.checkbox_item_indicator(), + default_checked=sms_notif.value, + on_checked_change=sms_notif.set_value, + ), + menu.checkbox_item( + hi("Notification01Icon"), + "Push notifications", + menu.checkbox_item_indicator(), + default_checked=push_notif.value, + on_checked_change=push_notif.set_value, + ), + ), + class_name="w-48", + ), + ), + ), + ) diff --git a/app/www/library/examples/menu_icons.py b/app/www/library/examples/menu_icons.py new file mode 100644 index 00000000..576282f2 --- /dev/null +++ b/app/www/library/examples/menu_icons.py @@ -0,0 +1,26 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.menu import menu + + +def menu_icons() -> rx.Component: + return menu.root( + menu.trigger( + render_=button("Open", variant="outline"), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.item(hi("UserIcon"), "Profile"), + menu.item(hi("CreditCardIcon"), "Billing"), + menu.item(hi("Setting07Icon"), "Settings"), + menu.separator(), + menu.item( + hi("LogoutSquare01Icon"), "Log out", variant="destructive" + ), + ), + ), + ), + ) diff --git a/app/www/library/examples/menu_radio_group.py b/app/www/library/examples/menu_radio_group.py new file mode 100644 index 00000000..67271d5b --- /dev/null +++ b/app/www/library/examples/menu_radio_group.py @@ -0,0 +1,45 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.ui.button import button +from components.ui.menu import menu + +panel_position = ClientStateVar.create("panel_position", "bottom") + + +def menu_radio_group() -> rx.Component: + return menu.root( + panel_position, + menu.trigger( + render_=button("Open", variant="outline"), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.group_label("Panel Position"), + menu.radio_group( + menu.radio_item( + "Top", + menu.radio_item_indicator(), + value="top", + ), + menu.radio_item( + "Bottom", + menu.radio_item_indicator(), + value="bottom", + ), + menu.radio_item( + "Right", + menu.radio_item_indicator(), + value="right", + ), + value=panel_position.value, + on_value_change=panel_position.set_value, + ), + ), + class_name="w-32", + ), + ), + ), + ) diff --git a/app/www/library/examples/menu_shortcuts.py b/app/www/library/examples/menu_shortcuts.py new file mode 100644 index 00000000..491e7a2b --- /dev/null +++ b/app/www/library/examples/menu_shortcuts.py @@ -0,0 +1,26 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.menu import menu + + +def menu_shortcuts() -> rx.Component: + return menu.root( + menu.trigger( + render_=button("Open", variant="outline"), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.group_label("My Account"), + menu.item("Profile", menu.shortcut("⇧⌘P")), + menu.item("Billing", menu.shortcut("⌘B")), + menu.item("Settings", menu.shortcut("⌘S")), + ), + menu.separator(), + menu.item("Log out", menu.shortcut("⇧⌘Q")), + ), + ), + ), + ) diff --git a/app/www/library/examples/select_groups.py b/app/www/library/examples/select_groups.py index 4db2c0ce..b90009d7 100644 --- a/app/www/library/examples/select_groups.py +++ b/app/www/library/examples/select_groups.py @@ -18,9 +18,7 @@ def select_groups(): return select.root( select.trigger( select.value(), - select.icon( - # hi("ArrowDown01Icon", classs_name="size-4"), - ), + select.icon(), class_name="w-full max-w-48 flex items-center justify-between", ), select.portal( @@ -31,9 +29,7 @@ def select_groups(): *[ select.item( select.item_text(item["label"]), - select.item_indicator( - # hi("Tick02Icon", class_name="size-4") - ), + select.item_indicator(), value=item["value"], class_name="flex flex-row items-center justify-between", ) @@ -46,9 +42,7 @@ def select_groups(): *[ select.item( select.item_text(item["label"]), - select.item_indicator( - # hi("Tick02Icon", class_name="size-4") - ), + select.item_indicator(), value=item["value"], class_name="flex flex-row items-center justify-between", ) diff --git a/app/www/library/examples/separator_horizontal.py b/app/www/library/examples/separator_horizontal.py new file mode 100644 index 00000000..835ce57c --- /dev/null +++ b/app/www/library/examples/separator_horizontal.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.separator import separator + + +def separator_horizontal() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.div("buridan/ui", class_name="leading-none font-medium"), + rx.el.div( + "The UI Library for Reflex Devs.", + class_name="text-muted-foreground", + ), + class_name="flex flex-col gap-1.5", + ), + separator(class_name="bg-zinc-200 dark:bg-zinc-800"), + rx.el.div( + "A set of beautifully designed components that you can customize, extend, and build on." + ), + class_name="flex max-w-sm flex-col gap-4 text-sm", + ) diff --git a/app/www/library/examples/separator_list.py b/app/www/library/examples/separator_list.py new file mode 100644 index 00000000..58880358 --- /dev/null +++ b/app/www/library/examples/separator_list.py @@ -0,0 +1,26 @@ +import reflex as rx + +from components.ui.separator import separator + + +def separator_list() -> rx.Component: + return rx.el.div( + rx.el.dl( + rx.el.dt("Item 1"), + rx.el.dd("Value 1", class_name="text-muted-foreground"), + class_name="flex items-center justify-between", + ), + separator(), + rx.el.dl( + rx.el.dt("Item 2"), + rx.el.dd("Value 2", class_name="text-muted-foreground"), + class_name="flex items-center justify-between", + ), + separator(), + rx.el.dl( + rx.el.dt("Item 3"), + rx.el.dd("Value 3", class_name="text-muted-foreground"), + class_name="flex items-center justify-between", + ), + class_name="flex w-full max-w-sm flex-col gap-2 text-sm", + ) diff --git a/app/www/library/examples/separator_menu.py b/app/www/library/examples/separator_menu.py new file mode 100644 index 00000000..bd446c43 --- /dev/null +++ b/app/www/library/examples/separator_menu.py @@ -0,0 +1,30 @@ +import reflex as rx + +from components.ui.separator import separator + + +def separator_menu() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.span("Settings", class_name="font-medium"), + rx.el.span( + "Manage preferences", class_name="text-xs text-muted-foreground" + ), + class_name="flex flex-col gap-1", + ), + separator(orientation="vertical"), + rx.el.div( + rx.el.span("Account", class_name="font-medium"), + rx.el.span( + "Profile & security", class_name="text-xs text-muted-foreground" + ), + class_name="flex flex-col gap-1", + ), + separator(orientation="vertical", class_name="hidden md:block"), + rx.el.div( + rx.el.span("Help", class_name="font-medium"), + rx.el.span("Support & docs", class_name="text-xs text-muted-foreground"), + class_name="hidden flex-col gap-1 md:flex", + ), + class_name="flex items-center gap-2 text-sm md:gap-4", + ) diff --git a/app/www/library/examples/separator_vertical.py b/app/www/library/examples/separator_vertical.py new file mode 100644 index 00000000..e7528f4e --- /dev/null +++ b/app/www/library/examples/separator_vertical.py @@ -0,0 +1,14 @@ +import reflex as rx + +from components.ui.separator import separator + + +def separator_vertical() -> rx.Component: + return rx.el.div( + rx.el.div("Blog"), + separator(orientation="vertical"), + rx.el.div("Docs"), + separator(orientation="vertical"), + rx.el.div("Source"), + class_name="flex h-5 items-center gap-4 text-sm", + ) diff --git a/app/www/library/examples/switch_choice_card.py b/app/www/library/examples/switch_choice_card.py new file mode 100644 index 00000000..42a3b8a0 --- /dev/null +++ b/app/www/library/examples/switch_choice_card.py @@ -0,0 +1,36 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.switch import switch + + +def switch_choice_card() -> rx.Component: + return field.group( + field.label( + field.root( + field.content( + field.title("Share across devices"), + field.description( + "Focus is shared across devices, and turns off when you leave the app." + ), + ), + switch.root(id="switch-share"), + orientation="horizontal", + ), + html_for="switch-share", + ), + field.label( + field.root( + field.content( + field.title("Enable notifications"), + field.description( + "Receive notifications when focus mode is enabled or disabled." + ), + ), + switch.root(id="switch-notifications", default_checked=True), + orientation="horizontal", + ), + html_for="switch-notifications", + ), + class_name="w-full max-w-sm my-8", + ) diff --git a/app/www/library/examples/switch_description.py b/app/www/library/examples/switch_description.py new file mode 100644 index 00000000..b4eddf8d --- /dev/null +++ b/app/www/library/examples/switch_description.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.switch import switch + + +def switch_description() -> rx.Component: + return field.root( + field.content( + field.label("Share across devices", html_for="switch-focus-mode"), + field.description( + "Focus is shared across devices, and turns off when you leave the app." + ), + ), + switch.root( + switch.thumb(), + id="switch-focus-mode", + ), + orientation="horizontal", + class_name="max-w-sm px-2", + ) diff --git a/app/www/library/examples/switch_disabled.py b/app/www/library/examples/switch_disabled.py new file mode 100644 index 00000000..914d0ee4 --- /dev/null +++ b/app/www/library/examples/switch_disabled.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.switch import switch + + +def switch_disabled() -> rx.Component: + + return field.root( + switch.root( + id="switch-disabled-unchecked", + disabled=True, + ), + field.label( + "Disabled", + html_for="switch-disabled-unchecked", + ), + orientation="horizontal", + data_disabled="true", + class_name="w-fit ", + ) diff --git a/app/www/library/examples/switch_invalid.py b/app/www/library/examples/switch_invalid.py new file mode 100644 index 00000000..c5bdc40b --- /dev/null +++ b/app/www/library/examples/switch_invalid.py @@ -0,0 +1,23 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.switch import switch + + +def switch_invalid() -> rx.Component: + return field.root( + field.content( + field.label( + "Accept terms and conditions", + html_for="switch-terms", + ), + field.description("You must accept the terms and conditions to continue."), + ), + switch.root( + id="switch-terms", + aria_invalid="true", + ), + orientation="horizontal", + data_invalid="true", + class_name="max-w-sm", + ) diff --git a/app/www/library/examples/switch_sizes.py b/app/www/library/examples/switch_sizes.py new file mode 100644 index 00000000..b99406c9 --- /dev/null +++ b/app/www/library/examples/switch_sizes.py @@ -0,0 +1,32 @@ +import reflex as rx + +from components.ui.field import field +from components.ui.switch import switch + + +def switch_sizes() -> rx.Component: + return field.group( + field.root( + switch.root( + id="switch-size-sm", + size="sm", + ), + field.label( + "Small", + html_for="switch-size-sm", + ), + orientation="horizontal", + ), + field.root( + switch.root( + id="switch-size-default", + size="default", + ), + field.label( + "Default", + html_for="switch-size-default", + ), + orientation="horizontal", + ), + class_name="w-full max-w-[10rem]", + ) diff --git a/app/www/library/examples/tabs_icons.py b/app/www/library/examples/tabs_icons.py new file mode 100644 index 00000000..49073251 --- /dev/null +++ b/app/www/library/examples/tabs_icons.py @@ -0,0 +1,23 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.tabs import tabs + + +def tabs_icons() -> rx.Component: + return tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab( + hi("BrowserIcon"), + "Preview", + value="preview", + ), + tabs.tab( + hi("CodeIcon"), + "Code", + value="code", + ), + ), + default_value="preview", + ) diff --git a/app/www/library/examples/tabs_line.py b/app/www/library/examples/tabs_line.py new file mode 100644 index 00000000..701b447e --- /dev/null +++ b/app/www/library/examples/tabs_line.py @@ -0,0 +1,16 @@ +import reflex as rx + +from components.ui.tabs import tabs + + +def tabs_line() -> rx.Component: + return tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("Overview", value="overview"), + tabs.tab("Analytics", value="analytics"), + tabs.tab("Reports", value="reports"), + variant="line", + ), + default_value="overview", + ) diff --git a/app/www/library/examples/textarea_basic_demo.py b/app/www/library/examples/textarea_basic_demo.py index ff533926..a1f934c7 100644 --- a/app/www/library/examples/textarea_basic_demo.py +++ b/app/www/library/examples/textarea_basic_demo.py @@ -2,4 +2,4 @@ def textarea_basic_demo(): - return textarea(placeholder="Type your message here.") + return textarea(placeholder="Type your message here.", class_name="max-w-xs") diff --git a/app/www/library/examples/textarea_custom_text_area.py b/app/www/library/examples/textarea_custom_text_area.py deleted file mode 100644 index 546a222d..00000000 --- a/app/www/library/examples/textarea_custom_text_area.py +++ /dev/null @@ -1,13 +0,0 @@ -import reflex as rx -from components.ui.textarea import textarea - - -def textarea_custom_text_area(): - return rx.el.div( - rx.el.p("Custom Height", class_name="text-sm font-medium mb-2"), - textarea( - placeholder="Taller textarea", - class_name="min-h-32", - ), - class_name="w-full max-w-md p-8", - ) diff --git a/app/www/library/examples/textarea_disabled.py b/app/www/library/examples/textarea_disabled.py index 4c5c2e01..2c6866f5 100644 --- a/app/www/library/examples/textarea_disabled.py +++ b/app/www/library/examples/textarea_disabled.py @@ -14,4 +14,5 @@ def textarea_disabled(): disabled=True, ), **{"data-disabled": True}, + class_name="max-w-xs", ) diff --git a/app/www/library/examples/textarea_field.py b/app/www/library/examples/textarea_field.py index 580d1ddb..63b20e71 100644 --- a/app/www/library/examples/textarea_field.py +++ b/app/www/library/examples/textarea_field.py @@ -15,4 +15,5 @@ def textarea_field(): id="textarea-message", placeholder="Type your message here.", ), + class_name="max-w-xs", ) diff --git a/app/www/library/examples/textarea_invalid.py b/app/www/library/examples/textarea_invalid.py index 768fe591..d19d8928 100644 --- a/app/www/library/examples/textarea_invalid.py +++ b/app/www/library/examples/textarea_invalid.py @@ -17,4 +17,5 @@ def textarea_invalid(): "Please enter a valid message.", ), **{"data-invalid": True}, + class_name="max-w-xs", ) diff --git a/app/www/library/examples/toggle_group_custom.py b/app/www/library/examples/toggle_group_custom.py new file mode 100644 index 00000000..408552d0 --- /dev/null +++ b/app/www/library/examples/toggle_group_custom.py @@ -0,0 +1,58 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.ui.field import field +from components.ui.toggle_group import toggle_group + +font_weight = ClientStateVar.create("font_weight_selector", "normal") + + +def toggle_group_custom() -> rx.Component: + + return field.set( + field.root( + field.label("Font Weight"), + toggle_group.root( + toggle_group.item( + rx.el.span("Aa", class_name="text-2xl leading-none font-light"), + rx.el.span("Light", class_name="text-xs text-muted-foreground"), + value="light", + aria_label="Light", + class_name="flex !size-16 flex-col items-center justify-center rounded-xl", + ), + toggle_group.item( + rx.el.span("Aa", class_name="text-2xl leading-none font-normal"), + rx.el.span("Normal", class_name="text-xs text-muted-foreground"), + value="normal", + aria_label="Normal", + class_name="flex !size-16 flex-col items-center justify-center rounded-xl", + ), + toggle_group.item( + rx.el.span("Aa", class_name="text-2xl leading-none font-medium"), + rx.el.span("Medium", class_name="text-xs text-muted-foreground"), + value="medium", + aria_label="Medium", + class_name="flex !size-16 flex-col items-center justify-center rounded-xl", + ), + toggle_group.item( + rx.el.span("Aa", class_name="text-2xl leading-none font-bold"), + rx.el.span("Bold", class_name="text-xs text-muted-foreground"), + value="bold", + aria_label="Bold", + class_name="flex !size-16 flex-col items-center justify-center rounded-xl", + ), + on_value_change=font_weight.set_value, + variant="outline", + spacing=2, + size="lg", + ), + field.description( + "Use ", + rx.el.code( + f"font-{font_weight.value}", + class_name="rounded-md bg-muted px-1 py-0.5 font-mono", + ), + " to set the font weight.", + ), + ) + ) diff --git a/app/www/library/examples/toggle_group_disabled.py b/app/www/library/examples/toggle_group_disabled.py new file mode 100644 index 00000000..f72a68c5 --- /dev/null +++ b/app/www/library/examples/toggle_group_disabled.py @@ -0,0 +1,25 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.toggle_group import toggle_group + + +def toggle_group_disabled() -> rx.Component: + return toggle_group.root( + toggle_group.item( + hi("TextBoldIcon"), + value="bold", + aria_label="Toggle bold", + ), + toggle_group.item( + hi("TextItalicIcon"), + value="italic", + aria_label="Toggle italic", + ), + toggle_group.item( + hi("TextUnderlineIcon"), + value="strikethrough", + aria_label="Toggle strikethrough", + ), + disabled=True, + ) diff --git a/app/www/library/examples/toggle_group_general.py b/app/www/library/examples/toggle_group_general.py deleted file mode 100644 index 50d0cbca..00000000 --- a/app/www/library/examples/toggle_group_general.py +++ /dev/null @@ -1,15 +0,0 @@ -from components.icons.hugeicon import hi -from components.ui.toggle import toggle -from components.ui.toggle_group import toggle_group - -toggle_style = "flex size-8 items-center justify-center rounded-sm active:bg-secondary text-muted-foreground active:text-foreground" - - -def toggle_group_general(): - return toggle_group( - toggle(hi("TextBoldIcon"), value="bold", class_name=toggle_style), - toggle(hi("TextItalicIcon"), value="italic", class_name=toggle_style), - toggle(hi("TextUnderlineIcon"), value="underline", class_name=toggle_style), - default_value=["bold"], - class_name="flex gap-px rounded-md border border-input bg-background p-0.5", - ) diff --git a/app/www/library/examples/toggle_group_multiple_selection.py b/app/www/library/examples/toggle_group_multiple_selection.py deleted file mode 100644 index be25936d..00000000 --- a/app/www/library/examples/toggle_group_multiple_selection.py +++ /dev/null @@ -1,16 +0,0 @@ -from components.icons.hugeicon import hi -from components.ui.toggle import toggle -from components.ui.toggle_group import toggle_group - -toggle_style = "flex size-8 items-center justify-center rounded-sm active:bg-secondary text-muted-foreground active:text-foreground" - - -def toggle_group_multiple_selection(): - return toggle_group( - toggle(hi("TextAlignLeftIcon"), value="left", class_name=toggle_style), - toggle(hi("TextAlignCenterIcon"), value="center", class_name=toggle_style), - toggle(hi("TextAlignRightIcon"), value="right", class_name=toggle_style), - default_value=["left", "right"], - multiple=True, - class_name="flex gap-px rounded-md border border-input bg-background p-0.5", - ) diff --git a/app/www/library/examples/toggle_group_outline.py b/app/www/library/examples/toggle_group_outline.py new file mode 100644 index 00000000..31361c1a --- /dev/null +++ b/app/www/library/examples/toggle_group_outline.py @@ -0,0 +1,20 @@ +import reflex as rx + +from components.ui.toggle_group import toggle_group + + +def toggle_group_outline() -> rx.Component: + return toggle_group.root( + toggle_group.item( + "All", + value="all", + aria_label="Toggle all", + ), + toggle_group.item( + "Missed", + value="missed", + aria_label="Toggle missed", + ), + variant="outline", + default_value=["all"], + ) diff --git a/app/www/library/examples/toggle_group_sizes.py b/app/www/library/examples/toggle_group_sizes.py new file mode 100644 index 00000000..ff90ebb6 --- /dev/null +++ b/app/www/library/examples/toggle_group_sizes.py @@ -0,0 +1,26 @@ +import reflex as rx + +from components.ui.toggle_group import toggle_group + + +def toggle_group_sizes() -> rx.Component: + return rx.el.div( + toggle_group.root( + toggle_group.item("Top", value="top", aria_label="Toggle top"), + toggle_group.item("Bottom", value="bottom", aria_label="Toggle bottom"), + toggle_group.item("Left", value="left", aria_label="Toggle left"), + toggle_group.item("Right", value="right", aria_label="Toggle right"), + size="sm", + variant="outline", + default_value=["top"], + ), + toggle_group.root( + toggle_group.item("Top", value="top", aria_label="Toggle top"), + toggle_group.item("Bottom", value="bottom", aria_label="Toggle bottom"), + toggle_group.item("Left", value="left", aria_label="Toggle left"), + toggle_group.item("Right", value="right", aria_label="Toggle right"), + variant="outline", + default_value=["top"], + ), + class_name="flex flex-col gap-4", + ) diff --git a/app/www/library/examples/toggle_group_spacing.py b/app/www/library/examples/toggle_group_spacing.py new file mode 100644 index 00000000..3681ac55 --- /dev/null +++ b/app/www/library/examples/toggle_group_spacing.py @@ -0,0 +1,16 @@ +import reflex as rx + +from components.ui.toggle_group import toggle_group + + +def toggle_group_spacing() -> rx.Component: + return toggle_group.root( + toggle_group.item("Top", value="top", aria_label="Toggle top"), + toggle_group.item("Bottom", value="bottom", aria_label="Toggle bottom"), + toggle_group.item("Left", value="left", aria_label="Toggle left"), + toggle_group.item("Right", value="right", aria_label="Toggle right"), + size="sm", + variant="outline", + spacing=4, + default_value=["top"], + ) diff --git a/app/www/library/examples/toggle_group_vertical.py b/app/www/library/examples/toggle_group_vertical.py new file mode 100644 index 00000000..2b4f79d2 --- /dev/null +++ b/app/www/library/examples/toggle_group_vertical.py @@ -0,0 +1,27 @@ +import reflex as rx + +from components.ui.toggle_group import toggle_group + + +def toggle_group_vertical() -> rx.Component: + return toggle_group.root( + toggle_group.item( + rx.icon(tag="bold"), + value="bold", + aria_label="Toggle bold", + ), + toggle_group.item( + rx.icon(tag="italic"), + value="italic", + aria_label="Toggle italic", + ), + toggle_group.item( + rx.icon(tag="underline"), + value="underline", + aria_label="Toggle underline", + ), + multiple=True, + orientation="vertical", + spacing=1, + default_value=["bold", "italic"], + ) diff --git a/app/www/library/examples/toggle_sizes.py b/app/www/library/examples/toggle_sizes.py new file mode 100644 index 00000000..f8b16102 --- /dev/null +++ b/app/www/library/examples/toggle_sizes.py @@ -0,0 +1,27 @@ +import reflex as rx + +from components.ui.toggle import toggle + + +def toggle_sizes() -> rx.Component: + return rx.el.div( + toggle( + "Small", + variant="outline", + aria_label="Toggle small", + size="sm", + ), + toggle( + "Default", + variant="outline", + aria_label="Toggle default", + size="default", + ), + toggle( + "Large", + variant="outline", + aria_label="Toggle large", + size="lg", + ), + class_name="flex flex-wrap items-center gap-2", + ) diff --git a/app/www/style.py b/app/www/style.py index a59487b6..ef377200 100644 --- a/app/www/style.py +++ b/app/www/style.py @@ -23,13 +23,6 @@ def render_parse_error(msg: str): return rx.el.p(msg, class_name="text-sm text-red-500") -# # --- Helper functions --- -# def render_heading(level: int, text: str) -> rx.Component: -# return rx.el.header( -# text, class_name=HEADING_1_CLASS if level == 1 else HEADING_2_CLASS, id=text -# ) - - def render_heading(level: int, text: str) -> rx.Component: normalized_id = rx.Var.create(text).to(str).lower().replace(" ", "-") @@ -65,21 +58,6 @@ def render_heading(level: int, text: str) -> rx.Component: id=normalized_id, ) - return rx.el.header( - rx.el.a( - rx.el.span( - text, - class_name="hover:underline hover:underline-offset-6", - ), - href=f"#{normalized_id}", - class_name=( - "after:content-['#'] after:ml-2 after:opacity-0 hover:after:opacity-100 after:text-muted-foreground" - ), - ), - class_name=HEADING_1_CLASS if level == 1 else HEADING_2_CLASS, - id=normalized_id, - ) - def render_paragraph(text: str) -> rx.Component: return rx.el.p(text, class_name=PARAGRAPH_CLASS) @@ -98,12 +76,15 @@ def render_pre(*children, **props) -> rx.Component: return rx.el.div( rx.el.div( - rx.el.div( - rx.el.p( - language, - class_name="text-muted-foreground text-sm font-normal px-[1rem] py-2", + rx.cond( + language != "python", + rx.el.div( + rx.el.p( + language, + class_name="text-muted-foreground text-sm font-normal px-[1rem] py-2", + ), + class_name="w-full border-b border-input/70 flex flex-row items-center justify-between", ), - class_name="w-full border-b border-input/70 flex flex-row items-center justify-between", ), rx.el.div( rx.el.pre( @@ -112,10 +93,11 @@ def render_pre(*children, **props) -> rx.Component: style={ "white-space": "pre", "color": "var(--foreground)", - "font-size": "13px", + "font-size": "13px !important", "padding": "1rem 1rem", "display": "block", }, + class_name="language-python", ), ), class_name="overflow-x-auto overflow-y-auto scrollbar-none flex-1 min-h-0 pr-[1rem]", diff --git a/app/www/wrapper.py b/app/www/wrapper.py index 1a791fc9..df60a18b 100644 --- a/app/www/wrapper.py +++ b/app/www/wrapper.py @@ -204,7 +204,7 @@ def demo_wrapper(component: rx.Component, source: str) -> rx.Component: return rx.el.div( rx.el.div( component, - class_name="min-h-[250px] flex items-center justify-center p-6", + class_name="w-full min-h-[250px] flex items-center justify-center !p-2 !sm:p-6 my-10", ), rx.el.div( rx.el.div( @@ -381,7 +381,7 @@ def tooltip_indicator(color: str, swatch: Swatch) -> rx.Component: ), style={"display": "flex", "flexDirection": "column", "gap": "2px"}, ) - return rx.fragment() # "border" swatch uses CSS border on the row itself + return rx.fragment() def tooltip_row( diff --git a/assets/docs/charts/area-chart.md b/assets/docs/charts/area-chart.md index f6295a3c..9c61c58c 100644 --- a/assets/docs/charts/area-chart.md +++ b/assets/docs/charts/area-chart.md @@ -129,6 +129,8 @@ chart_tooltip_content = _ChartTooltipContent() # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Basic A minimal example showing a single series with a smooth gradient fill. @@ -464,17 +466,14 @@ def areachart_v5(): ), data=SelectedRange.value, width="100%", - height=240, + height=250, ), - class_name="flex flex-col items-center h-[240px]", - ), - card.footer( rx.el.div( rx.foreach( ["Desktop", "Mobile"], lambda device, index: rx.el.div( rx.el.div( - class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + class_name=f"h-2 w-2 shrink-0 rounded-[2px] bg-chart-{index + 1}" ), rx.el.p(device, class_name="text-sm text-foreground"), class_name="flex flex-row items-center gap-x-2", @@ -482,6 +481,7 @@ def areachart_v5(): ), class_name="py-4 px-4 flex w-full flex justify-center gap-8", ), + class_name="flex flex-col items-center", ), class_name=chart_tooltip_content([1, 2], "square") + " w-full p-0", ) @@ -665,8 +665,11 @@ def areachart_v8(): rx.foreach( series, lambda s: rx.hstack( - rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), - rx.text( + rx.el.div( + class_name="h-2 w-2 shrink-0 rounded-[2px]", + bg=f"var({s[2]})", + ), + rx.el.p( s[1], class_name="text-xs font-medium", color=rx.color("slate", 11), @@ -831,8 +834,11 @@ def areachart_v10(): rx.foreach( series, lambda s: rx.hstack( - rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), - rx.text( + rx.el.div( + class_name="h-2 w-2 shrink-0 rounded-[2px]", + bg=f"var({s[2]})", + ), + rx.el.p( s[1], class_name="text-xs font-medium", color=rx.color("slate", 11), diff --git a/assets/docs/charts/bar-chart.md b/assets/docs/charts/bar-chart.md index 68dfe3f3..a4d59735 100644 --- a/assets/docs/charts/bar-chart.md +++ b/assets/docs/charts/bar-chart.md @@ -130,6 +130,8 @@ chart_tooltip_content = _ChartTooltipContent() # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Multiple Series A simple vertical bar chart comparing data categories. diff --git a/assets/docs/charts/doughnut-chart.md b/assets/docs/charts/doughnut-chart.md index 16fea1cb..2924639f 100644 --- a/assets/docs/charts/doughnut-chart.md +++ b/assets/docs/charts/doughnut-chart.md @@ -6,6 +6,8 @@ Doughnut Charts are ideal for showing changes over time or the magnitude of mult # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Doughnut Chart A customizable doughnut chart with flexible styling and data visualization options. diff --git a/assets/docs/charts/line-chart.md b/assets/docs/charts/line-chart.md index e862d756..a809141b 100644 --- a/assets/docs/charts/line-chart.md +++ b/assets/docs/charts/line-chart.md @@ -129,6 +129,8 @@ chart_tooltip_content = _ChartTooltipContent() # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Basic A minimal example showing a single series with a smooth line connection. diff --git a/assets/docs/charts/pie-chart.md b/assets/docs/charts/pie-chart.md index 3cadce07..1d66b6c9 100644 --- a/assets/docs/charts/pie-chart.md +++ b/assets/docs/charts/pie-chart.md @@ -6,6 +6,8 @@ Pie Charts are ideal for showing changes over time or the magnitude of multiple # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Basic A minimal example showing proportional data distribution in a circular format. diff --git a/assets/docs/charts/radar-chart.md b/assets/docs/charts/radar-chart.md index a876324a..e1c7a57a 100644 --- a/assets/docs/charts/radar-chart.md +++ b/assets/docs/charts/radar-chart.md @@ -6,6 +6,8 @@ Radar Charts are ideal for showing changes over time or the magnitude of multipl # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Basic A minimal example showing multivariate data in a radial layout with filled areas. diff --git a/assets/docs/charts/scatter-chart.md b/assets/docs/charts/scatter-chart.md index 893959ae..3ae04927 100644 --- a/assets/docs/charts/scatter-chart.md +++ b/assets/docs/charts/scatter-chart.md @@ -6,6 +6,8 @@ Scatter Charts are ideal for showing changes over time or the magnitude of multi # Examples +>**Note**: The chart example charts below are wrapped in [Card](/docs/components/card). Make sure to have the component installed before using any of the examples. + ## Basic A minimal example showing the relationship between two variables with individual data points. diff --git a/assets/docs/components/accordion.md b/assets/docs/components/accordion.md index f37f8a90..34d472fc 100644 --- a/assets/docs/components/accordion.md +++ b/assets/docs/components/accordion.md @@ -19,17 +19,15 @@ buridan add component accordion ```python from typing import Any, Literal +import reflex as rx 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.vars.object import ObjectVar -from reflex_components_core.core.foreach import foreach -from reflex_components_core.el import Div -from ..icons.hugeicon import hi, icon +from ..icons.hugeicon import hi +from ..utils.twmerge import cn from .base_ui import PACKAGE_NAME, BaseUIComponent -from .button import button LiteralOrientation = Literal["horizontal", "vertical"] @@ -37,15 +35,39 @@ ITEMS_TYPE = list[dict[str, str | Component]] class ClassNames: - """Class names for accordion components.""" + ROOT = "flex w-full flex-col" - ROOT = "flex w-full flex-col divide-y divide-input" - ITEM = "not-last:border-b" - HEADER = "" - TRIGGER = "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground" - PANEL = "h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0" - PANEL_DIV = "" - TRIGGER_ICON = "size-4 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" + ITEM = "not-last:border-b border-input" + + HEADER = "flex" + + TRIGGER = ( + "group/accordion-trigger relative flex flex-1 items-start justify-between " + "rounded-lg border border-input border-transparent py-2.5 text-left text-sm font-medium " + "transition-all outline-none hover:underline focus-visible:border-ring " + "focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring " + "aria-disabled:pointer-events-none aria-disabled:opacity-50 " + "**:data-[slot=accordion-trigger-icon]:ml-auto " + "**:data-[slot=accordion-trigger-icon]:size-4 " + "**:data-[slot=accordion-trigger-icon]:text-muted-foreground" + ) + + TRIGGER_ICON_DOWN = ( + "pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" + ) + TRIGGER_ICON_UP = "pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" + + PANEL = ( + "h-[var(--accordion-panel-height)] overflow-hidden text-sm " + "transition-[height] duration-200 ease-out " + "data-[ending-style]:h-0 data-[starting-style]:h-0" + ) + + PANEL_DIV = ( + "pt-0 pb-2.5 " + "[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground " + "[&_p:not(:last-child)]:mb-4" + ) class AccordionBaseComponent(BaseUIComponent): @@ -53,38 +75,25 @@ class AccordionBaseComponent(BaseUIComponent): @property def import_var(self): - """Return the import variable for the accordion component.""" return ImportVar(tag="Accordion", package_path="", install=False) class AccordionRoot(AccordionBaseComponent): - """Groups all parts of the accordion.""" - tag = "Accordion.Root" default_value: Var[list[Any]] - value: Var[list[Any]] - on_value_change: EventHandler[passthrough_event_spec(list[str])] - hidden_until_found: Var[bool] - multiple: Var[bool] - disabled: Var[bool] - loop_focus: Var[bool] - orientation: Var[LiteralOrientation] - keep_mounted: Var[bool] - render_: Var[Component] @classmethod def create(cls, *children, **props) -> BaseUIComponent: - """Create the accordion root component.""" props["data-slot"] = "accordion" cls.set_class_name(ClassNames.ROOT, props) return super().create(*children, **props) @@ -94,16 +103,12 @@ class AccordionItem(AccordionBaseComponent): tag = "Accordion.Item" value: Var[str] - on_open_change: EventHandler[passthrough_event_spec(bool)] - disabled: Var[bool] - render_: Var[Component] @classmethod def create(cls, *children, **props) -> BaseUIComponent: - """Create the accordion item component.""" props["data-slot"] = "accordion-item" cls.set_class_name(ClassNames.ITEM, props) return super().create(*children, **props) @@ -116,7 +121,6 @@ class AccordionHeader(AccordionBaseComponent): @classmethod def create(cls, *children, **props) -> BaseUIComponent: - """Create the accordion header component.""" props["data-slot"] = "accordion-header" cls.set_class_name(ClassNames.HEADER, props) return super().create(*children, **props) @@ -125,63 +129,49 @@ class AccordionHeader(AccordionBaseComponent): class AccordionTrigger(AccordionBaseComponent): tag = "Accordion.Trigger" - title: Var[str] - native_button: Var[bool] - - render_: Var[Component] | None = None + render_: Var[Component] @classmethod def create(cls, *children, **props) -> BaseUIComponent: props["data-slot"] = "accordion-trigger" cls.set_class_name(ClassNames.TRIGGER, props) - if "render_" not in props or props["render_"] is None: - if "title" not in props: - if len(children) == 1 and isinstance(children[0], str): - props["title"] = children[0] - elif children: - raise TypeError( - "AccordionTrigger expects a single string child " - "when used without a `title` prop." - ) - else: - raise ValueError("AccordionTrigger requires a `title`.") - - props["render_"] = button( - props["title"], - hi( - "Add01Icon", - class_name=( - "text-muted-foreground size-4 transition-transform duration-50 ease-in-out " - "group-aria-[expanded=true]:rotate-45" - ), - ), - variant="ghost", - class_name=( - "w-full flex items-center justify-between group py-2 " - "font-medium !text-sm hover:bg-transparent !px-0" - ), - ) + trigger = super().create( + *children, + hi( + "ArrowDown01Icon", + data_slot="accordion-trigger-icon", + class_name=ClassNames.TRIGGER_ICON_DOWN, + ), + hi( + "ArrowUp01Icon", + data_slot="accordion-trigger-icon", + class_name=ClassNames.TRIGGER_ICON_UP, + ), + **props, + ) - return super().create(*children, **props) + return AccordionHeader.create(trigger) class AccordionPanel(AccordionBaseComponent): tag = "Accordion.Panel" hidden_until_found: Var[bool] - keep_mounted: Var[bool] - render_: Var[Component] @classmethod def create(cls, *children, **props) -> BaseUIComponent: - """Create the accordion panel component.""" props["data-slot"] = "accordion-panel" + inner_class = props.pop("class_name", "") cls.set_class_name(ClassNames.PANEL, props) - return super().create(*children, **props) + + return super().create( + rx.el.div(*children, class_name=cn(ClassNames.PANEL_DIV, inner_class)), + **props, + ) class Accordion(ComponentNamespace): @@ -239,91 +229,160 @@ def accordion_basic(): return rx.el.div( accordion.root( accordion.item( - accordion.header( - accordion.trigger("Models"), - ), + accordion.trigger("Models"), accordion.panel( - rx.el.div( - rx.el.p( - "- Genesis launched a new era of exploration.", - class_name="mb-2", - ), - rx.el.p( - "- Explorer uncovered new planets beyond our reach.", - class_name="mb-2", - ), - rx.el.p( - "- Voyager 1 ventured into interstellar space.", - class_name="mb-2", - ), - rx.el.p( - "- Apollo landed humans on the Moon.", - class_name="mb-2", - ), - class_name="py-2 text-sm", - ), + rx.el.p("- Genesis launched a new era of exploration."), + rx.el.p("- Explorer uncovered new planets beyond our reach."), + rx.el.p("- Voyager 1 ventured into interstellar space."), + rx.el.p("- Apollo landed humans on the Moon."), ), value="section-1", ), accordion.item( - accordion.header( - accordion.trigger("Spacecraft"), - ), + accordion.trigger("Spacecraft"), accordion.panel( - rx.el.div( - rx.el.p( - "- Curiosity sent back valuable data from Mars.", - class_name="mb-2", - ), - rx.el.p( - "- The Hubble Telescope captured distant galaxies.", - class_name="mb-2", - ), - rx.el.p( - "- James Webb will explore the universe's origins.", - class_name="mb-2", - ), - rx.el.p( - "- The ISS orbits Earth, conducting critical experiments.", - class_name="mb-2", - ), - class_name="py-2 text-sm", - ), + rx.el.p("- Curiosity sent back valuable data from Mars."), + rx.el.p("- The Hubble Telescope captured distant galaxies."), + rx.el.p("- James Webb will explore the universe's origins."), + rx.el.p("- The ISS orbits Earth, conducting critical experiments."), ), value="section-2", ), accordion.item( - accordion.header( - accordion.trigger("Space Discoveries"), - ), + accordion.trigger("Space Discoveries"), accordion.panel( - rx.el.div( - rx.el.p( - "- Saturn's rings have fascinated scientists for years.", - class_name="mb-2", - ), - rx.el.p( - "- The Mars Rover is studying the planet's surface.", - class_name="mb-2", - ), - rx.el.p( - "- NASA's Artemis program aims to return humans to the Moon.", - class_name="mb-2", - ), - rx.el.p( - "- Solar missions help us understand space weather.", - class_name="mb-2", - ), - class_name="py-2 text-sm", + rx.el.p("- Saturn's rings have fascinated scientists for years."), + rx.el.p("- The Mars Rover is studying the planet's surface."), + rx.el.p( + "- NASA's Artemis program aims to return humans to the Moon." ), + rx.el.p("- Solar missions help us understand space weather."), ), value="section-3", ), class_name="w-full max-w-md mx-auto", - open_multiple=False, default_value=["section-1"], ), class_name="h-[45vh] w-full justify-center pt-10 px-8", ) ``` + +## Multiple + +Use the `multiple` prop to allow multiple items to be open at the same time. + + +```python +def accordion_multiple() -> rx.Component: + return accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + ) + for item in items + ], + multiple=True, + default_value=["notifications"], + class_name="max-w-sm", + ) +``` + + +## Disabled + +Use the `disabled` prop on `accordion.item` to disable individual items. + + +```python +def accordion_disabled() -> rx.Component: + return accordion.root( + accordion.item( + accordion.trigger("Can I access my account history?"), + accordion.panel( + "Yes, you can view your complete account history including all " + "transactions, plan changes, and support tickets in the Account " + "History section of your dashboard." + ), + value="item-1", + ), + accordion.item( + accordion.trigger("Premium feature information"), + accordion.panel( + "This section contains information about premium features. " + "Upgrade your plan to access this content." + ), + value="item-2", + disabled=True, + ), + accordion.item( + accordion.trigger("How do I update my email address?"), + accordion.panel( + "You can update your email address in your account settings. " + "You'll receive a verification email at your new address to " + "confirm the change." + ), + value="item-3", + ), + class_name="max-w-sm", + ) +``` + + +## Borders + +Add `border` to the `accordion.root` and `border-b last:border-b-0` to the `accordion.item` to add borders to the items. + + +```python +def accordion_borders() -> rx.Component: + return accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + class_name="border-b px-4 last:border-b-0", + ) + for item in items + ], + default_value=["billing"], + class_name="max-w-sm rounded-lg border border-input", + ) +``` + + +## Card + +Wrap the `accordion.root` in a `card` component. + + +```python +def accordion_card() -> rx.Component: + return card.root( + card.header( + card.title("Subscription & Billing"), + card.description( + "Common questions about your account, plans, payments and " + "cancellations." + ), + ), + card.content( + accordion.root( + *[ + accordion.item( + accordion.trigger(item["trigger"]), + accordion.panel(item["content"]), + value=item["value"], + ) + for item in items + ], + default_value=["plans"], + ), + ), + class_name="w-full max-w-sm", + ) +``` + diff --git a/assets/docs/components/attachment.md b/assets/docs/components/attachment.md index 1353fb73..cb2be5ed 100644 --- a/assets/docs/components/attachment.md +++ b/assets/docs/components/attachment.md @@ -230,7 +230,7 @@ def attachment_trigger( component_fn = rx.el.a if link else rx.el.button - props.setdefault("data_slot", "attachment-trigger") + props.setdefault("data-slot", "attachment-trigger") props.setdefault("class_name", cn(ClassNames.TRIGGER, class_name)) if not link: diff --git a/assets/docs/components/avatar.md b/assets/docs/components/avatar.md index 847acbe1..4bbd39f5 100644 --- a/assets/docs/components/avatar.md +++ b/assets/docs/components/avatar.md @@ -106,8 +106,6 @@ class AvatarBadge(Span, CoreComponent): class AvatarGroup(Div, CoreComponent): - """The flex container wrapper for grouping multiple avatars.""" - @classmethod def create(cls, *children, **props) -> Div: custom_classes = props.pop("class_name", "") diff --git a/assets/docs/components/bubble.md b/assets/docs/components/bubble.md index bff3a66e..2b825256 100644 --- a/assets/docs/components/bubble.md +++ b/assets/docs/components/bubble.md @@ -21,8 +21,6 @@ buridan add component bubble ### Manual Installation ```python -"""Bubble component — chat message bubble with variants, content, and reactions slots.""" - from typing import Literal import reflex as rx @@ -31,10 +29,22 @@ from reflex.components.component import ComponentNamespace from ..utils.twmerge import cn BubbleVariant = Literal[ - "default", "secondary", "muted", "tinted", "outline", "ghost", "destructive" + "default", + "secondary", + "muted", + "tinted", + "outline", + "ghost", + "destructive", +] +BubbleAlign = Literal[ + "start", + "end", +] +BubbleSide = Literal[ + "top", + "bottom", ] -BubbleAlign = Literal["start", "end"] -BubbleSide = Literal["top", "bottom"] class ClassNames: @@ -102,7 +112,7 @@ class ClassNames: def bubble_group(*children, class_name: str = "", **props) -> rx.Component: - """Vertical stack of bubbles.""" + return rx.el.div( *children, data_slot="bubble-group", @@ -118,12 +128,7 @@ def bubble_root( class_name: str = "", **props, ) -> rx.Component: - """ - Bubble root container. - Variants: default, secondary, muted, tinted, outline, ghost, destructive - Align: start (incoming), end (outgoing) - """ return rx.el.div( *children, data_slot="bubble", @@ -139,7 +144,7 @@ def bubble_root( def bubble_content(*children, class_name: str = "", **props) -> rx.Component: - """The bubble content pill — rounded, padded, colored by the parent variant.""" + return rx.el.div( *children, data_slot="bubble-content", @@ -155,12 +160,7 @@ def bubble_reactions( class_name: str = "", **props, ) -> rx.Component: - """ - Emoji reactions overlay — positioned relative to the bubble. - Side: top | bottom (default: bottom) - Align: start | end (default: end) - """ return rx.el.div( *children, data_slot="bubble-reactions", @@ -177,8 +177,6 @@ def bubble_reactions( class Bubble(ComponentNamespace): - """Bubble namespace.""" - group = staticmethod(bubble_group) root = staticmethod(bubble_root) content = staticmethod(bubble_content) diff --git a/assets/docs/components/button-group.md b/assets/docs/components/button-group.md new file mode 100644 index 00000000..58870473 --- /dev/null +++ b/assets/docs/components/button-group.md @@ -0,0 +1,285 @@ + + +# Button Group + +A container that groups related buttons together with consistent styling. + +# Installation + +Copy the following code into your app directory. + + +> **Error: 'button_group' not found in registry** + + +# Usage + + +> **Error: 'button_group' not found in registry** + + +# Anatomy +Use the following composition to build a `Button Group` component. + + +```python +button_group.root( + button(), + button_group.separator(), +) +``` + + + +# Accessibility + +- The `button_group.root` component has the `role` attribute set to `group`. +- Use `Tab` to navigate between the buttons in the group. +- Use `aria-label` or `aria-labelledby` to label the button group. + +# Examples + +## Orientation + +Set the `orientation` prop to change the button group layout. + + +```python +def button_group_orientation() -> rx.Component: + return button_group.root( + button( + hi("Add01Icon"), + variant="outline", + size="icon", + ), + button( + hi("MinusSignIcon"), + variant="outline", + size="icon", + ), + orientation="vertical", + aria_label="Media controls", + class_name="h-fit", + ) +``` + + +## Size + +Control the size of buttons using the `size` prop on individual buttons. + + +```python +def button_group_size() -> rx.Component: + return rx.el.div( + button_group.root( + button("Small", variant="outline", size="sm"), + button("Button", variant="outline", size="sm"), + button("Group", variant="outline", size="sm"), + button(hi("Add01Icon"), variant="outline", size="icon-sm"), + ), + button_group.root( + button("Default", variant="outline"), + button("Button", variant="outline"), + button("Group", variant="outline"), + button(hi("Add01Icon"), variant="outline", size="icon"), + ), + button_group.root( + button("Large", variant="outline", size="lg"), + button("Button", variant="outline", size="lg"), + button("Group", variant="outline", size="lg"), + button(hi("Add01Icon"), variant="outline", size="icon-lg"), + ), + class_name="flex flex-col items-start gap-8", + ) +``` + + +## Separator + +The `button_group.separator` component visually divides buttons within a group. + +Buttons with variant `outline` do not need a separator since they have a border. For other variants, a separator is recommended to improve the visual hierarchy. + + +```python +def button_group_separator() -> rx.Component: + return button_group.root( + button("Copy", variant="secondary", size="sm"), + button_group.separator(), + button("Paste", variant="secondary", size="sm"), + ) +``` + + +## Split + +Create a split button group by adding two buttons separated by a `button_group.separator`. + + +```python +def button_group_split() -> rx.Component: + return button_group.root( + button("Button", variant="secondary"), + button_group.separator(), + button( + hi("Add01Icon"), + variant="secondary", + size="icon", + ), + ) +``` + + +## Input + +Wrap an `input` component with buttons. + + +```python +def button_group_input() -> rx.Component: + return button_group.root( + input(placeholder="Search..."), + button( + hi("Search01Icon"), + variant="outline", + aria_label="Search", + ), + ) +``` + + +## Dropdown Menu + +Create a split button group with a `menu` component. + + +```python +def button_group_dropdown() -> rx.Component: + return button_group.root( + button("Follow", variant="outline"), + menu.root( + menu.trigger( + render_=button( + hi("ArrowDown01Icon"), + variant="outline", + class_name="pl-2!", + ), + ), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item("Mute Conversation"), + menu.item("Mark as Read"), + menu.item("Report Conversation"), + menu.item("Block User"), + menu.item("Share Conversation"), + menu.item("Copy Conversation"), + ), + menu.separator(), + menu.group( + menu.item("Delete Conversation", variant="destructive"), + ), + class_name="w-44", + ), + align="end", + ), + ), + ), + ) +``` + + + +## Select + +Pair with a `select` component. + + +```python +def button_group_select() -> rx.Component: + + return button_group.root( + button_group.root( + select.root( + select.trigger( + selected_currency.value, + select.icon(), + class_name="flex items-center gap-1", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text( + item["value"], + " ", + rx.el.span( + item["label"], + class_name="text-xs !text-muted-foreground", + ), + class_name="text-sm text-foreground", + ), + select.item_indicator(), + value=item["value"], + class_name="flex flex-row items-center justify-between", + ) + for item in currencies + ] + ), + ), + align="start", + ), + ), + items=currencies, + value=selected_currency.value, + on_value_change=selected_currency.set_value, + name="currency_select", + ), + input(placeholder="10.00", pattern="[0-9]*"), + ), + button_group.root( + button( + hi("ArrowRight01Icon"), + aria_label="Send", + size="icon", + variant="outline", + ) + ), + ) +``` + + +# API Reference + +## button_group.root + +The `button_group.root` component is a container that groups related buttons together with consistent styling. + +| Prop | Type | Default | +| ------------- | ---------------------------- | -------------- | +| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | + + +## button_group.separator + +The `button_group.separator` component visually divides buttons within a group. + +| Prop | Type | Default | +| ------------- | ---------------------------- | ------------ | +| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | + + +## button_group.text + +Use this component to display text within a button group. + +| Prop | Type | Default | +| --------- | --------- | ------- | +| `*children` | `rx.Component` | `false` | + + +The `button_group.text` accepts `*children` so any interactive component passed to it will be rendered. Use it to render a custom component as the text, for example a label. diff --git a/assets/docs/components/button.md b/assets/docs/components/button.md index 02668cf8..6b400b92 100644 --- a/assets/docs/components/button.md +++ b/assets/docs/components/button.md @@ -130,10 +130,11 @@ class Button(BaseButton, CoreComponent): variant = props.pop("variant", "default") size = props.pop("size", "default") custom_classes = props.pop("class_name", "") + data_slot = props.pop("data_slot", "button") return super().create( *children, - data_slot="button", + data_slot=data_slot, class_name=cn( DEFAULT_CLASS_NAME, BUTTON_VARIANTS["variant"].get(variant, ""), diff --git a/assets/docs/components/card.md b/assets/docs/components/card.md new file mode 100644 index 00000000..5cbf84e4 --- /dev/null +++ b/assets/docs/components/card.md @@ -0,0 +1,430 @@ + + +# Card + +Displays a card with header, content, and footer. + +# Installation + +Copy the following code into your app directory. + +### CLI + +```bash +buridan add component card +``` + +### Manual Installation + +```python +from reflex.components.component import ComponentNamespace +from reflex.vars.base import Var +from reflex_components_core.el import Div + +from components.ui.component import CoreComponent + + +class ClassNames: + ROOT = ( + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card " + "py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 " + "[--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 " + "has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] " + "data-[size=sm]:has-data-[slot=card-footer]:pb-0 " + "*:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl" + ) + HEADER = ( + "group/card-header @container/card-header grid auto-rows-min items-start gap-1 " + "rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] " + "has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)" + ) + TITLE = "cn-font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm" + DESCRIPTION = "text-sm text-muted-foreground" + ACTION = "col-start-2 row-span-2 row-start-1 self-start justify-self-end" + CONTENT = "px-(--card-spacing)" + FOOTER = "flex items-center rounded-b-xl border-t border-input bg-muted/50 p-(--card-spacing)" + + +class CardRoot(Div, CoreComponent): + size: Var[str] + + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card" + if "size" in props: + props["data-size"] = props.pop("size") + cls.set_class_name(ClassNames.ROOT, props) + return super().create(*children, **props) + + +class CardHeader(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-header" + cls.set_class_name(ClassNames.HEADER, props) + return super().create(*children, **props) + + +class CardTitle(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-title" + cls.set_class_name(ClassNames.TITLE, props) + return super().create(*children, **props) + + +class CardDescription(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-description" + cls.set_class_name(ClassNames.DESCRIPTION, props) + return super().create(*children, **props) + + +class CardAction(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-action" + cls.set_class_name(ClassNames.ACTION, props) + return super().create(*children, **props) + + +class CardContent(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-content" + cls.set_class_name(ClassNames.CONTENT, props) + return super().create(*children, **props) + + +class CardFooter(Div, CoreComponent): + @classmethod + def create(cls, *children, **props): + props["data-slot"] = "card-footer" + cls.set_class_name(ClassNames.FOOTER, props) + return super().create(*children, **props) + + +class Card(ComponentNamespace): + root = staticmethod(CardRoot.create) + header = staticmethod(CardHeader.create) + title = staticmethod(CardTitle.create) + description = staticmethod(CardDescription.create) + action = staticmethod(CardAction.create) + content = staticmethod(CardContent.create) + footer = staticmethod(CardFooter.create) + class_names = ClassNames + + +card = Card() +``` + + +# Usage + + +```python +from components.ui.card import Card +``` + + +# Anatomy +Use the following composition to build a `Card` component. + + +```python +card.root( + card.header( + card.title(), + card.description(), + card.action(), + ), + card.content(), + card.footer(), +) +``` + + +# Examples + +## Size + +Use the `size="sm"` prop to set the size of the card to small. The small size variant uses smaller spacing. + + +```python +def card_small() -> rx.Component: + feature_name = "Scheduled reports" + + return card.root( + card.header( + card.title(feature_name), + card.description("Weekly snapshots. No more manual exports."), + ), + card.content( + rx.el.ul( + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Choose a schedule (daily, or weekly)."), + class_name="flex gap-2", + ), + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Send to channels or specific teammates."), + class_name="flex gap-2", + ), + rx.el.li( + hi( + "ArrowRight01Icon", + class_name="mt-0.5 size-4 shrink-0 text-muted-foreground", + ), + rx.el.span("Include charts, tables, and key metrics."), + class_name="flex gap-2", + ), + class_name="grid gap-2 py-2 text-sm", + ) + ), + card.footer( + button( + "Set up scheduled reports", + size="sm", + class_name="w-full", + ), + button( + "See what's new", + variant="outline", + size="sm", + class_name="w-full", + ), + class_name="flex-col gap-2", + ), + size="sm", + class_name="mx-auto w-full max-w-xs my-10", + ) +``` + + +## Spacing + +In addition to the `size` prop, you can use the `--card-spacing` CSS variable to control the spacing between sections and the inset of card parts. + + +```python +def card_spacing() -> rx.Component: + return rx.el.div( + rx.el.div( + *[ + button( + item["label"], + variant="outline", + size="sm", + class_name=rx.cond( + selected_card_spacing.value == item["value"], "!bg-muted", "" + ), + on_click=selected_card_spacing.set_value(item["value"]), + ) + for item in spacing_options + ], + class_name="flex gap-1 justify-start items-center w-full", + ), + card.root( + card.header( + card.title("Login to your account"), + card.description("Enter your email below to login to your account"), + card.action( + button("Sign Up", variant="link"), + ), + ), + card.content( + rx.el.form( + rx.el.div( + rx.el.div( + rx.el.label( + "Email", + html_for="email-spacing", + class_name="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + ), + rx.el.input( + id="email-spacing", + type="email", + placeholder="m@example.com", + required=True, + class_name="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", + ), + class_name="grid gap-2", + ), + rx.el.div( + rx.el.div( + rx.el.label( + "Password", + html_for="password-spacing", + class_name="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + ), + rx.el.a( + "Forgot your password?", + href="#", + class_name="ml-auto inline-block text-sm underline-offset-4 hover:underline", + ), + class_name="flex items-center", + ), + rx.el.input( + id="password-spacing", + type="password", + required=True, + class_name="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", + ), + class_name="grid gap-2", + ), + class_name="flex flex-col gap-6", + ) + ) + ), + card.footer( + button("Login", type="submit", class_name="w-full"), + button("Login with Google", variant="outline", class_name="w-full"), + class_name="flex-col gap-2", + ), + class_name=f"[--card-spacing:--spacing({selected_card_spacing.value})]", + ), + class_name="mx-auto grid w-full max-w-sm gap-4 my-10", + ) +``` + + +Use negative margins with `-mx-(--card-spacing)` to make content go edge to edge while keeping it aligned with the card inset. When the edge-to-edge content sits above a footer, use `-mb-(--card-spacing)` on `card.content` to remove the section gap. + + +```python +def card_edge_to_edge() -> rx.Component: + return card.root( + card.header( + card.title("Terms of Service"), + card.description("Review the terms before accepting the agreement."), + ), + card.content( + rx.el.div( + rx.el.p( + "These terms govern your use of the workspace, including access to shared documents, project files, and collaboration tools." + ), + rx.el.p( + "You are responsible for the content you upload and for ensuring that your team has the appropriate permissions to view or edit it." + ), + rx.el.p( + "We may update features or limits as the service evolves. When those changes materially affect your workflow, we will notify your workspace administrators." + ), + rx.el.p( + "By continuing, you agree to keep your account credentials secure and to follow your organization's acceptable use policies." + ), + class_name="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-input border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed", + ), + class_name="-mb-(--card-spacing)", + ), + card.footer( + button("Decline", variant="outline"), + button("Accept"), + class_name="justify-end gap-2", + ), + class_name="mx-auto w-full max-w-sm", + ) +``` + + + +## Image + +Add an image before the card header to create a card with an image. + + +```python +def card_image() -> rx.Component: + return card.root( + rx.el.div( + class_name="absolute inset-0 z-30 aspect-video bg-black/35", + ), + rx.el.img( + src="https://avatar.vercel.sh/shadcn1", + alt="Event cover", + class_name="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40", + ), + card.header( + card.action( + badge("Featured", variant="secondary"), + ), + card.title("Design systems meetup"), + card.description( + "A practical talk on component APIs, accessibility, and shipping faster." + ), + ), + card.footer( + button("View Event", class_name="w-full"), + ), + class_name="relative mx-auto w-full max-w-sm pt-0", + ) +``` + + + +# API Reference + +## card.root + +The `card.root` component is the root container for card content. + +| Prop | Type | Default | +| ----------- | ------------------- | ----------- | +| `size` | `"default" \| "sm"` | `"default"` | +| `class_name` | `string` | - | + +## card.header + +The `card.header` component is used for a title, description, and optional action. + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | + +## card.title + +The `card.title` component is used for the card title. + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | + +## card.description + +The `card.description` component is used for helper text under the title. + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | + +## card.action + +The `card.action` component places content in the top-right of the header (for example, a button or a badge). + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | + +## card.content + +The `card.content` component is used for the main card body. + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | + +## card.footer + +The `card.footer` component is used for actions and secondary content at the bottom of the card. + +| Prop | Type | Default | +| ----------- | -------- | ------- | +| `class_name` | `string` | - | diff --git a/assets/docs/components/chart.md b/assets/docs/components/chart.md index b9965762..87b871f6 100644 --- a/assets/docs/components/chart.md +++ b/assets/docs/components/chart.md @@ -306,7 +306,10 @@ To add a tooltip, we'll use the custom `chart_tooltip` and `chart_tooltip_conten To use the custom tooltip, first import it at the top of your chart file. ```chart_example.py -from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.charts.chart_tooltip import ( + chart_tooltip, + chart_tooltip_content, +) ``` Add the component to your chart and pass in the props. diff --git a/assets/docs/components/checkbox.md b/assets/docs/components/checkbox.md index c403be6b..8ec65451 100644 --- a/assets/docs/components/checkbox.md +++ b/assets/docs/components/checkbox.md @@ -17,153 +17,80 @@ buridan add component checkbox ### Manual Installation ```python -"""Checkbox component from base-ui components.""" - -from reflex.components.component import ComponentNamespace +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 Label from ..icons.hugeicon import hi -from ..utils.twmerge import cn from .base_ui import PACKAGE_NAME, BaseUIComponent class ClassNames: - """Class names for the checkbox component.""" - - # ROOT = "flex size-4 items-center justify-center rounded-[4px] data-[checked]:bg-primary data-[unchecked]:border data-[unchecked]:border-input data-[disabled]:cursor-not-allowed data-[disabled]:border data-[disabled]:border-input/50 data-[disabled]:bg-secondary hover:bg-secondary transition-colors cursor-default" ROOT = ( - "flex size-4 items-center justify-center rounded-[4px] " - "border border-input " - "data-[checked]:border-primary " - "data-[checked]:bg-primary " - "hover:bg-secondary transition-colors " - "data-[disabled]:cursor-not-allowed " - "data-[disabled]:border-input/50 " - "data-[disabled]:bg-secondary " - "shrink-0 " - "cursor-default" + "peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] " + "border border-input transition-colors outline-none " + "group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 " + "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 " + "aria-invalid:aria-checked:border-primary dark:bg-input/30 " + "dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 " + "data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground " + "dark:data-checked:bg-primary" + ) + INDICATOR = ( + "grid place-content-center text-current transition-none [&>svg]:size-3.5" ) - INDICATOR = "flex text-primary-foreground data-[unchecked]:hidden data-[disabled]:text-foreground/50" - LABEL = "text-sm text-foreground font-medium flex items-center gap-2" - CONTAINER = "flex flex-row items-center gap-2" class CheckboxBaseComponent(BaseUIComponent): - """Base component for checkbox components.""" - library = f"{PACKAGE_NAME}/checkbox" @property def import_var(self): - """Return the import variable for the checkbox component.""" return ImportVar(tag="Checkbox", package_path="", install=False) class CheckboxRoot(CheckboxBaseComponent): - """The root checkbox component.""" - tag = "Checkbox.Root" - # Whether the checkbox is initially ticked. To render a controlled checkbox, use the checked prop instead. Defaults to False. default_checked: Var[bool] - - # Whether the checkbox is currently ticked. To render an uncontrolled checkbox, use the default_checked prop instead. checked: Var[bool] - - # Event handler called when the checkbox is ticked or unticked. on_checked_change: EventHandler[passthrough_event_spec(bool, dict)] - - # Whether the checkbox is in a mixed state: neither ticked, nor unticked. Defaults to False. indeterminate: Var[bool] - - # Whether the component should ignore user interaction. Defaults to False. disabled: Var[bool] - - # Whether the checkbox is required. Defaults to False. required: Var[bool] - - # Identifies the field when a form is submitted. name: Var[str] - - # The value of the selected checkbox. value: Var[str] - - # Whether the component renders a native - - ); - } - - // ── BuridanMessageScrollerItem ───────────────────────────────────────────── - function BuridanMessageScrollerItem({ - messageId, - scrollAnchor = false, - itemClass, - children, - }) { - return ( -
- {children} -
- ); - } - - if (typeof window !== "undefined") { - window.BuridanMessageScrollerRoot = BuridanMessageScrollerRoot; - window.BuridanMessageScrollerItem = BuridanMessageScrollerItem; - } - - // SSR-safe wrappers - function BuridanMessageScrollerRootSSR(props) { - const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { setMounted(true); }, []); - if (!mounted || typeof BuridanMessageScrollerRoot === "undefined") return null; - return React.createElement(BuridanMessageScrollerRoot, props); - } - - function BuridanMessageScrollerItemSSR(props) { - const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { setMounted(true); }, []); - if (!mounted || typeof BuridanMessageScrollerItem === "undefined") return null; - return React.createElement(BuridanMessageScrollerItem, props); - } - - if (typeof window !== "undefined") { - window.BuridanMessageScrollerRootSSR = BuridanMessageScrollerRootSSR; - window.BuridanMessageScrollerItemSSR = BuridanMessageScrollerItemSSR; - } -})(); -""" - - -class MessageScrollerRoot(rx.Component): - """ - Auto-scrolling chat container. - - Props: - auto_scroll — bool, auto-scroll to bottom when new messages arrive (default False) - default_scroll_position — "start" | "end" (default "end") - """ - - tag = "BuridanMessageScrollerRootSSR" - is_default = False - - auto_scroll: rx.Var[bool] - default_scroll_position: rx.Var[str] - root_class: rx.Var[str] - viewport_class: rx.Var[str] - content_class: rx.Var[str] - - def add_custom_code(self) -> list[str]: - return [BURIDAN_MESSAGE_SCROLLER_JS] - - def add_imports(self) -> dict: - return {"react": ImportVar(tag="React", is_default=True)} - - @classmethod - def create(cls, *children, **props): - props.setdefault("auto_scroll", False) - props.setdefault("default_scroll_position", "end") - props["root_class"] = cn(ClassNames.ROOT, props.pop("root_class", "")) - props["viewport_class"] = cn( - ClassNames.VIEWPORT, props.pop("viewport_class", "") - ) - props["content_class"] = cn(ClassNames.CONTENT, props.pop("content_class", "")) - return super().create(*children, **props) - - -class MessageScrollerItem(rx.Component): - """ - A single item in the scroller. - - Props: - message_id — str, unique ID for visibility tracking - scroll_anchor — bool, marks this item as a scroll anchor point (default False) - """ - - tag = "BuridanMessageScrollerItemSSR" - is_default = False - - message_id: rx.Var[str] - scroll_anchor: rx.Var[bool] - item_class: rx.Var[str] - - def add_custom_code(self) -> list[str]: - return [BURIDAN_MESSAGE_SCROLLER_JS] - - def add_imports(self) -> dict: - return {"react": ImportVar(tag="React", is_default=True)} - - @classmethod - def create(cls, *children, **props): - props.setdefault("scroll_anchor", False) - props["item_class"] = cn(ClassNames.ITEM, props.pop("item_class", "")) - return super().create(*children, **props) - - -class MessageScroller(ComponentNamespace): - """MessageScroller namespace.""" - - root = staticmethod(MessageScrollerRoot.create) - item = staticmethod(MessageScrollerItem.create) - - class_names = ClassNames - - -message_scroller = MessageScroller() diff --git a/components/ui/popover.py b/components/ui/popover.py index 23aeb91c..e52c5343 100644 --- a/components/ui/popover.py +++ b/components/ui/popover.py @@ -1,5 +1,3 @@ -"""Custom popover component.""" - from typing import Literal from reflex.components.component import Component, ComponentNamespace @@ -17,8 +15,6 @@ class ClassNames: - """Class names for popover components.""" - ROOT = "" TRIGGER = "" BACKDROP = "" @@ -33,149 +29,103 @@ class ClassNames: class PopoverBaseComponent(BaseUIComponent): - """Base component for popover components.""" - library = f"{PACKAGE_NAME}/popover" @property def import_var(self): - """Return the import variable for the popover component.""" return ImportVar(tag="Popover", package_path="", install=False) class PopoverRoot(PopoverBaseComponent): - """Groups all parts of the popover. Doesn't render its own HTML element.""" - tag = "Popover.Root" - # Whether the popover is initially open. To render a controlled popover, use the open prop instead. Defaults to False. default_open: Var[bool] - # Whether the popover is currently open. open: Var[bool] - # Event handler called when the popover is opened or closed on_open_change: EventHandler[passthrough_event_spec(bool, dict)] - # Event handler called after any animations complete when the popover is opened or closed. on_open_change_complete: EventHandler[passthrough_event_spec(bool)] - # Determines if the popover enters a modal state when open. - # - True: user interaction is limited to just the popover: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - # - False: user interaction with the rest of the document is allowed. - # - 'trap-focus': focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled. modal: Var[bool | Literal["trap-focus"]] - # Whether the popover should also open when the trigger is hovered. Defaults to False. open_on_hover: Var[bool] - # How long to wait before the popover may be opened on hover. Specified in milliseconds. Requires the open_on_hover prop. Defaults to 300. delay: Var[int] - # How long to wait before closing the popover that was opened on hover. Specified in milliseconds. Requires the open_on_hover prop. Defaults to 0. close_delay: Var[int] @classmethod def create(cls, *children, **props) -> BaseUIComponent: - """Create the popover root component.""" props["data-slot"] = "popover" return super().create(*children, **props) class PopoverTrigger(PopoverBaseComponent): - """A button that opens the popover. Renders a