Skip to content

Commit 1c3a508

Browse files
authored
Align XY docs with the shared Reflex theme (#514)
* Align XY docs with the shared Reflex theme * Space the mobile docs GitHub action below navigation * Mute XY wordmark suffix to match Reflex docs * Require the standard namespace in sitemap validation * Address docs theme review regressions * Hide the Reflex badge in XY docs * bump * Remove XY sidebar category icons * Use Reflex main and align sidebar tests with icon-free rows * Link XY navbar logo to the main docs home
1 parent 8d84ec1 commit 1c3a508

22 files changed

Lines changed: 861 additions & 838 deletions

‎docs/app/README.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,28 @@ uv run --no-sync python scripts/check_html_routes.py
7171

7272
The frontend is mounted at `/docs/xy`; preserve that prefix in canonical URLs,
7373
the sitemap, Markdown aliases, and internal documentation links.
74+
75+
## Working on the shared Reflex theme
76+
77+
The theme, button and search assets, navbar frame, announcement, sidebar shell,
78+
and footer primitives come directly from `reflex-site-shared`. Do not copy them
79+
into XY. XY owns its wordmark, navigation destinations, page actions, and footer
80+
links.
81+
82+
To preview unpublished edits from a Reflex checkout, first install the locked
83+
XY docs environment, then install just the shared package editably (run from
84+
`docs/app`; replace the checkout path as needed):
85+
86+
```bash
87+
HATCH_BUILD_NO_HOOKS=true uv pip install --python .venv/bin/python --no-deps \
88+
--editable ~/Drive/reflex-work/reflex/reflex/packages/reflex-site-shared
89+
uv run --no-sync reflex run --env prod
90+
```
91+
92+
`--no-deps` retains XY's locked framework environment; disabling build hooks
93+
avoids generating upstream stubs just to preview the site. Use `--no-sync` for
94+
subsequent commands, and restart after shared Python or theme asset changes.
95+
`uv sync --frozen --group dev` restores the published Git dependency. The local
96+
editable installation is intentionally not a machine-specific lockfile change.
97+
The neutral recolor requires the updated shared package; refresh the Git lock
98+
once those upstream changes are published before deploying this styling.

‎docs/app/reflex.lock/bun.lock‎

Lines changed: 61 additions & 91 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎docs/app/reflex.lock/package.json‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,29 @@
66
"export": "react-router build"
77
},
88
"dependencies": {
9-
"@base-ui/react": "1.7.0",
9+
"@base-ui/react": "1.8.0",
1010
"@fontsource-variable/instrument-sans": "5.2.8",
1111
"@fontsource-variable/jetbrains-mono": "5.2.8",
12-
"@hugeicons/core-free-icons": "4.2.3",
13-
"@hugeicons/react": "1.1.9",
14-
"@radix-ui/react-accordion": "1.2.18",
15-
"@radix-ui/react-dialog": "1.1.21",
16-
"@radix-ui/react-form": "0.1.14",
12+
"@hugeicons/core-free-icons": "4.3.0",
13+
"@hugeicons/react": "1.1.10",
14+
"@radix-ui/react-accordion": "1.2.20",
15+
"@radix-ui/react-dialog": "1.1.23",
16+
"@radix-ui/react-form": "0.1.16",
1717
"@radix-ui/themes": "3.3.0",
18-
"@react-router/node": "8.3.0",
18+
"@react-router/node": "8.3.1",
1919
"clsx": "2.1.1",
2020
"clsx-for-tailwind": "1.0.0",
2121
"es-toolkit": "1.46.1",
2222
"hast-util-to-string": "3.0.1",
23-
"isbot": "5.2.1",
23+
"isbot": "5.2.2",
2424
"lucide-react": "1.26.0",
2525
"react": "19.2.8",
2626
"react-dom": "19.2.8",
2727
"react-helmet": "6.1.0",
28-
"react-router": "8.3.0",
29-
"shiki": "4.3.1",
28+
"react-router": "8.3.1",
29+
"shiki": "4.4.3",
3030
"socket.io-client": "4.8.3",
31-
"sonner": "2.0.7",
31+
"sonner": "2.0.8",
3232
"tailwind-merge": "3.6.0",
3333
"tailwindcss-animated": "2.0.0",
3434
"tailwindcss-scroll-mask": "0.0.3",
@@ -37,17 +37,17 @@
3737
},
3838
"devDependencies": {
3939
"@emotion/react": "11.14.0",
40-
"@react-router/dev": "8.3.0",
41-
"@react-router/fs-routes": "8.3.0",
40+
"@react-router/dev": "8.3.1",
41+
"@react-router/fs-routes": "8.3.1",
4242
"@tailwindcss/postcss": "4.3.0",
4343
"autoprefixer": "10.5.4",
44-
"postcss": "8.5.23",
45-
"postcss-import": "16.1.1",
44+
"postcss": "8.5.26",
45+
"postcss-import": "17.0.0",
4646
"tailwindcss": "4.3.0",
47-
"vite": "8.2.0"
47+
"vite": "8.2.2"
4848
},
4949
"overrides": {
5050
"cookie": "1.1.1",
51-
"postcss": "8.5.23"
51+
"postcss": "8.5.26"
5252
}
5353
}

