Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/advanced_onboarding/code_structure.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

```python exec
from reflex_docs.pages.docs import custom_components
```

# Project Structure (Advanced)

## App Module
Expand Down Expand Up @@ -255,7 +259,7 @@ component.

### External Components

Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview), which makes it easy
Reflex 0.4.3 introduced support for the [`reflex component` CLI commands]({custom_components.overview.path}), which makes it easy
to bundle up common functionality to publish on PyPI as a standalone Python package
that can be installed and used in any Reflex app.

Expand Down
6 changes: 5 additions & 1 deletion docs/advanced_onboarding/configuration.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

```python exec
from reflex_docs.pages.docs import api_reference
```

# Configuration

Reflex apps can be configured using a configuration file, environment variables, and command line arguments.
Expand Down Expand Up @@ -42,7 +46,7 @@ Finally, you can override the configuration file and environment variables by pa
uv run reflex run --frontend-port 3001
```

See the [CLI reference](/docs/api-reference/cli) for all the arguments available.
See the [CLI reference]({api_reference.cli.path}) for all the arguments available.

## Customizable App Data Directory

Expand Down
14 changes: 9 additions & 5 deletions docs/advanced_onboarding/how-reflex-works.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@

```python exec
from reflex_docs.pages.docs import custom_components, events, styling, wrapping_react
```
# How Reflex Works

We'll use the following basic app that displays Github profile images as an example to explain the different parts of the architecture.
Expand Down Expand Up @@ -114,15 +118,15 @@ Many of our core components are based on [Radix](https://radix-ui.com/), a popul

We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers.

This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview) and then [publish them](/docs/custom-components/overview) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview) so that users can easily find and use components that others have built.
This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components]({wrapping_react.overview.path}) and then [publish them]({custom_components.overview.path}) for others to use. Over time we will build out our [third party component ecosystem]({custom_components.overview.path}) so that users can easily find and use components that others have built.

### Styling

We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app.

We have a core [theming system](/docs/styling/theming) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel.
We have a core [theming system]({styling.theming.path}) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel.

Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive) by passing a list of values.
Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props]({styling.responsive.path}) by passing a list of values.

## Backend

Expand Down Expand Up @@ -197,7 +201,7 @@ On the frontend, we maintain an event queue of all pending events.
When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time.

```md alert info
# There are exceptions to this, such as [background events](/docs/events/background_events) which allow you to run events in the background without blocking the UI.
# There are exceptions to this, such as [background events]({events.background_events.path}) which allow you to run events in the background without blocking the UI.
```

Once the event is ready to be processed, it is sent to the backend through a WebSocket connection.
Expand Down Expand Up @@ -225,7 +229,7 @@ In our example, the `set_profile` event handler is run on the user's state. This

### State Updates

Every time an event handler returns (or [yields](/docs/events/yield_events)), we save the state in the state manager and send the **state updates** to the frontend to update the UI.
Every time an event handler returns (or [yields]({events.yield_events.path})), we save the state in the state manager and send the **state updates** to the frontend to update the UI.

To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend.

Expand Down
6 changes: 5 additions & 1 deletion docs/ai_builder/files.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@

```python exec
from reflex_docs.pages.docs import ai_builder
```
# Files

To upload a file to the AI Builder click the `📎 Attach` button and select the file you want to upload from your computer. You can also drag and drop files directly into the chat window.

This section does not cover uploading images. Check out [Images](/docs/ai-builder/images/) to learn more about uploading images.
This section does not cover uploading images. Check out [Images]({ai_builder.images.path}) to learn more about uploading images.

```md alert
## Supported File Types
Expand Down
6 changes: 4 additions & 2 deletions docs/api-reference/browser_javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import asyncio
from typing import Any
import reflex as rx

from reflex_docs.pages.docs import library, wrapping_react
```

# Browser Javascript
Expand All @@ -23,7 +25,7 @@ Prefer to use the Python API whenever possible and file an issue if you need add

There are four ways to execute custom Javascript code into your Reflex app:

- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script).
- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}).
- These components can be directly included in the body of a page, or they may
be passed to `rx.App(head_components=[rx.script(...)])` to be included in
the `<Head>` tag of all pages.
Expand All @@ -34,7 +36,7 @@ These previous two methods can work in tandem to load external scripts and then
call functions defined within them in response to user events.

The following two methods are geared towards wrapping components and are
described with examples in the [Wrapping React](/docs/wrapping-react/overview)
described with examples in the [Wrapping React]({wrapping_react.overview.path})
section.

- `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass
Expand Down
4 changes: 3 additions & 1 deletion docs/api-reference/event_triggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from datetime import datetime

import reflex as rx

from reflex_docs.pages.docs import events
```

# Event Triggers
Expand Down Expand Up @@ -73,7 +75,7 @@ def protected_page():
return rx.text("Protected content")
```

