Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit b0e84e0

Browse files
committed
Merge branch 'main' of https://github.com/reflex-dev/reflex-web into carlos/update-blog-page
2 parents 102ab19 + 8efdae2 commit b0e84e0

4 files changed

Lines changed: 130 additions & 70 deletions

File tree

pcweb/components/icons/icons.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,8 @@
538538
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 8.25C9.37459 8.25 6.96692 9.01746 5.19692 10.2958C3.42791 11.5734 2.25 13.4037 2.25 15.5C2.25 17.5963 3.42791 19.4266 5.19692 20.7042C6.96692 21.9825 9.37459 22.75 12 22.75C14.6254 22.75 17.0331 21.9825 18.8031 20.7042C20.5721 19.4266 21.75 17.5963 21.75 15.5C21.75 13.4037 20.5721 11.5734 18.8031 10.2958C17.0331 9.01746 14.6254 8.25 12 8.25ZM7.90846 17.289C8.16308 16.9623 8.63434 16.9038 8.96106 17.1585C9.82307 17.8302 10.8715 18.2199 12 18.2199C13.1286 18.2199 14.177 17.8302 15.039 17.1585C15.3657 16.9038 15.837 16.9623 16.0916 17.289C16.3462 17.6157 16.2878 18.087 15.9611 18.3416C14.8528 19.2053 13.4839 19.7199 12 19.7199C10.5162 19.7199 9.14724 19.2053 8.03901 18.3416C7.71229 18.087 7.65385 17.6157 7.90846 17.289ZM9 12C8.17157 12 7.5 12.6716 7.5 13.5C7.5 14.3284 8.17157 15 9 15H9.00897C9.8374 15 10.509 14.3284 10.509 13.5C10.509 12.6754 9.82084 12 9 12ZM14.991 12C14.1626 12 13.491 12.6716 13.491 13.5C13.491 14.3284 14.1626 15 14.991 15H15C15.8284 15 16.5 14.3284 16.5 13.5C16.5 12.6754 15.8119 12 14.991 12Z" fill="currentColor"></path>
539539
</svg>"""
540540

541+
markdown = """<svg width="27" height="16" viewBox="0 0 27 16" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M24.337 0q-.027 0-.052.026H2.168q-.47 0-.875.22Q.888.469.627.832q-.444.676-.34 1.48v11.975q.027.467.288.844t.626.584q.653.365 1.437.26V16l22.012-.026a1.63 1.63 0 0 0 .849-.286q.378-.259.587-.623.366-.624.261-1.429h.026l-.026-11.922a1.6 1.6 0 0 0-.287-.818 2.2 2.2 0 0 0-.626-.61A2.6 2.6 0 0 0 24.336 0m-.105 1.247h.053q.314 0 .522.13a.6.6 0 0 1 .222.168q.065.091.065.247l.026 11.844v.078q.052.415-.13.753a.6.6 0 0 1-.17.195.42.42 0 0 1-.248.065l-21.934.026h-.079a1.14 1.14 0 0 1-.73-.13.6.6 0 0 1-.223-.168.4.4 0 0 1-.065-.247V2.312l-.026-.104a.9.9 0 0 1 .143-.65q.196-.285.483-.285h21.856zM4.048 3.767v8.493h2.506V7.377l2.507 3.116 2.507-3.116v4.883h2.507V3.766h-2.507L9.06 6.883 6.554 3.766zm14.414 0V8.13h-2.507l3.76 4.13 3.76-4.13h-2.507V3.766z" fill="currentColor"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h27v16H0z"/></clipPath></defs></svg>"""
542+
541543
ICONS = {
542544
# Socials
543545
"github": github,
@@ -621,6 +623,7 @@
621623
"linkedin_blog": linkedin_blog,
622624
"link_blog": link_blog,
623625
"reddit_blog": reddit_blog,
626+
"markdown": markdown,
624627
}
625628

626629

pcweb/pages/blog/page.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def more_posts(current_post: dict) -> rx.Component:
180180
def page(document, route) -> rx.Component:
181181
"""Create a page."""
182182
meta = document.metadata
183-
toc = get_toc(document, route)
183+
toc, _ = get_toc(document, route)
184184
page_url = f"{REFLEX_URL.strip('/')}{route}"
185185
return rx.el.section(
186186
rx.el.article(

pcweb/pages/docs_landing/views/hero.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def hero() -> rx.Component:
1111
rx.el.div(
1212
rx.el.p(
1313
"About Reflex",
14-
class_name="text-sm font-[525] text-m-slate-10 dark:text-m-slate-6",
14+
class_name="text-sm font-[525] text-primary-10 dark:text-m-slate-6",
1515
),
1616
rx.el.h1(
1717
"Reflex Documentation",

pcweb/templates/docpage/docpage.py

Lines changed: 125 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
import reflex as rx
1010
import reflex_ui as ui
1111
from reflex.components.radix.themes.base import LiteralAccentColor
12+
from reflex.experimental.client_state import ClientStateVar
1213
from reflex.utils.format import to_snake_case, to_title_case
1314

14-
from pcweb.components.button import button
1515
from pcweb.components.icons.icons import get_icon
16+
from pcweb.components.marketing_button import button as marketing_button
1617
from pcweb.route import Route, get_path
1718
from pcweb.styles.colors import c_color
1819

@@ -156,8 +157,8 @@ def footer_link_flex(heading: str, links):
156157

157158
def thumb_card(score: int, icon: str) -> rx.Component:
158159
return rx.el.button(
159-
rx.icon(
160-
tag=icon,
160+
ui.icon(
161+
icon,
161162
color=rx.cond(
162163
FeedbackState.score == score, c_color("slate", 11), c_color("slate", 9)
163164
),
@@ -173,53 +174,39 @@ def thumb_card(score: int, icon: str) -> rx.Component:
173174

174175
def thumbs_cards() -> rx.Component:
175176
return rx.hstack(
176-
thumb_card(1, "thumbs-up"),
177-
thumb_card(0, "thumbs-down"),
177+
thumb_card(1, "ThumbsUpIcon"),
178+
thumb_card(0, "ThumbsDownIcon"),
178179
gap="8px",
179180
)
180181

181182

182183
def feedback_content() -> rx.Component:
183-
return rx.box(
184-
rx.box(
185-
rx.text(
186-
"Send feedback",
187-
class_name="font-md text-slate-11",
188-
),
184+
return rx.el.div(
185+
rx.el.div(
189186
rx.form(
190-
rx.box(
191-
rx.el.textarea(
187+
rx.el.div(
188+
ui.textarea(
192189
name="feedback",
193190
placeholder="Write a comment…",
194191
type="text",
195192
max_length=500,
196193
enter_key_submit=True,
197194
resize="vertical",
198195
required=True,
199-
class_name="w-full h-full p-2 text-slate-11 font-small bg-white-1 border border-slate-4 rounded-[10px] max-h-[300px] min-h-[72px] outline-none overflow-y-auto placeholder-slate-9 focus:border-violet-9 focus:border-1",
200196
),
201197
thumbs_cards(),
202-
rx.el.input(
198+
ui.input(
203199
name="email",
204200
type="email",
205201
placeholder="Contact email (optional)",
206202
max_length=100,
207-
class_name="w-full h-full p-2 text-slate-11 font-small bg-white-1 border border-slate-4 rounded-[10px] box-border outline-none placeholder-slate-9 focus:border-violet-9 focus:border-1",
208203
),
209-
rx.box(
210-
rx.popover.close(
211-
button(
212-
"Send",
213-
type="submit",
214-
)
215-
),
216-
rx.popover.close(
217-
button(
218-
"Cancel",
219-
variant="secondary",
220-
)
221-
),
222-
class_name="flex flex-row gap-4 justify-between items-center",
204+
ui.popover.close(
205+
ui.button(
206+
"Send feedback",
207+
type="submit",
208+
class_name="w-full",
209+
)
223210
),
224211
class_name="w-full gap-4 flex flex-col",
225212
),
@@ -229,48 +216,102 @@ def feedback_content() -> rx.Component:
229216
),
230217
class_name="flex flex-col gap-4 w-full",
231218
),
232-
class_name="rounded-[26px] bg-white-1 w-[341px] max-h-[564px] shadow-large h-auto p-4",
219+
class_name="p-2",
233220
)
234221

235222

236223
def feedback_button() -> rx.Component:
237224
thumb_cn = " flex flex-row items-center justify-center gap-2 text-slate-9 whitespace-nowrap border border-slate-5 bg-slate-1 shadow-large cursor-pointer transition-bg hover:bg-slate-3 font-small"
238-
return rx.popover.root(
239-
rx.box(
240-
rx.popover.trigger(
241-
rx.box(
242-
rx.icon(tag="thumbs-up", size=15, class_name="!text-slate-9"),
243-
rx.text(
244-
"Yes",
225+
return ui.popover.root(
226+
ui.popover.trigger(
227+
render_=rx.el.div(
228+
rx.el.button(
229+
ui.icon("ThumbsUpIcon"),
230+
"Yes",
231+
type="button",
232+
class_name=ui.cn(
233+
"w-full gap-2 border-r-0 px-3 py-0.5 rounded-[20px_0_0_20px]",
234+
thumb_cn,
245235
),
246-
class_name="w-full gap-2 border-r-0 px-3 py-0.5 rounded-[20px_0_0_20px]"
247-
+ thumb_cn,
236+
aria_label="Yes",
237+
on_click=FeedbackState.set_score(1),
248238
),
249-
custom_attrs={"role": "button"},
250-
aria_label="Yes",
251-
on_click=FeedbackState.set_score(1),
252-
),
253-
rx.popover.trigger(
254-
rx.box(
255-
rx.icon(tag="thumbs-down", size=15, class_name="!text-slate-9"),
256-
rx.text(
257-
"No",
239+
rx.el.button(
240+
ui.icon("ThumbsDownIcon"),
241+
"No",
242+
type="button",
243+
class_name=ui.cn(
244+
"w-full gap-2 border-r-0 px-3 py-0.5 rounded-[0_20px_20px_0]",
245+
thumb_cn,
258246
),
259-
class_name="w-full gap-2 px-3 py-0.5 rounded-[0_20px_20px_0]"
260-
+ thumb_cn,
247+
aria_label="No",
248+
on_click=FeedbackState.set_score(0),
261249
),
262-
custom_attrs={"role": "button"},
263-
aria_label="No",
264-
on_click=FeedbackState.set_score(0),
250+
class_name="w-full lg:w-auto items-center flex flex-row",
265251
),
266-
class_name="w-full lg:w-auto items-center flex flex-row",
267252
),
268-
rx.popover.content(
269-
feedback_content(),
270-
align="start",
271-
class_name="border-none left-0 lg:left-[-255px] origin-bottom lg:origin-bottom-right !p-0 overflow-visible !bg-transparent shadow-none",
272-
avoid_collisions=True,
253+
ui.popover.portal(
254+
ui.popover.positioner(
255+
ui.popover.popup(
256+
render_=feedback_content(),
257+
),
258+
),
259+
),
260+
)
261+
262+
263+
def feedback_button_toc() -> rx.Component:
264+
return ui.popover(
265+
trigger=marketing_button(
266+
ui.icon("ThumbsUpIcon"),
267+
"Send feedback",
268+
variant="ghost",
269+
size="sm",
270+
type="button",
271+
on_click=FeedbackState.set_score(1),
272+
class_name="justify-start text-m-slate-7 dark:text-m-slate-6",
273273
),
274+
content=feedback_content(),
275+
)
276+
277+
278+
@rx.memo
279+
def copy_to_markdown(text: str) -> rx.Component:
280+
copied = ClientStateVar.create("is_copied", default=False, global_ref=False)
281+
return marketing_button(
282+
rx.cond(
283+
copied.value,
284+
ui.icon(
285+
"CheckmarkCircle02Icon",
286+
),
287+
get_icon("markdown", class_name="[&_svg]:h-4 [&_svg]:w-auto"),
288+
),
289+
"Copy to markdown",
290+
type="button",
291+
size="sm",
292+
variant="ghost",
293+
class_name="justify-start text-m-slate-7 dark:text-m-slate-6",
294+
on_click=[
295+
rx.call_function(copied.set_value(True)),
296+
rx.set_clipboard(text),
297+
],
298+
on_mouse_down=rx.call_function(copied.set_value(False)).debounce(1500),
299+
)
300+
301+
302+
def ask_ai_chat() -> rx.Component:
303+
from pcweb.pages.docs import ai_builder as ai_builder_pages
304+
305+
return rx.el.a(
306+
marketing_button(
307+
ui.icon("AiChat02Icon"),
308+
"Ask AI about this page",
309+
size="sm",
310+
variant="ghost",
311+
class_name="justify-start text-m-slate-7 dark:text-m-slate-6",
312+
native_button=False,
313+
),
314+
to=ai_builder_pages.integrations.mcp_overview.path,
274315
)
275316

276317

@@ -474,11 +515,11 @@ def get_toc(source, href, component_list=None):
474515
env["__xd"] = xd
475516

476517
# Get the content of the document.
477-
source = source.content
518+
doc_content = source.content
478519

479520
# Get the blocks in the source code.
480521
# Note: we must use reflex-web's special flexdown instance xd here - it knows about all custom block types (like DemoBlock)
481-
blocks = xd.get_blocks(source, href)
522+
blocks = xd.get_blocks(doc_content, href)
482523

483524
content_pieces = []
484525
for block in blocks:
@@ -502,7 +543,7 @@ def get_toc(source, href, component_list=None):
502543
headings.append((1, "API Reference"))
503544
for component_tuple in component_list:
504545
headings.append((2, component_tuple[1]))
505-
return headings
546+
return headings, doc_content
506547

507548

508549
def docpage(
@@ -617,13 +658,21 @@ def wrapper(*args, **kwargs) -> rx.Component:
617658
links.append(rx.fragment())
618659

619660
toc = []
661+
doc_content = None
620662
if not isinstance(contents, rx.Component):
621663
comp = contents(*args, **kwargs)
622664
else:
623665
comp = contents
624666

625-
if isinstance(comp, tuple):
626-
toc, comp = comp
667+
if isinstance(comp, tuple) and len(comp) == 2:
668+
first, second = comp
669+
# Check if first is (toc, doc_content) from get_toc
670+
if isinstance(first, tuple) and len(first) == 2:
671+
toc, doc_content = first
672+
comp = second
673+
else:
674+
# Legacy format: (toc, comp)
675+
toc, comp = first, second
627676

628677
show_right_sidebar = right_sidebar and len(toc) >= 2
629678
return rx.box(
@@ -715,9 +764,18 @@ def wrapper(*args, **kwargs) -> rx.Component:
715764
)
716765
for level, text in toc
717766
],
718-
class_name="flex flex-col gap-y-1 list-none shadow-[1.5px_0_0_0_var(--m-slate-4)_inset] dark:shadow-[1.5px_0_0_0_var(--m-slate-9)_inset]",
767+
id="toc-navigation",
768+
class_name="flex flex-col gap-y-1 list-none shadow-[1.5px_0_0_0_var(--m-slate-4)_inset] dark:shadow-[1.5px_0_0_0_var(--m-slate-9)_inset] max-h-[80vh]",
769+
),
770+
rx.el.div(
771+
feedback_button_toc(),
772+
copy_to_markdown(text=doc_content)
773+
if doc_content
774+
else None,
775+
ask_ai_chat(),
776+
class_name="flex flex-col mt-1.5 justify-start",
719777
),
720-
class_name="flex flex-col justify-start gap-y-4 max-h-[80vh] overflow-y-auto sticky top-4",
778+
class_name="flex flex-col justify-start gap-y-4 overflow-y-auto sticky top-4",
721779
),
722780
class_name=(
723781
"w-full h-full"
@@ -727,7 +785,6 @@ def wrapper(*args, **kwargs) -> rx.Component:
727785
" mt-[90px]",
728786
)
729787
),
730-
id="toc-navigation",
731788
),
732789
class_name=(
733790
"w-[240px] h-screen sticky top-0 shrink-0 hidden xl:block"

0 commit comments

Comments
 (0)