‎docs/app/rxconfig.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@fontsource-variable/jetbrains-mono@5.2.8",
2020
],
2121
telemetry_enabled=False,
22+
show_built_with_reflex=False,
2223
plugins=[
2324
rx.plugins.TailwindV4Plugin(config={"darkMode": "selector"}),
2425
SharedSiteStylesPlugin(),

‎docs/app/scripts/check_html_routes.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def validate_inline_svg_gallery(page_route: str, module_path: Path) -> None:
111111
f"expected {INLINE_SVG_PREVIEW_COUNT}: {page_route}"
112112
)
113113
raise RuntimeError(msg)
114-
for marker in ("gallery-preview-surface", "aspect-[320/232]", "shadow-large"):
114+
for marker in ("gallery-preview-surface", "aspect-[320/232]", "shadow-small"):
115115
if marker not in source:
116116
msg = f"Inline SVG gallery omits {marker!r}: {page_route}"
117117
raise RuntimeError(msg)

‎docs/app/scripts/check_sitemap.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from xy_docs.constants import PUBLIC_DOCS_URL
99

1010
APP_ROOT = Path(__file__).resolve().parents[1]
11-
SITEMAP_NAMESPACE = {"sitemap": "https://www.sitemaps.org/schemas/sitemap/0.9"}
11+
SITEMAP_NAMESPACE = {"sitemap": "http://www.sitemaps.org/schemas/sitemap/0.9"}
1212

1313

1414
def expected_locations() -> list[str]:

‎docs/app/tests/test_docs_site.py‎

