From 25b91e190adc5c7af0404d16f1c76fc5c75abc75 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:20:30 +0000 Subject: [PATCH 1/2] Add lemcal booking integration component - Create lemcal_button wrapper for embedding booking buttons - Create lemcal_calendar component for calendar embeds - Add get_lemcal_script function for external script loading - Follow telemetry integration patterns for external services - Add lemcal components to reflex-ui exports and type stubs - Include demo usage in demo app Co-Authored-By: Alek --- demo/demo/demo.py | 8 ++++ reflex_ui/__init__.py | 1 + reflex_ui/blocks/lemcal.py | 93 +++++++++++++++++++++++++++++++++++++ reflex_ui/blocks/lemcal.pyi | 23 +++++++++ 4 files changed, 125 insertions(+) create mode 100644 reflex_ui/blocks/lemcal.py create mode 100644 reflex_ui/blocks/lemcal.pyi diff --git a/demo/demo/demo.py b/demo/demo/demo.py index d884642..3ffe0f9 100644 --- a/demo/demo/demo.py +++ b/demo/demo/demo.py @@ -3,6 +3,7 @@ import reflex as rx import reflex_ui as ui +from reflex_ui.blocks.lemcal import get_lemcal_script, lemcal_button, lemcal_calendar class State(rx.State): @@ -58,6 +59,12 @@ def index() -> rx.Component: on_value_change=lambda value: rx.toast.success(f"Value: {value}"), on_open_change=lambda value: rx.toast.success(f"Open: {value}"), ), + rx.el.h3("Lemcal Integration Demo", class_name="text-lg font-semibold mt-8 mb-4"), + lemcal_button( + ui.button("Book a Demo", variant="outline"), + class_name="mb-4", + ), + lemcal_calendar(class_name="w-full max-w-md h-96 border rounded-lg"), ui.theme_switcher(class_name="absolute top-4 right-4"), class_name=ui.cn( "flex flex-col gap-6 items-center justify-center h-screen", "bg-secondary-1" @@ -85,6 +92,7 @@ def index() -> rx.Component: href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400..700&display=swap", rel="stylesheet", ), + get_lemcal_script(), ], ) app.add_page(index) diff --git a/reflex_ui/__init__.py b/reflex_ui/__init__.py index 231af2a..dbf4aa3 100644 --- a/reflex_ui/__init__.py +++ b/reflex_ui/__init__.py @@ -39,6 +39,7 @@ "components.icons.hugeicon": ["hi", "icon"], "components.icons.others": ["spinner"], "utils.twmerge": ["cn"], + "blocks.lemcal": ["get_lemcal_script", "lemcal_button", "lemcal_calendar"], } getattr, __dir__, __all__ = lazy_loader.attach( diff --git a/reflex_ui/blocks/lemcal.py b/reflex_ui/blocks/lemcal.py new file mode 100644 index 0000000..8cd3a62 --- /dev/null +++ b/reflex_ui/blocks/lemcal.py @@ -0,0 +1,93 @@ +"""Lemcal booking integration for Reflex applications.""" + +import reflex as rx +from typing import Any + + +LEMCAL_SCRIPT_URL = "https://cdn.lemcal.com/lemcal-integrations.min.js" + + +def get_lemcal_script() -> rx.Component: + """Generate Lemcal script component for a Reflex application. + + Returns: + rx.Component: Script component needed for Lemcal integration + """ + return rx.el.script( + src=LEMCAL_SCRIPT_URL, + defer=True, + ) + + +def lemcal_button( + child: rx.Component | None = None, + label: str = "Book a Demo", + class_name: str = "", + user_id: str = "usr_8tiwtJ8nEJaFj2qH9", + meeting_type: str = "met_ToQQ9dLZDYrEBv5qz", + **props: Any, +) -> rx.Component: + """Reusable Lemcal embed button wrapper. + + Wraps provided child (or a default button) in a div with the Lemcal + integration class and data attributes so that the external script can + attach the booking behavior. + + Args: + child: Custom component to wrap (defaults to a button with label) + label: Default button text if no child provided + class_name: Additional CSS classes to apply + user_id: Lemcal user ID for booking integration + meeting_type: Lemcal meeting type ID for booking integration + **props: Additional props to pass to the wrapper div + + Returns: + rx.Component: Lemcal button wrapper component + """ + content = child if child is not None else rx.el.button(label) + return rx.el.div( + content, + class_name=("lemcal-embed-button " + class_name).strip(), + custom_attrs={ + "data-user": user_id, + "data-meeting-type": meeting_type, + }, + **props, + ) + + +def lemcal_calendar( + user_id: str = "usr_8tiwtJ8nEJaFj2qH9", + meeting_type: str = "met_ToQQ9dLZDYrEBv5qz", + class_name: str = "", + refresh_on_mount: bool = True, + **props: Any, +) -> rx.Component: + """Lemcal booking calendar embed component. + + Creates a div with the Lemcal calendar integration class and data attributes. + Optionally refreshes the Lemcal integration when the component mounts. + + Args: + user_id: Lemcal user ID for booking integration + meeting_type: Lemcal meeting type ID for booking integration + class_name: Additional CSS classes to apply + refresh_on_mount: Whether to call window.lemcal.refresh() on mount + **props: Additional props to pass to the wrapper div + + Returns: + rx.Component: Lemcal calendar embed component + """ + calendar_props = { + "class_name": ("lemcal-embed-booking-calendar " + class_name).strip(), + "custom_attrs": { + "data-user": user_id, + "data-meeting-type": meeting_type, + }, + **props, + } + + if refresh_on_mount: + calendar_props["on_mount"] = rx.call_function("window.lemcal.refresh") + + return rx.el.div(**calendar_props) diff --git a/reflex_ui/blocks/lemcal.pyi b/reflex_ui/blocks/lemcal.pyi new file mode 100644 index 0000000..342fddf --- /dev/null +++ b/reflex_ui/blocks/lemcal.pyi @@ -0,0 +1,23 @@ +"""Lemcal booking integration for Reflex applications.""" + +import reflex as rx +from typing import Any + +def get_lemcal_script() -> rx.Component: ... + +def lemcal_button( + child: rx.Component | None = ..., + label: str = ..., + class_name: str = ..., + user_id: str = ..., + meeting_type: str = ..., + **props: Any, +) -> rx.Component: ... + +def lemcal_calendar( + user_id: str = ..., + meeting_type: str = ..., + class_name: str = ..., + refresh_on_mount: bool = ..., + **props: Any, +) -> rx.Component: ... From 6337409c2710cc9911f6aec55a7bd0316522abf1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:27:21 +0000 Subject: [PATCH 2/2] Fix whitespace and formatting issues in lemcal component - Remove trailing whitespace from blank lines in docstrings - Apply ruff formatting to ensure consistent code style - Resolves pre-commit CI failures Co-Authored-By: Alek --- reflex_ui/blocks/lemcal.py | 18 +++++++++--------- reflex_ui/blocks/lemcal.pyi | 5 ++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/reflex_ui/blocks/lemcal.py b/reflex_ui/blocks/lemcal.py index 8cd3a62..61f9a24 100644 --- a/reflex_ui/blocks/lemcal.py +++ b/reflex_ui/blocks/lemcal.py @@ -1,15 +1,15 @@ """Lemcal booking integration for Reflex applications.""" -import reflex as rx from typing import Any +import reflex as rx LEMCAL_SCRIPT_URL = "https://cdn.lemcal.com/lemcal-integrations.min.js" def get_lemcal_script() -> rx.Component: """Generate Lemcal script component for a Reflex application. - + Returns: rx.Component: Script component needed for Lemcal integration """ @@ -32,7 +32,7 @@ def lemcal_button( Wraps provided child (or a default button) in a div with the Lemcal integration class and data attributes so that the external script can attach the booking behavior. - + Args: child: Custom component to wrap (defaults to a button with label) label: Default button text if no child provided @@ -40,7 +40,7 @@ def lemcal_button( user_id: Lemcal user ID for booking integration meeting_type: Lemcal meeting type ID for booking integration **props: Additional props to pass to the wrapper div - + Returns: rx.Component: Lemcal button wrapper component """ @@ -64,17 +64,17 @@ def lemcal_calendar( **props: Any, ) -> rx.Component: """Lemcal booking calendar embed component. - + Creates a div with the Lemcal calendar integration class and data attributes. Optionally refreshes the Lemcal integration when the component mounts. - + Args: user_id: Lemcal user ID for booking integration meeting_type: Lemcal meeting type ID for booking integration class_name: Additional CSS classes to apply refresh_on_mount: Whether to call window.lemcal.refresh() on mount **props: Additional props to pass to the wrapper div - + Returns: rx.Component: Lemcal calendar embed component """ @@ -86,8 +86,8 @@ def lemcal_calendar( }, **props, } - + if refresh_on_mount: calendar_props["on_mount"] = rx.call_function("window.lemcal.refresh") - + return rx.el.div(**calendar_props) diff --git a/reflex_ui/blocks/lemcal.pyi b/reflex_ui/blocks/lemcal.pyi index 342fddf..51c5923 100644 --- a/reflex_ui/blocks/lemcal.pyi +++ b/reflex_ui/blocks/lemcal.pyi @@ -1,10 +1,10 @@ """Lemcal booking integration for Reflex applications.""" -import reflex as rx from typing import Any -def get_lemcal_script() -> rx.Component: ... +import reflex as rx +def get_lemcal_script() -> rx.Component: ... def lemcal_button( child: rx.Component | None = ..., label: str = ..., @@ -13,7 +13,6 @@ def lemcal_button( meeting_type: str = ..., **props: Any, ) -> rx.Component: ... - def lemcal_calendar( user_id: str = ..., meeting_type: str = ...,