For more details on page load events, see the [page load events documentation](/docs/events/page_load_events).
For more details on page load events, see the [page load events documentation]({events.page_load_events.path}).

# Event Reference

Expand Down
6 changes: 3 additions & 3 deletions docs/api-reference/special_events.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
```python exec
import reflex as rx

from reflex_docs.pages.docs import api_reference
```

# Special Events
Expand Down Expand Up @@ -40,9 +42,7 @@ Redirect the user to a new path within the application.

```python demo
rx.vstack(
rx.button(
"open in tab", on_click=rx.redirect("/docs/api-reference/special-events")
),
rx.button("open in tab", on_click=rx.redirect(api_reference.special_events.path)),
rx.button(
"open in new tab",
on_click=rx.redirect("https://github.com/reflex-dev/reflex/", is_external=True),
Expand Down
126 changes: 87 additions & 39 deletions docs/app/reflex_docs/docgen_pipeline.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Pipeline for rendering reflex-shipped docs via reflex_docgen.markdown."""

import re
import sys
import types
from pathlib import Path
Expand Down Expand Up @@ -145,35 +146,47 @@ def _exec_code(content: str, env: dict, filename: str) -> None:
# Span → rx.Component helpers
# ---------------------------------------------------------------------------

_BRACE_RE = re.compile(r"\{([^{}]+)\}")

def _render_spans(spans: tuple[Span, ...]) -> list[rx.Component | str]:
"""Convert a sequence of spans into a list of Reflex children."""
out: list[rx.Component | str] = []
for span in spans:
match span:
case TextSpan(text=text):
out.append(text)
case BoldSpan(children=children):
out.append(rx.el.strong(*_render_spans(children)))
case ItalicSpan(children=children):
out.append(rx.el.em(*_render_spans(children)))
case StrikethroughSpan(children=children):
inner = "".join(
c if isinstance(c, str) else "" for c in _render_spans(children)
)
out.append(rx.text("~" + inner + "~", as_="span"))
case CodeSpan(code=code):
out.append(code_comp(text=code))
case LinkSpan(children=children, target=target):
inner = "".join(
c if isinstance(c, str) else "" for c in _render_spans(children)
)
out.append(doclink2(text=inner, href=target))
case ImageSpan(src=src):
out.append(img_comp_xd(src=src))
case LineBreakSpan(soft=soft):
out.append("\n" if soft else rx.el.br())
return out

def _resolve_link_target(target: str, env: dict) -> str:
"""Resolve ``{a.b.c}`` dotted-path substrings in a markdown link href.

Supports composition like ``{enterprise.overview.path}#section``. The
head name is looked up in *env*; subsequent segments are ``getattr``
walks. No ``eval`` — only attribute access is permitted.

Args:
target: Raw href from the markdown source.
env: Doc exec environment (the transformer's accumulated namespace).

Returns:
Resolved href.

Raises:
ValueError: If a brace expression cannot be resolved against *env*.
TypeError: If a brace expression resolves to a non-string.
"""

def _sub(match: re.Match[str]) -> str:
expr = match.group(1).strip()
parts = expr.split(".")
try:
value = env[parts[0]]
for part in parts[1:]:
value = getattr(value, part)
except (KeyError, AttributeError) as e:
msg = f"Link {target!r}: cannot resolve {match.group(0)!r} ({e})"
raise ValueError(msg) from e
if not isinstance(value, str):
msg = (
f"Link {target!r}: {match.group(0)!r} must resolve to str, "
f"got {type(value).__name__}"
)
raise TypeError(msg)
return value
Comment thread
carlosabadia marked this conversation as resolved.

return _BRACE_RE.sub(_sub, target.strip())


def _spans_to_plaintext(spans: tuple[Span, ...]) -> str:
Expand Down Expand Up @@ -233,6 +246,42 @@ def transform(self, document: Document) -> rx.Component:

return rx.fragment(*children)

def _render_spans(self, spans: tuple[Span, ...]) -> list[rx.Component | str]:
"""Convert a sequence of spans into a list of Reflex children.

Link hrefs are resolved against ``self.env`` so markdown can reference
Python paths like ``[…]({enterprise.overview.path})``.
"""
out: list[rx.Component | str] = []
for span in spans:
match span:
case TextSpan(text=text):
out.append(text)
case BoldSpan(children=children):
out.append(rx.el.strong(*self._render_spans(children)))
case ItalicSpan(children=children):
out.append(rx.el.em(*self._render_spans(children)))
case StrikethroughSpan(children=children):
inner = "".join(
c if isinstance(c, str) else ""
for c in self._render_spans(children)
)
out.append(rx.text("~" + inner + "~", as_="span"))
case CodeSpan(code=code):
out.append(code_comp(text=code))
case LinkSpan(children=children, target=target):
inner = "".join(
c if isinstance(c, str) else ""
for c in self._render_spans(children)
)
href = _resolve_link_target(target, self.env)
out.append(doclink2(text=inner, href=href))
case ImageSpan(src=src):
out.append(img_comp_xd(src=src))
case LineBreakSpan(soft=soft):
out.append("\n" if soft else rx.el.br())
return out

# ------------------------------------------------------------------
# Blocks
# ------------------------------------------------------------------
Expand All @@ -253,7 +302,7 @@ def heading(self, block: HeadingBlock) -> rx.Component:
return h4_comp_xd(text=text)

def text_block(self, block: TextBlock) -> rx.Component:
children = _render_spans(block.children)
children = self._render_spans(block.children)
if len(children) == 1 and isinstance(children[0], str):
return text_comp(text=children[0])
return rx.text(
Expand Down Expand Up @@ -310,13 +359,12 @@ def list_block(self, block: ListBlock) -> rx.Component:
return rx.list.ordered(*items, class_name="mb-6")
return rx.list.unordered(*items, class_name="mb-6")

@staticmethod
def _list_item_from_spans(spans: tuple[Span, ...]) -> rx.Component:
def _list_item_from_spans(self, spans: tuple[Span, ...]) -> rx.Component:
"""Render a list item, preserving inline code/links when present."""
if all(isinstance(s, TextSpan) for s in spans):
return list_comp(text=_spans_to_plaintext(spans))
return rx.list_item(
*_render_spans(spans),
*self._render_spans(spans),
class_name="font-[475] text-secondary-11 mb-4",
)

Expand All @@ -342,7 +390,7 @@ def quote(self, block: QuoteBlock) -> rx.Component:
def table(self, block: TableBlock) -> rx.Component:
header_cells = [
rx.table.column_header_cell(
*_render_spans(cell.children),
*self._render_spans(cell.children),
class_name="font-small text-slate-12 font-bold",
)
for cell in block.header.cells
Expand All @@ -351,7 +399,7 @@ def table(self, block: TableBlock) -> rx.Component:
for row in block.rows:
cells = [
rx.table.cell(
*_render_spans(cell.children),
*self._render_spans(cell.children),
class_name="font-small text-slate-11",
)
for cell in row.cells
Expand All @@ -371,7 +419,7 @@ def transform_table_row(self, row: TableRow) -> rx.Component:
return rx.table.row(*cells)

def transform_table_cell(self, cell: TableCell) -> rx.Component:
return rx.table.cell(*_render_spans(cell.children))
return rx.table.cell(*self._render_spans(cell.children))

def thematic_break(self, block: ThematicBreakBlock) -> rx.Component:
return rx.separator(class_name="my-6")
Expand All @@ -398,7 +446,7 @@ def code_span(self, span: CodeSpan) -> rx.Component:

def link(self, span: LinkSpan) -> rx.Component:
inner = _spans_to_plaintext(span.children)
return doclink2(text=inner, href=span.target)
return doclink2(text=inner, href=_resolve_link_target(span.target, self.env))

def image(self, span: ImageSpan) -> rx.Component:
return img_comp_xd(src=span.src)
Expand Down Expand Up @@ -546,7 +594,7 @@ def _render_alert(self, block: DirectiveBlock) -> rx.Component:

def title_comp() -> rx.Component:
return rx.box(
*_render_spans(title_spans),
*self._render_spans(title_spans),
class_name="font-[475]",
color=f"{rx.color(color, 11)}",
)
Expand Down Expand Up @@ -583,7 +631,7 @@ def title_comp() -> rx.Component:
spans: list[rx.Component | str] = []
for child in children:
if isinstance(child, TextBlock):
spans.extend(_render_spans(child.children))
spans.extend(self._render_spans(child.children))
else:
spans.append(self.transform_block(child))
trigger.append(
Expand Down Expand Up @@ -642,7 +690,7 @@ def _render_quote_directive(self, block: DirectiveBlock) -> rx.Component:
role = ""
for child in block.children:
if isinstance(child, TextBlock):
quote_parts.extend(_render_spans(child.children))
quote_parts.extend(self._render_spans(child.children))
elif isinstance(child, ListBlock):
for item in child.items:
for sub in item.children:
Expand Down
5 changes: 5 additions & 0 deletions docs/app/reflex_docs/pages/docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,10 @@ def register_doc(virtual_doc: str, comp):
get_component_docgen(_virtual, _actual, doc_title_from_path(_virtual)),
)

# Top-level pages get shadowed by the subpage SimpleNamespace below; copy
# their .path across so markdown can reference {library.path} etc.
docs_ns.library.path = library.path # type: ignore[attr-defined]
docs_ns.custom_components.path = custom_components.path # type: ignore[attr-defined]

for name, ns in docs_ns.__dict__.items():
globals()[name] = ns
Loading
Loading