Skip to content

Commit adc6999

Browse files
authored
Merge branch 'main' into carlos/prompt-box-again
2 parents 749b62d + 9d25b05 commit adc6999

File tree

18 files changed

+802
-444
lines changed

18 files changed

+802
-444
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
### **✨ The open-source framework to build and deploy web apps - no Javascript required. ✨**
88
[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex)
9-
![tests](https://github.com/pynecone-io/pynecone/actions/workflows/integration.yml/badge.svg)
9+
[![tests](https://github.com/reflex-dev/reflex-web/actions/workflows/integration_tests.yml/badge.svg)](https://github.com/reflex-dev/reflex-web/actions/workflows/integration_tests.yml)
1010
![versions](https://img.shields.io/pypi/pyversions/reflex.svg)
1111
[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction/)
1212
[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ)

docs/wrapping-react/props.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,49 @@ class EventHandlerComponent(MyBaseComponent):
154154

155155
```md alert info
156156
# Custom event specs have a few use case where they are particularly useful. If the event returns non-serializable data, you can filter them out so the event can be sent to the backend. You can also use them to transform the data before sending it to the backend.
157+
```
158+
159+
### Emulating Event Handler Behavior Outside a Component
160+
161+
In some instances, you may need to replicate the special behavior applied to
162+
event handlers from outside of a component context. For example if the component
163+
to be wrapped requires event callbacks passed in a dictionary, this can be
164+
achieved by directly instantiating an `EventChain`.
165+
166+
A real-world example of this is the `onEvents` prop of
167+
[`echarts-for-react`](https://www.npmjs.com/package/echarts-for-react) library,
168+
which, unlike a normal event handler, expects a mapping of event handlers like:
169+
170+
```javascript
171+
<ReactECharts
172+
option={this.getOption()}
173+
style={{ height: '300px', width: '100%' }}
174+
onEvents={{
175+
'click': this.onChartClick,
176+
'legendselectchanged': this.onChartLegendselectchanged
177+
}}
178+
/>
179+
```
180+
181+
To achieve this in Reflex, you can create an explicit `EventChain` for each
182+
event handler:
183+
184+
```python
185+
@classmethod
186+
def create(cls, *children, **props):
187+
on_events = props.pop("on_events", {})
188+
189+
event_chains = {}
190+
for event_name, handler in on_events.items():
191+
# Convert the EventHandler/EventSpec/lambda to an EventChain
192+
event_chains[event_name] = rx.EventChain.create(
193+
handler,
194+
args_spec=rx.event.no_args_event_spec,
195+
key=event_name,
196+
)
197+
if on_events:
198+
props["on_events"] = event_chains
199+
200+
# Create the component instance
201+
return super().create(*children, **props)
157202
```

pcweb/components/docpage/navbar/navbar.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from pcweb.pages.framework.framework import framework
1414
from pcweb.pages.hosting.hosting import hosting_landing
1515
from pcweb.pages.use_cases.use_cases import use_cases_page
16-
16+
from reflex_ui.blocks.lemcal import lemcal_dialog, LEMCAL_DEMO_URL
17+
import reflex_ui as ui
1718
from ...link_button import resources_button
1819
from ..sidebar import SidebarState
1920
from .buttons.discord import discord
@@ -443,7 +444,7 @@ def new_component_section() -> rx.Component:
443444
"hidden lg:flex",
444445
"hidden",
445446
)
446-
)
447+
),
447448
),
448449
class_name="flex flex-row gap-x-0 items-center",
449450
),
@@ -534,14 +535,13 @@ def new_component_section() -> rx.Component:
534535
class_name="desktop-only",
535536
),
536537
nav_menu.item(
537-
rx.link(
538-
button(
538+
ui.link(
539+
render_=button(
539540
"Book a Demo",
540541
class_name="!h-8 !font-small-smbold !rounded-[0.625rem] whitespace-nowrap",
541542
),
542-
underline="none",
543-
is_external=True,
544-
href="/pricing",
543+
target="_blank",
544+
to=LEMCAL_DEMO_URL,
545545
),
546546
class_name="xl:flex hidden",
547547
),

pcweb/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
REFLEX_DEV_WEB_LANDING_FORM_SALES_CALL_WEBHOOK_URL: str = (
66
"https://hooks.zapier.com/hooks/catch/20661176/2s1nxp9/"
77
)
8+
REFLEX_DEV_WEB_LANDING_FORM_URL_GET_DEMO: str = (
9+
"https://cal.com/forms/f87bd9b2-b339-4915-b4d4-0098e2db4394"
10+
)
811

912
REFLEX_DEV_WEB_NEWSLETTER_FORM_WEBHOOK_URL: str = "https://hkdk.events/t0qopjbznnp2fr"
1013
REFLEX_DEV_WEB_GENERAL_FORM_FEEDBACK_WEBHOOK_URL: str = os.environ.get(

pcweb/pages/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .page404 import page404 as page404
1919
from .pricing.pricing import pricing as pricing
2020
from .sales import sales as sales
21+
from .demo.book_demo import book_demo as book_demo
2122
from .booked import booked as booked
2223
from .to_be_booked import to_be_booked as to_be_booked
2324

pcweb/pages/demo/book_demo.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import reflex as rx
2+
from pcweb.pages.framework.index_colors import index_colors
3+
from pcweb.pages.framework.views.footer_index import footer_index
4+
from pcweb.meta.meta import meta_tags
5+
from pcweb.pages.demo.header import header
6+
from pcweb.templates.mainpage import mainpage
7+
from pcweb.components.icons.patterns import default_patterns
8+
9+
10+
@mainpage(path="/demo", title="Reflex · Book Demo", meta=meta_tags)
11+
def book_demo() -> rx.Component:
12+
"""Get the Book Demo landing page."""
13+
14+
return rx.el.main(
15+
rx.el.section(
16+
*default_patterns(),
17+
header(),
18+
class_name="overflow-hidden w-full relative mx-auto flex flex-col justify-center items-center max-w-[64.19rem]",
19+
),
20+
class_name="flex flex-col w-full max-w-[94.5rem] justify-center items-center mx-auto px-4 lg:px-5 relative overflow-hidden",
21+
)

pcweb/pages/demo/header.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import reflex as rx
2+
import reflex_ui as ui
3+
from reflex_ui.blocks.lemcal import lemcal_dialog, LEMCAL_DEMO_URL
4+
from pcweb.components.hosting_banner import HostingBannerState
5+
from pcweb.pages.framework.views.companies import pricing_page_companies
6+
7+
8+
def custom_quote_form() -> rx.Component:
9+
"""Custom quote form component with clean, maintainable structure."""
10+
return rx.box(
11+
rx.box(
12+
rx.el.h2(
13+
"Book a Demo",
14+
class_name="text-slate-12 text-4xl font-bold",
15+
),
16+
rx.el.p(
17+
"Enterprise-ready solutions designed for scale, compliance, and support. Contact us for a tailored quote based on your infrastructure and team size.",
18+
class_name="text-slate-11 text-md leading-relaxed font-medium text-center max-w-xl mx-auto",
19+
),
20+
ui.link(
21+
render_=rx.el.div(
22+
ui.button(
23+
"Contact Sales",
24+
class_name="font-semibold",
25+
size="lg",
26+
),
27+
class_name="p-3 border border-slate-3 rounded-[1.375rem] border-solid mt-2",
28+
),
29+
target="_blank",
30+
to=LEMCAL_DEMO_URL,
31+
),
32+
rx.box(
33+
rx.el.span(
34+
"Trusted by",
35+
class_name="text-slate-10 text-sm leading-relaxed font-medium text-center max-w-lg mx-auto mt-2",
36+
),
37+
pricing_page_companies(),
38+
class_name="flex flex-col items-center justify-center gap-4",
39+
),
40+
class_name="flex flex-col items-center justify-center gap-6",
41+
),
42+
id="demo-form",
43+
class_name="pb-12 sm:pb-20 px-4 sm:px-8 scroll-m-20 text-center",
44+
)
45+
46+
47+
def header() -> rx.Component:
48+
return rx.box(
49+
custom_quote_form(),
50+
class_name="flex flex-col gap-2 justify-center items-center max-w-[64.19rem] xl:border-x border-slate-4 w-full -mb-10 pb-16 "
51+
+ rx.cond(
52+
HostingBannerState.show_banner,
53+
"pt-[8rem] lg:pt-[11rem]",
54+
"pt-[8rem] lg:pt-[12rem]",
55+
),
56+
)

pcweb/pages/docs/apiref.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
rx.Base,
1010
rx.Component,
1111
rx.ComponentState,
12-
rx.Config,
12+
(rx.Config, rx.config.BaseConfig),
1313
rx.event.Event,
1414
rx.event.EventHandler,
1515
rx.event.EventSpec,
@@ -26,9 +26,17 @@
2626

2727
pages = []
2828
for module in modules:
29+
if isinstance(module, tuple):
30+
module, *extra_modules = module
31+
extra_fields = []
32+
for extra_module in extra_modules:
33+
s_extra = Source(module=extra_module)
34+
extra_fields.extend(s_extra.get_fields())
35+
else:
36+
extra_fields = None
2937
s = Source(module=module)
3038
name = module.__name__.lower()
31-
docs = generate_docs(name, s)
39+
docs = generate_docs(name, s, extra_fields=extra_fields)
3240
title = name.replace("_", " ").title()
3341
page_data = docpage(f"/docs/api-reference/{name}/", title)(docs)
3442
page_data.title = page_data.title.split("·")[0].strip()

pcweb/pages/docs/source.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import dataclasses
12
import inspect
23
import re
34

45
# Get the comment for a specific field.
5-
from typing import Callable, Type
6+
from typing import Callable, ClassVar, Type, get_origin, get_type_hints
67

78
import reflex as rx
89
from pcweb.templates.docpage import h1_comp, h2_comp
@@ -54,9 +55,11 @@ def get_class_fields(self) -> list[dict]:
5455
return out
5556

5657
def get_fields(self) -> list[dict]:
57-
if not issubclass(self.module, rx.Base):
58-
return []
59-
return self.get_annotations(self.module.__fields__)
58+
if dataclasses.is_dataclass(self.module):
59+
return self.get_annotations({f.name: f for f in dataclasses.fields(self.module)})
60+
elif isinstance(self.module, type) and issubclass(self.module, rx.Base):
61+
return self.get_annotations(self.module.__fields__)
62+
return []
6063

6164
def get_methods(self):
6265
return [
@@ -170,8 +173,16 @@ def get_annotations(self, props) -> list[dict]:
170173

171174

172175
def format_field(field):
173-
type_ = field["prop"].type_
176+
prop = field["prop"]
177+
try:
178+
type_ = prop.type_
179+
except AttributeError:
180+
type_ = prop.type
174181
default = field["prop"].default
182+
if default is dataclasses.MISSING:
183+
default = None
184+
else:
185+
default = repr(default)
175186
type_str = type_.__name__ if hasattr(type_, "__name__") else str(type_)
176187
if default:
177188
type_str += f" = {default}"
@@ -217,8 +228,10 @@ def format_fields(headers, fields):
217228
)
218229

219230

220-
def generate_docs(title: str, s: Source):
231+
def generate_docs(title: str, s: Source, extra_fields: list[dict] | None = None):
221232
fields = s.get_fields()
233+
if extra_fields:
234+
fields.extend(extra_fields)
222235
class_fields = s.get_class_fields()
223236
return rx.box(
224237
h1_comp(text=title.title()),

0 commit comments

Comments
 (0)