diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml
index be95b63576..06000054b9 100644
--- a/.github/workflows/deploy-dev.yml
+++ b/.github/workflows/deploy-dev.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
+ workflow_dispatch:
concurrency:
group: deploy-dev
diff --git a/assets/bayesline_dark_landing.png b/assets/bayesline_dark_landing.png
new file mode 100644
index 0000000000..560874dee4
Binary files /dev/null and b/assets/bayesline_dark_landing.png differ
diff --git a/assets/bayesline_light_landing.png b/assets/bayesline_light_landing.png
new file mode 100644
index 0000000000..3bf9ba7de3
Binary files /dev/null and b/assets/bayesline_light_landing.png differ
diff --git a/docs/ai_builder/quickstart.md b/docs/ai_builder/quickstart.md
new file mode 100644
index 0000000000..2777913804
--- /dev/null
+++ b/docs/ai_builder/quickstart.md
@@ -0,0 +1 @@
+## Some placeholder text ...
\ No newline at end of file
diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py
index a720b542df..21ba703abb 100644
--- a/pcweb/components/docpage/navbar/navbar.py
+++ b/pcweb/components/docpage/navbar/navbar.py
@@ -1,7 +1,9 @@
"""UI and logic for the navbar component."""
import reflex as rx
+from reflex.experimental import ClientStateVar
+from pcweb.pages.customers.views.bento_cards import _card
from pcweb.pages.docs import (
wrapping_react,
styling,
@@ -26,17 +28,26 @@
from pcweb.pages.blog.paths import blog_data
from pcweb.components.docpage.navbar.navmenu.navmenu import nav_menu
-from pcweb.constants import CONTRIBUTING_URL, FORUM_URL, ROADMAP_URL, REFLEX_CLOUD_URL
+from pcweb.constants import (
+ CONTRIBUTING_URL,
+ FORUM_URL,
+ ROADMAP_URL,
+ REFLEX_CLOUD_URL,
+ REFLEX_AI_BUILDER,
+)
+from ..sidebar import SidebarState
+from ...link_button import resources_button
-def resource_item(text: str, url: str, icon: str):
+def resource_item(text: str, url: str, icon: str, index):
return rx.el.li(
rx.link(
rx.box(
rx.icon(icon, size=16, class_name="flex-shrink-0 text-slate-9"),
+ rx.spacer(),
rx.text(
text,
- class_name="font-small text-slate-9 truncate",
+ class_name="font-small text-slate-9 truncate text-start w-[150px]",
),
rx.spacer(),
rx.icon(
@@ -44,33 +55,54 @@ def resource_item(text: str, url: str, icon: str):
size=14,
class_name="flex-shrink-0 text-slate-12",
),
- class_name="flex flex-row flex-nowrap items-center gap-3 hover:bg-slate-3 px-[1.125rem] py-2 rounded-md w-full transition-bg",
+ class_name="flex flex-row flex-nowrap items-center gap-3 hover:bg-slate-3 px-[1.125rem] py-2 rounded-md w-full transition-bg justify-between",
),
class_name="w-full text-slate-9 hover:!text-slate-9",
underline="none",
href=url,
+ on_click=SidebarState.set_sidebar_index(index),
),
class_name="w-full",
)
def link_item(name: str, url: str, active_str: str = ""):
- # If URL doesn't end with a slash, add one
router_path = rx.State.router.page.path
+
url = url.rstrip("/") + "/"
- active = router_path.contains(active_str)
- if active_str == "docs":
+
+ if active_str == "framework":
+ is_home = router_path == "/"
+ is_docs = router_path.contains("docs")
+ not_cloud = ~(router_path.contains("cloud") | router_path.contains("hosting"))
+ not_ai_builder = ~router_path.contains("ai-builder")
+
+ active = rx.cond(
+ is_home, True, rx.cond(is_docs & not_cloud & not_ai_builder, True, False)
+ )
+
+ elif active_str == "builder":
+ active = router_path.contains("ai-builder")
+
+ elif active_str == "hosting" or active_str == "cloud":
+ active = router_path.contains("cloud") | router_path.contains("hosting")
+
+ elif active_str == "pricing":
+ active = router_path.contains("pricing")
+
+ elif active_str == "docs":
active = rx.cond(
- router_path.contains("library"),
- False,
- active,
+ router_path.contains("library"), False, router_path.contains("docs")
)
- if active_str == "":
+ elif active_str:
+ active = router_path.contains(active_str)
+ else:
active = False
common_cn = "transition-color p-[1.406rem_0px] font-small desktop-only items-center justify-center "
active_cn = "shadow-[inset_0_-1px_0_0_var(--c-violet-9)] text-violet-9"
unactive_cn = "shadow-none text-slate-9"
+
return rx.link(
name,
href=url,
@@ -186,64 +218,101 @@ def blog_section() -> rx.Component:
)
-def resources_section() -> rx.Component:
- return nav_menu.content(
- rx.el.ul(
- resource_item("Changelog", changelog.path, "list"),
- resource_item("Debugging Guide", errors.path, "bug"),
- resource_item("FAQ", faq.path, "circle-help"),
- resource_item("Contribute", CONTRIBUTING_URL, "code-xml"),
- resource_item("Roadmap", ROADMAP_URL, "route"),
- resource_item("Forum", FORUM_URL, "github"),
- class_name="items-start gap-1.5 gap-x-1.5 grid grid-cols-2 m-0 p-1.5 w-[492px] min-w-max",
+def link_button(label: str, url: str) -> rx.Component:
+ return rx.link(
+ resources_button(
+ label, size="md", variant="transparent", class_name="justify-start w-full"
),
+ href=url,
+ is_external=True,
+ underline="none",
+ class_name="!w-full",
)
-def components_section() -> rx.Component:
+def grid_card(
+ title: str, description: str, url: str, image: str, image_style: str
+) -> rx.Component:
+ return rx.link(
+ rx.box(
+ rx.text(title, class_name="text-slate-12 text-base font-semibold"),
+ rx.el.button(
+ rx.icon("chevron-right", class_name="text-slate-9 size-4"),
+ class_name="size-6 group-hover:bg-slate-3 transition-bg rounded-md flex items-center justify-center",
+ ),
+ class_name="flex flex-row items-center gap-2 justify-between",
+ ),
+ rx.text(description, class_name="text-slate-9 text-sm font-medium"),
+ rx.image(
+ src=image,
+ class_name=image_style,
+ ),
+ href=url,
+ is_external=True,
+ underline="none",
+ class_name="w-[14.5rem] rounded-md shadow-small bg-white-1 border border-slate-4 flex flex-col gap-3 p-5 relative border-solid !h-[16.5625rem] overflow-hidden group",
+ )
+
+
+def grid_card_unique(title: str, description: str, url: str, component) -> rx.Component:
+ return rx.link(
+ rx.box(
+ rx.text(title, class_name="text-slate-12 text-base font-semibold"),
+ rx.el.button(
+ rx.icon("chevron-right", class_name="text-slate-9 size-4"),
+ class_name="size-6 group-hover:bg-slate-3 transition-bg rounded-md flex items-center justify-center",
+ ),
+ class_name="flex flex-row items-center gap-2 justify-between",
+ ),
+ rx.text(description, class_name="text-slate-9 text-sm font-medium"),
+ component,
+ href=url,
+ is_external=True,
+ underline="none",
+ class_name="w-[14.5rem] rounded-md shadow-small bg-white-1 border border-slate-4 flex flex-col gap-3 p-5 relative border-solid !h-[14.5625rem] overflow-hidden group",
+ )
+
+
+def new_resource_section():
return nav_menu.content(
rx.box(
+ # Links
rx.box(
- rx.el.h3(
- "Core Components",
- class_name="px-[1.125rem] py-3.5 font-smbold text-slate-12 truncate self-stretch",
+ link_button("Changelog", changelog.path),
+ link_button(
+ "Contributing",
+ "https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md",
),
- rx.el.ul(
- resource_item(
- "Component Library", library.path, "layout-panel-left"
- ),
- resource_item("Theming", styling.theming.path, "palette"),
- class_name="flex flex-col items-start gap-1.5 w-full",
+ link_button(
+ "Discussions", "https://github.com/orgs/reflex-dev/discussions"
),
- class_name="flex flex-col items-start gap-1.5 p-1.5 w-[248px]",
+ link_button("FAQ", faq.path),
+ class_name="flex flex-col w-full p-2",
),
- rx.box(
- rx.el.h3(
- "Custom Components",
- class_name="px-[1.125rem] py-3.5 font-smbold text-slate-12 truncate self-stretch",
- ),
- rx.el.ul(
- resource_item(
- "Community Library",
- custom_components.path,
- "blocks",
- ),
- resource_item(
- "Wrapping React",
- wrapping_react.overview.path,
- "atom",
- ),
- resource_item(
- "Publishing Components",
- custom_c.overview.path,
- "git-fork",
- ),
- class_name="flex flex-col items-start gap-1.5 w-full",
+ class_name="flex flex-col w-full max-w-[10.1875rem] border-r border-slate-5",
+ ),
+ # Grid cards
+ rx.box(
+ grid_card(
+ "Blog",
+ "See what's new in the Reflex ecosystem.",
+ f"/blog",
+ "/blog/top_python_web_frameworks.png",
+ "absolute bottom-0 rounded-tl-md",
+ ),
+ grid_card(
+ "Customers",
+ "Meet the teams who chose Reflex.",
+ "/customers",
+ rx.color_mode_cond(
+ "/bayesline_light_landing.png",
+ "/bayesline_dark_landing.png",
),
- class_name="flex flex-col items-start gap-1.5 border-slate-5 bg-slate-1 p-1.5 border-l w-[280px]",
+ "absolute -bottom-7 rounded-tl-md",
),
- class_name="flex flex-row items-start m-0 w-full min-w-max",
+ class_name="grid grid-cols-2 gap-3 p-3 bg-slate-1",
),
+ class_name="flex flex-row shadow-large rounded-xl bg-slate-2 border border-slate-5 w-[41.55rem] font-sans overflow-hidden",
)
@@ -273,74 +342,88 @@ def logo() -> rx.Component:
)
+def doc_section():
+ from pcweb.pages.docs.ai_builder import pages as ai_pages
+ from pcweb.pages.docs.cloud import pages as cloud_pages
+ from pcweb.pages.docs import hosting as hosting_page
+
+ return nav_menu.content(
+ rx.el.ul(
+ # resource_item("AI Builder Docs", ai_pages[0].path, "bot", 0),
+ resource_item(
+ "Framework Docs", getting_started.introduction.path, "frame", 0
+ ),
+ resource_item(
+ "Cloud Docs", hosting_page.deploy_quick_start.path, "server", 0
+ ),
+ class_name="items-start gap-1.5 gap-x-1.5 grid grid-cols-1 m-0 p-1.5 w-[280px] min-w-max",
+ ),
+ )
+
+
def new_component_section() -> rx.Component:
+ from pcweb.pages.docs.ai_builder import pages as ai_pages
+ from pcweb.pages.docs.cloud import pages as cloud_pages
+ from pcweb.pages.docs import hosting as hosting_page
+
return nav_menu.root(
nav_menu.list(
- nav_menu.item(
- logo(),
- ),
- nav_menu.item(
- link_item("Docs", getting_started.introduction.path, "docs"),
- ),
- nav_menu.item(
- link_item("Templates", gallery.path, "templates"),
- ),
- nav_menu.item(
- new_menu_trigger("Blog", blogs.path, "blog"),
- blog_section(),
- ),
- # Case Studies link isn't shown on docs pages
- nav_menu.item(
- new_menu_trigger("Case Studies", "/customers", "customers"),
- display=rx.cond(
- rx.State.router.page.path.contains("docs"),
- "none",
- "block",
+ nav_menu.item(logo()),
+ rx.cond(
+ rx.State.router.page.path.contains("docs")
+ | rx.State.router.page.path.contains("ai-builder")
+ | rx.State.router.page.path.contains("cloud"),
+ rx.el.div(
+ # nav_menu.item(
+ # link_item("AI Builder", ai_pages[0].path, "builder"),
+ # ),
+ nav_menu.item(
+ link_item(
+ "Framework", getting_started.introduction.path, "framework"
+ ),
+ ),
+ nav_menu.item(
+ link_item(
+ "Cloud", hosting_page.deploy_quick_start.path, "hosting"
+ ),
+ ),
+ class_name="desktop-only flex flex-row items-center gap-0 lg:gap-7 m-0 h-full list-none",
),
- ),
- # Resources link is shown on docs pages
- nav_menu.item(
- new_menu_trigger("Resources"),
- resources_section(),
- display=rx.cond(
- rx.State.router.page.path.contains("docs"),
- "block",
- "none",
+ rx.el.div(
+ # nav_menu.item(
+ # link_item("AI Builder", REFLEX_AI_BUILDER, "builder"),
+ # ),
+ nav_menu.item(
+ link_item("Framework", "/", "framework"),
+ ),
+ nav_menu.item(
+ link_item("Cloud", "/hosting", "hosting"),
+ ),
+ class_name="desktop-only flex flex-row items-center gap-0 lg:gap-7 m-0 h-full list-none",
),
),
- # Components link is shown on non docs pages
nav_menu.item(
- new_menu_trigger("Components", library.path, "library"),
- components_section(),
+ new_menu_trigger("Docs"),
+ doc_section(),
display=rx.cond(
- rx.State.router.page.path.contains("docs"),
- "block",
+ rx.State.router.page.path.contains("docs")
+ | rx.State.router.page.path.contains("ai-builder")
+ | rx.State.router.page.path.contains("cloud"),
"none",
+ "block",
),
),
+ nav_menu.item(new_menu_trigger("Resources"), new_resource_section()),
nav_menu.item(
- link_item("Hosting", "/hosting", "hosting"),
- ),
- nav_menu.item(
- link_item("Pricing", "/pricing", "pricing"),
+ new_menu_trigger("Pricing", "/pricing", "pricing"),
),
class_name="desktop-only flex flex-row items-center gap-0 lg:gap-7 m-0 h-full list-none",
),
nav_menu.list(
- nav_menu.item(
- search_bar(),
- ),
- nav_menu.item(
- github(),
- ),
- nav_menu.item(
- discord(),
- class_name="desktop-only",
- ),
- nav_menu.item(
- color(),
- class_name="desktop-only",
- ),
+ nav_menu.item(search_bar()),
+ nav_menu.item(github()),
+ nav_menu.item(discord(), class_name="desktop-only"),
+ nav_menu.item(color(), class_name="desktop-only"),
nav_menu.item(
rx.link(
button(
@@ -353,10 +436,7 @@ def new_component_section() -> rx.Component:
),
class_name="desktop-only",
),
- nav_menu.item(
- navbar_sidebar_button(),
- class_name="mobile-only",
- ),
+ nav_menu.item(navbar_sidebar_button(), class_name="mobile-only"),
class_name="flex flex-row gap-2 m-0 h-full list-none items-center",
),
rx.box(
diff --git a/pcweb/components/docpage/sidebar/sidebar.py b/pcweb/components/docpage/sidebar/sidebar.py
index c7b43a340f..61ce4d9daa 100644
--- a/pcweb/components/docpage/sidebar/sidebar.py
+++ b/pcweb/components/docpage/sidebar/sidebar.py
@@ -4,13 +4,15 @@
import reflex as rx
from pcweb.components.docpage.navbar.state import NavbarState
+from pcweb.pages.docs import cloud_cliref
from .state import SidebarState, SideBarItem, SideBarBase
-from .sidebar_items.learn import learn, frontend, backend, hosting
+from .sidebar_items.learn import learn, frontend, backend, hosting, cli_ref
from .sidebar_items.component_lib import (
component_lib,
graphing_libs,
)
+from .sidebar_items.ai_builder import ai_builder_items
from .sidebar_items.reference import api_reference
from .sidebar_items.recipes import recipes
from pcweb.styles.colors import c_color
@@ -217,6 +219,7 @@ def append_to_items(items, flat_items):
+ component_lib
+ graphing_libs
+ recipes
+ + ai_builder_items
+ api_reference,
flat_items,
)
@@ -342,6 +345,9 @@ def sidebar_comp(
graphing_libs_index: list[int],
api_reference_index: list[int],
recipes_index: list[int],
+ #
+ cli_ref_index: list[int],
+ ai_builder_index: list[int],
tutorials_index: list[int],
width: str = "100%",
):
@@ -355,113 +361,158 @@ def sidebar_comp(
hosting as hosting_page,
)
from pcweb.pages.docs.apiref import pages
+ from pcweb.pages.docs.cloud import pages as cloud_pages
return rx.box(
- rx.el.ul(
- sidebar_category(
- "Learn", getting_started.introduction.path, "graduation-cap", 0
- ),
- sidebar_category("Components", library.path, "layout-panel-left", 1),
- sidebar_category(
- "Deploy", hosting_page.deploy_quick_start.path, "cloud", 2
+ # Handle sidebar categories for docs/cloud first
+ rx.cond(
+ rx.State.router.page.path.startswith("/docs/hosting/"),
+ rx.el.ul(
+ sidebar_category(
+ "Cloud", hosting_page.deploy_quick_start.path, "cloud", 0
+ ),
+ # sidebar_category(
+ # "CLI Reference", cloud_pages[0].path, "book-marked", 1
+ # ),
+ class_name="flex flex-col items-start gap-1 w-full list-none",
),
- sidebar_category("API Reference", pages[0].path, "book-text", 3),
- class_name="flex flex-col items-start gap-1 w-full list-none",
- ),
- rx.match(
- SidebarState.sidebar_index,
- (
- 0,
+ # If the path doesn't start with /docs/cloud, check for general docs
+ rx.cond(
+ rx.State.router.page.path.startswith("/docs/"),
rx.el.ul(
- create_sidebar_section(
- "Onboarding",
- getting_started.introduction.path,
- learn,
- learn_index,
- url,
- ),
- create_sidebar_section(
- "User Interface",
- ui.overview.path,
- filter_out_non_sidebar_items(frontend),
- frontend_index,
- url,
+ sidebar_category(
+ "Learn", getting_started.introduction.path, "graduation-cap", 0
),
- create_sidebar_section(
- "State",
- state.overview.path,
- filter_out_non_sidebar_items(backend),
- backend_index,
- url,
+ sidebar_category(
+ "Components", library.path, "layout-panel-left", 1
),
- create_sidebar_section(
- "Recipes", overview.path, recipes, recipes_index, url
- ),
- class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
+ sidebar_category("API Reference", pages[0].path, "book-text", 2),
+ class_name="flex flex-col items-start gap-1 w-full list-none",
),
),
- (
- 1,
- rx.el.ul(
- create_sidebar_section(
- "Core", library.path, component_lib, component_lib_index, url
- ),
- create_sidebar_section(
- "Graphing",
- library.path,
- graphing_libs,
- graphing_libs_index,
- url,
+ ),
+ # Handle the sidebar content based on docs/cloud or docs
+ rx.cond(
+ rx.State.router.page.path.startswith("/docs/hosting/"),
+ rx.match(
+ SidebarState.sidebar_index,
+ (
+ 0,
+ rx.el.ul(
+ create_sidebar_section(
+ "Cloud",
+ hosting_page.deploy_quick_start.path,
+ hosting,
+ hosting_index,
+ url,
+ ),
+ class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
),
- rx.link(
- rx.box(
- rx.box(
- rx.icon("atom", size=16),
- rx.el.h5(
- "Custom Components",
- class_name="font-smbold text-[0.875rem] text-slate-12 leading-5 tracking-[-0.01313rem] transition-color",
- ),
- class_name="flex flex-row items-center gap-3 text-slate-12",
+ ),
+ # (
+ # 1,
+ # rx.el.ul(
+ # create_sidebar_section(
+ # "CLI Reference",
+ # cloud_pages[0].path,
+ # cli_ref,
+ # cli_ref_index,
+ # url,
+ # ),
+ # class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
+ # ),
+ # ),
+ ),
+ rx.cond(
+ rx.State.router.page.path.startswith("/docs/"),
+ rx.match(
+ SidebarState.sidebar_index,
+ (
+ 0,
+ rx.el.ul(
+ create_sidebar_section(
+ "Onboarding",
+ getting_started.introduction.path,
+ learn,
+ learn_index,
+ url,
),
- rx.text(
- "See what components people have made with Reflex!",
- class_name="font-small text-slate-9",
+ create_sidebar_section(
+ "User Interface",
+ ui.overview.path,
+ filter_out_non_sidebar_items(frontend),
+ frontend_index,
+ url,
+ ),
+ create_sidebar_section(
+ "State",
+ state.overview.path,
+ filter_out_non_sidebar_items(backend),
+ backend_index,
+ url,
+ ),
+ create_sidebar_section(
+ "Recipes", overview.path, recipes, recipes_index, url
),
- class_name="flex flex-col gap-2 border-slate-5 bg-slate-1 hover:bg-slate-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg",
+ class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
),
- underline="none",
- href=custom_components.path,
),
- class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
- ),
- ),
- (
- 2,
- rx.el.ul(
- create_sidebar_section(
- "Hosting",
- hosting_page.deploy_quick_start.path,
- hosting,
- hosting_index,
- url,
+ (
+ 1,
+ rx.el.ul(
+ create_sidebar_section(
+ "Core",
+ library.path,
+ component_lib,
+ component_lib_index,
+ url,
+ ),
+ create_sidebar_section(
+ "Graphing",
+ library.path,
+ graphing_libs,
+ graphing_libs_index,
+ url,
+ ),
+ rx.link(
+ rx.box(
+ rx.box(
+ rx.icon("atom", size=16),
+ rx.el.h5(
+ "Custom Components",
+ class_name="font-smbold text-[0.875rem] text-slate-12 leading-5 tracking-[-0.01313rem] transition-color",
+ ),
+ class_name="flex flex-row items-center gap-3 text-slate-12",
+ ),
+ rx.text(
+ "See what components people have made with Reflex!",
+ class_name="font-small text-slate-9",
+ ),
+ class_name="flex flex-col gap-2 border-slate-5 bg-slate-1 hover:bg-slate-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg",
+ ),
+ underline="none",
+ href=custom_components.path,
+ ),
+ class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
+ ),
),
- class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
- ),
- ),
- (
- 3,
- rx.el.ul(
- create_sidebar_section(
- "Reference",
- pages[0].path,
- api_reference,
- api_reference_index,
- url,
+ (
+ 2,
+ rx.el.ul(
+ create_sidebar_section(
+ "Reference",
+ pages[0].path,
+ api_reference,
+ api_reference_index,
+ url,
+ ),
+ class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
+ ),
),
- class_name="flex flex-col items-start gap-6 p-[0px_1rem_0px_0.5rem] w-full list-none list-style-none",
),
),
),
+ # Handle general docs sections
style={
"&::-webkit-scrollbar-thumb": {
"background_color": "transparent",
@@ -485,6 +536,9 @@ def sidebar(url=None, width: str = "100%") -> rx.Component:
api_reference_index = calculate_index(api_reference, url)
recipes_index = calculate_index(recipes, url)
+ cli_ref_index = calculate_index(cli_ref, url)
+ ai_builder_index = calculate_index(ai_builder_items, url)
+
return rx.box(
sidebar_comp(
url=url,
@@ -496,6 +550,9 @@ def sidebar(url=None, width: str = "100%") -> rx.Component:
graphing_libs_index=graphing_libs_index,
api_reference_index=api_reference_index,
recipes_index=recipes_index,
+ ai_builder_index=ai_builder_index,
+ cli_ref_index=cli_ref_index,
+ #
width=width,
),
class_name="flex justify-end w-full h-full",
diff --git a/pcweb/components/docpage/sidebar/sidebar_items/ai_builder.py b/pcweb/components/docpage/sidebar/sidebar_items/ai_builder.py
new file mode 100644
index 0000000000..c46c51e151
--- /dev/null
+++ b/pcweb/components/docpage/sidebar/sidebar_items/ai_builder.py
@@ -0,0 +1,12 @@
+from .item import create_item
+
+
+def get_sidebar_items_ai_builder():
+ from pcweb.pages.docs.ai_builder import pages as ai_builder_pages
+
+ items = [create_item("AI Builder", children=ai_builder_pages)]
+
+ return items
+
+
+ai_builder_items = get_sidebar_items_ai_builder()
diff --git a/pcweb/components/docpage/sidebar/sidebar_items/cloud.py b/pcweb/components/docpage/sidebar/sidebar_items/cloud.py
new file mode 100644
index 0000000000..77aec01e1e
--- /dev/null
+++ b/pcweb/components/docpage/sidebar/sidebar_items/cloud.py
@@ -0,0 +1,14 @@
+from .item import create_item
+
+
+def get_sidebar_items_cli_ref():
+ from pcweb.pages.docs.cloud_cliref import pages as cloud_cli_pages
+
+ items = [
+ create_item("CLI Reference", children=cloud_cli_pages),
+ ]
+
+ return items
+
+
+cli_ref = get_sidebar_items_cli_ref()
diff --git a/pcweb/components/docpage/sidebar/sidebar_items/learn.py b/pcweb/components/docpage/sidebar/sidebar_items/learn.py
index e61f2d67f6..d9e52b9cbc 100644
--- a/pcweb/components/docpage/sidebar/sidebar_items/learn.py
+++ b/pcweb/components/docpage/sidebar/sidebar_items/learn.py
@@ -240,7 +240,17 @@ def get_sidebar_items_hosting():
return items
+def get_sidebar_items_hosting_cli_ref():
+ from pcweb.pages.docs.cloud_cliref import pages as cloud_pages
+
+ items = [
+ create_item("CLI Reference", children=cloud_cliref.pages),
+ ]
+ return items
+
+
learn = get_sidebar_items_learn()
frontend = get_sidebar_items_frontend()
backend = get_sidebar_items_backend()
hosting = get_sidebar_items_hosting()
+cli_ref = get_sidebar_items_hosting_cli_ref()
diff --git a/pcweb/components/docpage/sidebar/state.py b/pcweb/components/docpage/sidebar/state.py
index 4a888f7b4f..d9402b4583 100644
--- a/pcweb/components/docpage/sidebar/state.py
+++ b/pcweb/components/docpage/sidebar/state.py
@@ -49,11 +49,11 @@ def sidebar_index(self) -> int:
if "library" in route:
return 1
elif "hosting" in route:
- return 2
+ return 0
elif "api-reference" in route:
return 3
else:
return 0
if "hosting" in route:
- return 2
+ return 0
return self._sidebar_index
diff --git a/pcweb/components/link_button.py b/pcweb/components/link_button.py
new file mode 100644
index 0000000000..96f3d79cbb
--- /dev/null
+++ b/pcweb/components/link_button.py
@@ -0,0 +1,123 @@
+from typing import Dict, Literal
+
+import reflex as rx
+
+"""Tailwind CSS class merging utility."""
+
+from reflex import ImportVar
+from reflex.vars import FunctionVar, Var
+from reflex.vars.base import VarData
+
+
+def cn(class_1: Var | str, class_2: Var | str = "") -> Var:
+ """Merge Tailwind CSS classes.
+
+ Args:
+ class_1: First class string or Var
+ class_2: Second class string or Var (optional)
+
+ Returns:
+ Var: A Var with merged classes
+
+ Examples:
+ >>> cn("bg-red-500", rx.cond(State.is_active, "bg-blue-500", "bg-red-500"))
+ >>> cn("bg-red-500", "text-white bg-blue-500")
+ >>> cn("base-class")
+
+ """
+ return (
+ Var(
+ "cn",
+ _var_data=VarData(imports={"clsx-for-tailwind": ImportVar(tag="cn")}),
+ )
+ .to(FunctionVar)
+ .call(class_1, class_2)
+ )
+
+
+LiteralButtonVariant = Literal[
+ "primary", "secondary", "transparent", "destructive", "outline"
+]
+LiteralButtonSize = Literal[
+ "xs", "sm", "md", "lg", "icon-xs", "icon-sm", "icon-md", "icon-lg"
+]
+
+DEFAULT_CLASS_NAME = "text-sm cursor-pointer inline-flex items-center justify-center relative transition-bg shrink-0 font-sans disabled:cursor-not-allowed disabled:border disabled:border-slate-5 disabled:!bg-slate-3 disabled:!text-slate-8 transition-bg outline-none peer-placeholder-shown:!bg-slate-3 peer-placeholder-shown:!bg-none peer-placeholder-shown:cursor-not-allowed peer-placeholder-shown:border peer-placeholder-shown:border-slate-5 peer-placeholder-shown:!text-slate-8 text-nowrap"
+
+
+def get_variant_bg_cn(variant: str) -> str:
+ """Get the background color class name for a button variant.
+
+ Args:
+ variant (str): The variant of the button.
+
+ Returns:
+ str: The background color class name.
+
+ """
+ return f"enabled:bg-gradient-to-b from-[--{variant}-9] to-[--{variant}-10] dark:to-[--{variant}-9] hover:to-[--{variant}-9] dark:hover:to-[--{variant}-10] disabled:hover:bg-[--{variant}-9]"
+
+
+BUTTON_STYLES: Dict[str, Dict[str, str]] = {
+ "size": {
+ "xs": "px-1.5 h-7 rounded-md gap-1.5",
+ "sm": "px-2 h-8 rounded-lg gap-2",
+ "md": "px-2.5 h-9 rounded-[10px] gap-2.5",
+ "lg": "px-3 h-10 rounded-xl gap-3",
+ "icon-xs": "size-7 rounded-md",
+ "icon-sm": "size-8 rounded-lg",
+ "icon-md": "size-9 rounded-[10px]",
+ "icon-lg": "size-10 rounded-md",
+ },
+ "variant": {
+ "primary": f"{get_variant_bg_cn('violet')} text-[#FCFCFD] font-semibold",
+ "secondary": "bg-slate-4 hover:bg-slate-5 text-slate-11 font-semibold",
+ "transparent": "bg-transparent hover:bg-slate-3 text-slate-9 font-medium",
+ "destructive": f"{get_variant_bg_cn('red')} text-[#FCFCFD] font-semibold",
+ "outline": "bg-slate-1 hover:bg-slate-3 text-slate-9 font-medium border border-slate-5",
+ },
+}
+
+
+def resources_button(
+ *children: rx.Component | str | rx.Var,
+ variant: LiteralButtonVariant = "primary",
+ size: LiteralButtonSize = "sm",
+ class_name: str | rx.Var[str] = "",
+ **props,
+) -> rx.Component:
+ """Create a button component.
+
+ Args:
+ variant (LiteralButtonVariant, optional): The button variant. Defaults to "primary".
+ size (LiteralButtonSize, optional): The button size. Defaults to "sm".
+ class_name (str, optional): Additional CSS classes to apply to the button. Defaults to "".
+ **props: Additional props to pass to the button element.
+
+ Returns:
+ rx.Component: A button component with the specified properties.
+
+ """
+ # Validate size and variant
+ if size not in BUTTON_STYLES["size"]:
+ raise ValueError(
+ f"Invalid size: {size}. Must be one of {list(BUTTON_STYLES['size'].keys())}"
+ )
+ if variant not in BUTTON_STYLES["variant"]:
+ raise ValueError(
+ f"Invalid variant: {variant}. Must be one of {list(BUTTON_STYLES['variant'].keys())}"
+ )
+ variant_class = BUTTON_STYLES["variant"][variant]
+ # variant_class = variant_class() if callable(variant_class) else variant_class
+
+ internal_classes = [
+ DEFAULT_CLASS_NAME,
+ BUTTON_STYLES["size"][size],
+ variant_class,
+ ]
+
+ return rx.el.button(
+ *children,
+ class_name=cn(internal_classes, class_name),
+ **props,
+ )
diff --git a/pcweb/constants.py b/pcweb/constants.py
index 20d6f2f82a..16b3572185 100644
--- a/pcweb/constants.py
+++ b/pcweb/constants.py
@@ -18,6 +18,7 @@
REFLEX_URL = "https://reflex.dev/"
REFLEX_DOCS_URL = "https://reflex.dev/docs/getting-started/introduction/"
REFLEX_CLOUD_URL = "https://cloud.reflex.dev/"
+REFLEX_AI_BUILDER = "https://reflex.build/"
PYNECONE_URL = "https://pynecone.io"
PIP_URL = "https://pypi.org/project/reflex"
GITHUB_URL = "https://github.com/reflex-dev/reflex"
diff --git a/pcweb/pages/customers/views/bento_cards.py b/pcweb/pages/customers/views/bento_cards.py
index 2f4bdc7772..6bc51097f9 100644
--- a/pcweb/pages/customers/views/bento_cards.py
+++ b/pcweb/pages/customers/views/bento_cards.py
@@ -107,3 +107,90 @@ def bento_cards() -> rx.Component:
),
class_name="grid grid-cols-1 lg:grid-cols-2 gap-4 mx-auto w-full max-w-[69.25rem]",
)
+
+
+def _card(company: str, is_company: bool = True, **kwarg) -> rx.Component:
+ return rx.link(
+ # Center company logo
+ rx.cond(
+ is_company,
+ rx.image(
+ rx.color_mode_cond(
+ light=f"/customers/light/{company}/{company}_middle.svg",
+ dark=f"/customers/dark/{company}/{company}_middle.svg",
+ ),
+ alt=f"{company} small logo",
+ loading="lazy",
+ class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[2] max-h-[88px]",
+ ),
+ rx.image(
+ **kwarg,
+ alt=f"{company} small logo",
+ loading="lazy",
+ class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[2] max-h-[88px]",
+ ),
+ ),
+ # Wave pattern
+ rx.html(
+ """""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[0] pointer-events-none",
+ ),
+ # Glowing
+ rx.html(
+ """""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[1] pointer-events-none w-[15rem] h-[15rem]",
+ ),
+ )
+
+
+def bento_cards() -> rx.Component:
+ return rx.el.section(
+ # # Dell
+ # card(
+ # company="dell",
+ # text="Dell is the standard for frontend hosting. Reflex keeps them shipping.",
+ # ),
+ # # LlamaIndex
+ # card(
+ # company="llamaindex",
+ # text="To build the fastest-growing corporate card, LlamaIndex chose the fastest tool.",
+ # ),
+ # # Autodesk
+ # card(
+ # company="autodesk",
+ # text="Autodesk switched their 1,000 person team to Reflex to move faster.",
+ # ),
+ # Bayesline
+ card(
+ company="bayesline",
+ text="Why Basyesline Chose Reflex over Plotly Dash",
+ ),
+ # Ansa
+ card(
+ company="ansa",
+ text="Why Ansa chose Reflex over a no-code/low-code framework for their workflow automations",
+ ),
+ # Seller X
+ card(
+ company="sellerx",
+ text="Why SellerX chose Reflex over Streamlit for their data processing pipeline",
+ class_name=" col-span-2",
+ ),
+ class_name="grid grid-cols-1 lg:grid-cols-2 gap-4 mx-auto w-full max-w-[69.25rem]",
+ )
diff --git a/pcweb/pages/docs/__init__.py b/pcweb/pages/docs/__init__.py
index 8900d6984d..0965c12016 100644
--- a/pcweb/pages/docs/__init__.py
+++ b/pcweb/pages/docs/__init__.py
@@ -23,6 +23,8 @@
from .apiref import pages as apiref_pages
from .cloud_cliref import pages as cloud_cliref_pages
from pcweb.pages.library_previews import components_previews_pages
+from .ai_builder import pages as ai_builder_pages
+from .cloud import pages as cloud_pages
def should_skip_compile(doc: flexdown.Document):
@@ -189,16 +191,25 @@ def get_component(doc: str, title: str):
]
+ apiref_pages
+ cloud_cliref_pages
+ + ai_builder_pages
+ + cloud_pages
)
+for cloud_page in cloud_pages:
+ title = rx.utils.format.to_snake_case(cloud_page.title)
+ build_nested_namespace(docs_ns, ["cloud"], title, cloud_page)
+
+for ai_page in ai_builder_pages:
+ title = rx.utils.format.to_snake_case(ai_page.title)
+ build_nested_namespace(docs_ns, ["ai_builder"], title, ai_page)
for api_route in apiref_pages:
title = rx.utils.format.to_snake_case(api_route.title)
build_nested_namespace(docs_ns, ["api_reference"], title, api_route)
-for api_route in cloud_cliref_pages:
- title = rx.utils.format.to_snake_case(api_route.title)
- build_nested_namespace(docs_ns, ["api_reference"], title, api_route)
+for ref in cloud_cliref_pages:
+ title = rx.utils.format.to_snake_case(ref.title)
+ build_nested_namespace(docs_ns, ["cloud"], title, ref)
for doc in sorted(flexdown_docs):
path = doc.split("/")[1:-1]
@@ -206,11 +217,18 @@ def get_component(doc: str, title: str):
title = rx.utils.format.to_snake_case(os.path.basename(doc).replace(".md", ""))
title2 = to_title_case(title)
route = rx.utils.format.to_kebab_case(f"/{doc.replace('.md', '/')}")
+
comp = get_component(doc, title)
+ # # Check if the path starts with '/docs/cloud/', and if so, replace 'docs' with an empty string
+ # if route.startswith("/docs/cloud/"):
+ # route = route.replace("/docs", "")
+
if path[0] == "library" and isinstance(library, Route):
locals()["library_"] = library
+ # print(route)
+
# Add the component to the nested namespaces.
build_nested_namespace(
docs_ns, path, title, Route(path=route, title=title2, component=lambda: "")
@@ -230,4 +248,6 @@ def get_component(doc: str, title: str):
recipes_list[category].append(doc)
for name, ns in docs_ns.__dict__.items():
+ # if name == "cloud":
+ # print(name, ns)
locals()[name] = ns
diff --git a/pcweb/pages/docs/ai_builder.py b/pcweb/pages/docs/ai_builder.py
new file mode 100644
index 0000000000..c05380da6f
--- /dev/null
+++ b/pcweb/pages/docs/ai_builder.py
@@ -0,0 +1,14 @@
+from pcweb.templates.docpage import docpage
+
+quickstart_page = docpage("/ai-builder/quickstart/", "AI Builder Quickstart")(
+ lambda: "Quickstart content from markdown"
+)
+quickstart_page.title = "Quickstart"
+
+integration_page = docpage("/ai-builder/integration/", "AI Builder Integration")(
+ lambda: "Integration content from markdown"
+)
+integration_page.title = "Integration"
+
+
+pages = [quickstart_page, integration_page]
diff --git a/pcweb/pages/docs/cloud.py b/pcweb/pages/docs/cloud.py
new file mode 100644
index 0000000000..1147a20913
--- /dev/null
+++ b/pcweb/pages/docs/cloud.py
@@ -0,0 +1,9 @@
+from pcweb.templates.docpage import docpage
+
+cloud_overview = docpage("overview/", "Cloud Overview")(
+ lambda: "Cloud overview content from markdown"
+)
+cloud_overview.title = "Overview"
+
+
+pages = [cloud_overview]
diff --git a/pcweb/pages/docs/library.py b/pcweb/pages/docs/library.py
index 684e0f8fcb..5a0fd35035 100644
--- a/pcweb/pages/docs/library.py
+++ b/pcweb/pages/docs/library.py
@@ -78,7 +78,8 @@ def generate_gallery(
@docpage(
- right_sidebar=False,
+ right_sidebar=True,
+ pseudo_right_bar=True,
)
def library():
return rx.box(
diff --git a/pcweb/templates/docpage/docpage.py b/pcweb/templates/docpage/docpage.py
index bcf0301837..46ef6a1609 100644
--- a/pcweb/templates/docpage/docpage.py
+++ b/pcweb/templates/docpage/docpage.py
@@ -379,6 +379,7 @@ def docpage(
t: str | None = None,
right_sidebar: bool = True,
page_title: str | None = None,
+ pseudo_right_bar: bool = False,
) -> rx.Component:
"""A template that most pages on the reflex.dev site should use.
@@ -529,28 +530,17 @@ def wrapper(*args, **kwargs) -> rx.Component:
else " lg:max-w-[60%] 2xl:max-w-[100%]"
),
),
- rx.el.nav(
- rx.box(
- rx.el.h5(
- "On this page",
- class_name="font-smbold text-[0.875rem] text-slate-12 hover:text-violet-9 leading-5 tracking-[-0.01313rem] transition-color",
- ),
- rx.el.ul(
- *[
- (
- rx.el.li(
- rx.link(
- text,
- class_name="font-small text-slate-9 hover:!text-slate-11 whitespace-normal transition-color",
- underline="none",
- href=path
- + "#"
- + text.lower().replace(" ", "-"),
- )
- )
- if level == 1
- else (
- rx.list_item(
+ (
+ rx.el.nav(
+ rx.box(
+ rx.el.h5(
+ "On this page",
+ class_name="font-smbold text-[0.875rem] text-slate-12 hover:text-violet-9 leading-5 tracking-[-0.01313rem] transition-color",
+ ),
+ rx.el.ul(
+ *[
+ (
+ rx.el.li(
rx.link(
text,
class_name="font-small text-slate-9 hover:!text-slate-11 whitespace-normal transition-color",
@@ -560,35 +550,56 @@ def wrapper(*args, **kwargs) -> rx.Component:
+ text.lower().replace(" ", "-"),
)
)
- if level == 2
- else rx.el.li(
- rx.link(
- text,
- underline="none",
- class_name="pl-6 font-small text-slate-9 hover:!text-slate-11 transition-color",
- href=path
- + "#"
- + text.lower().replace(" ", "-"),
+ if level == 1
+ else (
+ rx.list_item(
+ rx.link(
+ text,
+ class_name="font-small text-slate-9 hover:!text-slate-11 whitespace-normal transition-color",
+ underline="none",
+ href=path
+ + "#"
+ + text.lower().replace(
+ " ", "-"
+ ),
+ )
+ )
+ if level == 2
+ else rx.el.li(
+ rx.link(
+ text,
+ underline="none",
+ class_name="pl-6 font-small text-slate-9 hover:!text-slate-11 transition-color",
+ href=path
+ + "#"
+ + text.lower().replace(
+ " ", "-"
+ ),
+ )
)
)
)
- )
- for level, text in toc
- ],
- class_name="flex flex-col gap-4 list-none",
+ for level, text in toc
+ ],
+ class_name="flex flex-col gap-4 list-none",
+ ),
+ class_name="fixed flex flex-col justify-start gap-4 p-[0.875rem_0.5rem_0px_0.5rem] max-h-[80vh] overflow-y-scroll",
+ style={"width": "inherit"},
+ ),
+ class_name="shrink-0 w-[16%]"
+ + rx.cond(
+ HostingBannerState.show_banner,
+ " mt-[146px]",
+ " mt-[90px]",
+ )
+ + (
+ " hidden xl:flex xl:flex-col"
+ if right_sidebar
+ else " hidden"
),
- class_name="fixed flex flex-col justify-start gap-4 p-[0.875rem_0.5rem_0px_0.5rem] max-h-[80vh] overflow-y-scroll",
- style={
- "width":"inherit"
- }
- ),
- class_name="shrink-0 w-[16%]"
- + rx.cond(
- HostingBannerState.show_banner,
- " mt-[146px]",
- " mt-[90px]",
)
- + (" hidden xl:flex xl:flex-col" if right_sidebar else " hidden"),
+ if not pseudo_right_bar
+ else rx.spacer()
),
class_name="justify-center flex flex-row mx-auto mt-0 max-w-[94.5em] h-full min-h-screen w-full",
),