Skip to content

Commit 25b91e1

Browse files
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 <[email protected]>
1 parent cc6e1d1 commit 25b91e1

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

demo/demo/demo.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import reflex as rx
44

55
import reflex_ui as ui
6+
from reflex_ui.blocks.lemcal import get_lemcal_script, lemcal_button, lemcal_calendar
67

78

89
class State(rx.State):
@@ -58,6 +59,12 @@ def index() -> rx.Component:
5859
on_value_change=lambda value: rx.toast.success(f"Value: {value}"),
5960
on_open_change=lambda value: rx.toast.success(f"Open: {value}"),
6061
),
62+
rx.el.h3("Lemcal Integration Demo", class_name="text-lg font-semibold mt-8 mb-4"),
63+
lemcal_button(
64+
ui.button("Book a Demo", variant="outline"),
65+
class_name="mb-4",
66+
),
67+
lemcal_calendar(class_name="w-full max-w-md h-96 border rounded-lg"),
6168
ui.theme_switcher(class_name="absolute top-4 right-4"),
6269
class_name=ui.cn(
6370
"flex flex-col gap-6 items-center justify-center h-screen", "bg-secondary-1"
@@ -85,6 +92,7 @@ def index() -> rx.Component:
8592
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:[email protected]&display=swap",
8693
rel="stylesheet",
8794
),
95+
get_lemcal_script(),
8896
],
8997
)
9098
app.add_page(index)

reflex_ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"components.icons.hugeicon": ["hi", "icon"],
4040
"components.icons.others": ["spinner"],
4141
"utils.twmerge": ["cn"],
42+
"blocks.lemcal": ["get_lemcal_script", "lemcal_button", "lemcal_calendar"],
4243
}
4344

4445
getattr, __dir__, __all__ = lazy_loader.attach(

reflex_ui/blocks/lemcal.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Lemcal booking integration for Reflex applications."""
2+
3+
import reflex as rx
4+
from typing import Any
5+
6+
7+
LEMCAL_SCRIPT_URL = "https://cdn.lemcal.com/lemcal-integrations.min.js"
8+
9+
10+
def get_lemcal_script() -> rx.Component:
11+
"""Generate Lemcal script component for a Reflex application.
12+
13+
Returns:
14+
rx.Component: Script component needed for Lemcal integration
15+
"""
16+
return rx.el.script(
17+
src=LEMCAL_SCRIPT_URL,
18+
defer=True,
19+
)
20+
21+
22+
def lemcal_button(
23+
child: rx.Component | None = None,
24+
label: str = "Book a Demo",
25+
class_name: str = "",
26+
user_id: str = "usr_8tiwtJ8nEJaFj2qH9",
27+
meeting_type: str = "met_ToQQ9dLZDYrEBv5qz",
28+
**props: Any,
29+
) -> rx.Component:
30+
"""Reusable Lemcal embed button wrapper.
31+
32+
Wraps provided child (or a default button) in a div with the Lemcal
33+
integration class and data attributes so that the external script can
34+
attach the booking behavior.
35+
36+
Args:
37+
child: Custom component to wrap (defaults to a button with label)
38+
label: Default button text if no child provided
39+
class_name: Additional CSS classes to apply
40+
user_id: Lemcal user ID for booking integration
41+
meeting_type: Lemcal meeting type ID for booking integration
42+
**props: Additional props to pass to the wrapper div
43+
44+
Returns:
45+
rx.Component: Lemcal button wrapper component
46+
"""
47+
content = child if child is not None else rx.el.button(label)
48+
return rx.el.div(
49+
content,
50+
class_name=("lemcal-embed-button " + class_name).strip(),
51+
custom_attrs={
52+
"data-user": user_id,
53+
"data-meeting-type": meeting_type,
54+
},
55+
**props,
56+
)
57+
58+
59+
def lemcal_calendar(
60+
user_id: str = "usr_8tiwtJ8nEJaFj2qH9",
61+
meeting_type: str = "met_ToQQ9dLZDYrEBv5qz",
62+
class_name: str = "",
63+
refresh_on_mount: bool = True,
64+
**props: Any,
65+
) -> rx.Component:
66+
"""Lemcal booking calendar embed component.
67+
68+
Creates a div with the Lemcal calendar integration class and data attributes.
69+
Optionally refreshes the Lemcal integration when the component mounts.
70+
71+
Args:
72+
user_id: Lemcal user ID for booking integration
73+
meeting_type: Lemcal meeting type ID for booking integration
74+
class_name: Additional CSS classes to apply
75+
refresh_on_mount: Whether to call window.lemcal.refresh() on mount
76+
**props: Additional props to pass to the wrapper div
77+
78+
Returns:
79+
rx.Component: Lemcal calendar embed component
80+
"""
81+
calendar_props = {
82+
"class_name": ("lemcal-embed-booking-calendar " + class_name).strip(),
83+
"custom_attrs": {
84+
"data-user": user_id,
85+
"data-meeting-type": meeting_type,
86+
},
87+
**props,
88+
}
89+
90+
if refresh_on_mount:
91+
calendar_props["on_mount"] = rx.call_function("window.lemcal.refresh")
92+
93+
return rx.el.div(**calendar_props)

reflex_ui/blocks/lemcal.pyi

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Lemcal booking integration for Reflex applications."""
2+
3+
import reflex as rx
4+
from typing import Any
5+
6+
def get_lemcal_script() -> rx.Component: ...
7+
8+
def lemcal_button(
9+
child: rx.Component | None = ...,
10+
label: str = ...,
11+
class_name: str = ...,
12+
user_id: str = ...,
13+
meeting_type: str = ...,
14+
**props: Any,
15+
) -> rx.Component: ...
16+
17+
def lemcal_calendar(
18+
user_id: str = ...,
19+
meeting_type: str = ...,
20+
class_name: str = ...,
21+
refresh_on_mount: bool = ...,
22+
**props: Any,
23+
) -> rx.Component: ...

0 commit comments

Comments
 (0)