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..61f9a24 --- /dev/null +++ b/reflex_ui/blocks/lemcal.py @@ -0,0 +1,93 @@ +"""Lemcal booking integration for Reflex applications.""" + +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 + """ + 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..51c5923 --- /dev/null +++ b/reflex_ui/blocks/lemcal.pyi @@ -0,0 +1,22 @@ +"""Lemcal booking integration for Reflex applications.""" + +from typing import Any + +import reflex as rx + +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: ...