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", ),