Lines changed: 113 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
from xy_docs.sidebar import (
7676
CHART_FAMILY_SIDEBAR_SECTIONS,
7777
CHART_GALLERY_SIDEBAR_LINK,
78-
INTEGRATION_LINK_ICONS,
7978
PIE_DOCS_ROUTE,
8079
POLAR_DOCS_ROUTE,
8180
POLAR_DOCS_ROUTES,
@@ -94,7 +93,6 @@
9493
import xy
9594
from xy.components import _MARK_APPLIERS, _POLAR_INERT_AXIS_KEYWORDS
9695

97-
SITEMAP_NAMESPACE = {"sitemap": "https://www.sitemaps.org/schemas/sitemap/0.9"}
9896
DOCS_APP_ROOT = Path(__file__).resolve().parent.parent
9997
DOCS_ROOT = DOCS_APP_ROOT.parent
10098
EXPORTED_SITEMAP = DOCS_APP_ROOT / ".web" / "public" / "sitemap.xml"
@@ -179,7 +177,9 @@ def _sitemap_routes(sitemap_path: Path) -> set[str]:
179177
root = ET.parse(sitemap_path).getroot()
180178
return {
181179
route
182-
for location in root.findall("sitemap:url/sitemap:loc", SITEMAP_NAMESPACE)
180+
for location in root.findall(
181+
"{http://www.sitemaps.org/schemas/sitemap/0.9}url/{http://www.sitemaps.org/schemas/sitemap/0.9}loc"
182+
)
183183
if location.text is not None
184184
if (route := _normalize_xy_docs_path(location.text.strip())) is not None
185185
}
@@ -838,8 +838,8 @@ def test_sdf_plot_grid_is_cached_and_uses_reflex_toolbar_tokens() -> None:
838838

839839
rendered = str(xy_sdf_plot_grid(config))
840840
expected_tokens = {
841-
"--chart-text": "var(--secondary-11)",
842-
"--chart-focus": "var(--primary-9)",
841+
"--chart-text": "var(--muted-foreground)",
842+
"--chart-focus": "var(--ring)",
843843
}
844844

845845
for name, value in expected_tokens.items():
@@ -1610,14 +1610,14 @@ def test_chart_gallery_grid_renders_every_type_as_inline_svg(
16101610
assert rendered.count("max-width: 88rem") == 1
16111611
assert rendered.count("2xl:grid-cols-3") == 9
16121612
assert rendered.count("aspect-[320/232]") == 35
1613-
assert rendered.count("shadow-large") == 35
1613+
assert rendered.count("shadow-small") == 35
16141614
assert rendered.count("transition-bg") == 35
1615-
assert "--gallery-preview-surface: #fff" in rendered
1616-
assert "--gallery-preview-fill: #efeaff" in rendered
1617-
assert "--gallery-preview-soft: #dccfff" in rendered
1618-
assert "--gallery-preview-bar: #dccfff" in rendered
1619-
assert "--gallery-preview-stroke: #a790f0" in rendered
1620-
assert "--gallery-preview-strong: #8067d7" in rendered
1615+
assert "--gallery-preview-surface: var(--muted)" in rendered
1616+
assert "--gallery-preview-fill: var(--accent)" in rendered
1617+
assert "--gallery-preview-soft: var(--border)" in rendered
1618+
assert "--gallery-preview-bar: var(--subtle-foreground)" in rendered
1619+
assert "--gallery-preview-stroke: var(--muted-foreground)" in rendered
1620+
assert "--gallery-preview-strong: var(--foreground)" in rendered
16211621
assert "--gallery-preview-muted" not in rendered
16221622
assert "object-contain" not in rendered
16231623
assert "object-center" not in rendered
@@ -2151,7 +2151,7 @@ def test_inline_svg_gallery_validator_requires_every_styled_preview(tmp_path: Pa
21512151
"""Accept only the complete code-native gallery in the production route."""
21522152
module_path = tmp_path / "route.jsx"
21532153
preview = 'viewBox=\\"0 0 320 232\\"'
2154-
surface = "gallery-preview-surface aspect-[320/232] shadow-large"
2154+
surface = "gallery-preview-surface aspect-[320/232] shadow-small"
21552155
expected = check_html_routes.INLINE_SVG_PREVIEW_COUNT
21562156
module_path.write_text(preview * expected + surface, encoding="utf-8")
21572157

@@ -2238,7 +2238,7 @@ def test_xy_sidebar_reuses_memoized_official_navigation_rows() -> None:
22382238

22392239
assert "/core-concepts/axes-and-scales/" in instance
22402240
assert re.findall(
2241-
r'jsx\(RadixThemesText,\{as:"p",className:"m-0 text-sm font-\[525\]"\},"([^"]+)"\)',
2241+
r'jsx\(RadixThemesText,\{as:"p",className:"m-0 text-sm font-\[475\]"\},"([^"]+)"\)',
22422242
rendered,
22432243
) == [
22442244
row_title
@@ -2263,22 +2263,26 @@ def test_xy_sidebar_reuses_memoized_official_navigation_rows() -> None:
22632263
title != "Integrations" and bool(leaves)
22642264
for title, _landing_route, _icon, leaves in grouped_sections
22652265
)
2266-
direct_link_count = len(INTEGRATION_LINK_ICONS) + 1
2266+
direct_link_count = sum(
2267+
len(leaves) if title == "Integrations" else int(not leaves)
2268+
for title, _landing_route, _icon, leaves in grouped_sections
2269+
)
2270+
22672271
assert rendered.count('jsx("details"') == accordion_count
22682272
assert rendered.count('jsx("summary"') == accordion_count
22692273
assert rendered.count("group/details") == accordion_count
22702274
assert rendered.count("guideMarginClass") == expected_leaf_count
22712275
assert (
22722276
rendered.count(
2273-
"absolute left-0 top-1/2 -z-10 h-8 w-full -translate-y-1/2 rounded-lg bg-secondary-3"
2277+
"absolute left-0 top-1/2 -z-10 h-8 w-full -translate-y-1/2 rounded-lg bg-accent"
22742278
)
22752279
== direct_link_count
22762280
)
22772281
assert (
22782282
rendered.count(
22792283
"ml-[2.5rem] flex h-8 w-[calc(100%-2.5rem)] items-center "
2280-
"justify-start text-secondary-11 transition-colors "
2281-
"group-hover:text-primary-10 dark:group-hover:text-primary-9 "
2284+
"justify-start text-muted-foreground transition-colors "
2285+
"group-hover:text-primary-hover dark:group-hover:text-primary "
22822286
"xl:max-w-[14rem]"
22832287
)
22842288
== direct_link_count
@@ -2374,7 +2378,7 @@ def test_xy_sidebar_reuses_memoized_official_navigation_rows() -> None:
23742378
"LucideNotebookTabs",
23752379
"LucideChartNoAxesCombined",
23762380
):
2377-
assert icon in rendered
2381+
assert icon not in rendered
23782382
assert "LucidePlug" not in rendered
23792383
assert rendered.count('"aria-current":((') == direct_link_count
23802384
assert ">XY<" not in rendered
@@ -2400,12 +2404,13 @@ def test_xy_sidebar_opens_only_the_current_chart_family(
24002404
chart_families = (*CHART_FAMILY_SIDEBAR_SECTIONS, POLAR_SIDEBAR_SECTION)
24012405
open_groups = [
24022406
title
2403-
for title, landing_route, icon, leaves in chart_families
2404-
if "open:true" in str(_section_items(title, landing_route, icon, leaves, route)[0])
2407+
for title, landing_route, _icon, leaves in chart_families
2408+
if "open:true" in str(_section_items(title, landing_route, leaves, route)[0])
24052409
]
24062410

24072411
assert open_groups == ([] if expected_open_group is None else [expected_open_group])
2408-
gallery_link = str(_section_items(*CHART_GALLERY_SIDEBAR_LINK, route)[0])
2412+
title, landing_route, _icon, leaves = CHART_GALLERY_SIDEBAR_LINK
2413+
gallery_link = str(_section_items(title, landing_route, leaves, route)[0])
24092414
assert 'jsx("details"' not in gallery_link
24102415
assert ('"aria-current":(true ? "page"' in gallery_link) == (
24112416
route == CHART_GALLERY_SIDEBAR_LINK[1]
@@ -2419,11 +2424,12 @@ def test_xy_navbar_uses_xy_links_github_and_the_official_drawer() -> None:
24192424

24202425
rendered = str(xy_docs_navbar._definition.component)
24212426

2422-
assert 'href:"/"' in rendered
2427+
assert 'to:"/"' in rendered
24232428
assert '"aria-label":"Reflex XY"' in rendered
24242429
assert "M29 16H32V10H39V7H32V4H39V1H29V16" in rendered
2425-
assert 'href:"/docs/xy/"' in rendered
2426-
assert 'href:"/docs/xy/integrations/reflex/"' in rendered
2430+
assert 'to:"/"' in rendered
2431+
assert 'href:"/docs/"' in rendered
2432+
assert 'to:"/integrations/reflex/"' in rendered
24272433
assert "Overview" in rendered
24282434
assert "Reflex Integration" in rendered
24292435
assert 'variant:"ghost"},"Build with AI"' not in rendered
@@ -2434,18 +2440,21 @@ def test_xy_navbar_uses_xy_links_github_and_the_official_drawer() -> None:
24342440
assert "Discord Community" not in rendered
24352441
assert 'variant:"primary"' in rendered
24362442
assert "View XY on GitHub -" not in rendered
2437-
assert rendered.count("View XY on GitHub") == 2
2443+
assert rendered.count("View XY on GitHub") == 4
24382444
assert 'target:"_blank"' in rendered
24392445
assert 'rel:"noopener noreferrer"' in rendered
2440-
assert "Open sidebar" in rendered
2446+
from xy_docs.navbar import _mobile_navigation
2447+
2448+
assert str(_mobile_navigation()) in rendered
24412449
assert "Menu01Icon" in rendered
24422450
assert "Cancel01Icon" in rendered
24432451
assert "Mobile documentation navigation" not in rendered
24442452
assert "<details" not in rendered
24452453
assert "<summary" not in rendered
24462454
assert XY_REPOSITORY_URL in rendered
2447-
assert "XY's initial launch is here" in rendered
2448-
assert "Get started" in rendered
2455+
from reflex_site_shared.views.hosting_banner import hosting_banner
2456+
2457+
assert str(hosting_banner()) in rendered
24492458
assert "Reserve your spot" not in rendered
24502459
assert "https://luma.com/a1ty77bt" not in rendered
24512460
assert "Reflex Agent Toolkit is launching" not in rendered
@@ -2982,3 +2991,78 @@ def test_documented_factories_describe_every_parameter() -> None:
29822991
"Chart",
29832992
parameter.name,
29842993
)
2994+
2995+
2996+
def test_xy_navbar_uses_early_mobile_breakpoint_and_current_section() -> None:
2997+
"""Keep section selection accessible and desktop links hidden below 1280px."""
2998+
from xy_docs.navbar import _menu_item, _navigation_menu
2999+
3000+
menu = _navigation_menu()
3001+
assert "hidden xl:flex" in str(menu.children[0].class_name)
3002+
assert "xl:hidden" in str(menu.children[1].children[-1].class_name)
3003+
link = _menu_item("Overview", "/docs/xy/").children[0]
3004+
assert '"aria-current"' in str(link)
3005+
3006+
3007+
def test_xy_navbar_internal_links_preserve_client_navigation() -> None:
3008+
"""Internal section changes keep the document and announcement mounted."""
3009+
from xy_docs.navbar import _menu_item
3010+
3011+
item = _menu_item("Reflex Integration", "/docs/xy/integrations/reflex/")
3012+
assert type(item.children[0]).__name__ == "ReactRouterLink"
3013+
assert str(item.children[0].to).strip('"') == "/integrations/reflex/"
3014+
assert "inset_0_-1px" not in str(item.class_name)
3015+
3016+
3017+
def test_xy_code_blocks_use_shared_docs_syntax_theme() -> None:
3018+
"""Normal and demo code use the same syntax palette as Reflex docs."""
3019+
from xy_docs.code import code_block
3020+
3021+
rendered = str(code_block("print('hello')", "python"))
3022+
assert "github-light-high-contrast" in rendered
3023+
assert "github-dark-high-contrast" in rendered
3024+
3025+
3026+
@pytest.mark.parametrize(
3027+
("namespace", "accepted"),
3028+
[
3029+
("http://www.sitemaps.org/schemas/sitemap/0.9", True),
3030+
("https://www.sitemaps.org/schemas/sitemap/0.9", False),
3031+
("https://example.com/invalid", False),
3032+
("", False),
3033+
],
3034+
)
3035+
def test_sitemap_validator_requires_standard_namespace(
3036+
tmp_path: Path, namespace: str, accepted: bool
3037+
) -> None:
3038+
"""Only sitemap elements in the standard HTTP namespace supply locations."""
3039+
import runpy
3040+
3041+
sitemap_locations = runpy.run_path(str(DOCS_APP_ROOT / "scripts/check_sitemap.py"))[
3042+
"sitemap_locations"
3043+
]
3044+
3045+
path = tmp_path / "sitemap.xml"
3046+
location = "https://reflex.dev/docs/xy/"
3047+
path.write_text(f'<urlset xmlns="{namespace}"><url><loc>{location}</loc></url></urlset>')
3048+
assert sitemap_locations(path) == ([location] if accepted else [])
3049+
3050+
3051+
@pytest.mark.parametrize(
3052+
"path", ["/", "/index", "/overview/installation/", "/docs/xy/overview/installation/"]
3053+
)
3054+
def test_overview_navbar_marks_child_routes_current(
3055+
monkeypatch: pytest.MonkeyPatch, path: str
3056+
) -> None:
3057+
"""Overview descendants retain the active section indicator."""
3058+
from types import SimpleNamespace
3059+
3060+
import reflex as rx
3061+
from xy_docs.navbar import _menu_item
3062+
3063+
monkeypatch.setattr(
3064+
rx, "State", SimpleNamespace(router=SimpleNamespace(page=SimpleNamespace(path=path)))
3065+
)
3066+
item = _menu_item("Overview", "/docs/xy/")
3067+
assert '"aria-current":(true ? "page" : null)' in str(item.children[0])
3068+
assert "inset_0_-1px" not in str(item.class_name)

0 commit comments

Comments
 (0)