From e545672af150f7dd0e8ecdc79a0161983014b213 Mon Sep 17 00:00:00 2001 From: carlosabadia Date: Tue, 24 Feb 2026 16:47:53 +0100 Subject: [PATCH 1/4] add search to navbar --- pcweb/components/docpage/navbar/inkeep.py | 299 +++++++++++----------- pcweb/components/docpage/navbar/search.py | 4 +- pcweb/views/marketing_navbar.py | 15 +- 3 files changed, 165 insertions(+), 153 deletions(-) diff --git a/pcweb/components/docpage/navbar/inkeep.py b/pcweb/components/docpage/navbar/inkeep.py index 102baf5fb..727d80654 100644 --- a/pcweb/components/docpage/navbar/inkeep.py +++ b/pcweb/components/docpage/navbar/inkeep.py @@ -11,6 +11,8 @@ class InkeepSearchBar(rx.NoSSRComponent): class Search(rx.el.Div): + custom_style: rx.Var[str] = rx.Var.create("") + def add_imports(self): """Add the imports for the component.""" return { @@ -18,130 +20,126 @@ def add_imports(self): "$/utils/context": {ImportVar(tag="ColorModeContext")}, } - def add_hooks(self): + def add_hooks(self) -> list[str | rx.Var]: """Add the hooks for the component.""" return [ "const { resolvedColorMode } = useContext(ColorModeContext)", - """ -const escalationParams = { + rx.Var( + f"""const escalationParams = {{ type: "object", - properties: { - explanation: { + properties: {{ + explanation: {{ type: "string", description: "A brief few word justification of why a specific confidence level was chosen.", - }, - answerConfidence: { + }}, + answerConfidence: {{ anyOf: [ - { + {{ type: "string", const: "very_confident", - description: `\n The AI Assistant provided a complete and direct answer to all parts of the User Question.\n The answer fully resolved the issue without requiring any further action from the User.\n Every part of the answer was cited from the information sources.\n The assistant did not ask for more information or provide options requiring User action.\n This is the highest Answer Confidence level and should be used sparingly.\n `, - }, - { + description: `\\n The AI Assistant provided a complete and direct answer to all parts of the User Question.\\n The answer fully resolved the issue without requiring any further action from the User.\\n Every part of the answer was cited from the information sources.\\n The assistant did not ask for more information or provide options requiring User action.\\n This is the highest Answer Confidence level and should be used sparingly.\\n `, + }}, + {{ type: "string", const: "somewhat_confident", - description: `\n The AI Assistant provided a complete and direct answer to the User Question, but the answer contained minor caveats or uncertainties. \n \n Examples:\n • The AI Assistant asked follow-up questions to the User\n • The AI Assistant requested additional information from the User\n • The AI Assistant suggested uncertainty in the answer\n • The AI Assistant answered the question but mentioned potential exceptions\n `, - }, - { + description: `\\n The AI Assistant provided a complete and direct answer to the User Question, but the answer contained minor caveats or uncertainties. \\n \\n Examples:\\n • The AI Assistant asked follow-up questions to the User\\n • The AI Assistant requested additional information from the User\\n • The AI Assistant suggested uncertainty in the answer\\n • The AI Assistant answered the question but mentioned potential exceptions\\n `, + }}, + {{ type: "string", const: "not_confident", - description: `\n The AI Assistant tried to answer the User Question but did not fully resolve it.\n The assistant provided options requiring further action from the User, asked for more information, showed uncertainty,\n suggested the user contact support or provided contact information, or provided an indirect or incomplete answer.\n This is the most common Answer Confidence level.\n \n Examples:\n • The AI Assistant provided a general answer not directly related to the User Question\n • The AI Assistant said to reach out to support or provided an email address or contact information\n • The AI Assistant provided options that require further action from the User to resolve the issue\n `, - }, - { + description: `\\n The AI Assistant tried to answer the User Question but did not fully resolve it.\\n The assistant provided options requiring further action from the User, asked for more information, showed uncertainty,\\n suggested the user contact support or provided contact information, or provided an indirect or incomplete answer.\\n This is the most common Answer Confidence level.\\n \\n Examples:\\n • The AI Assistant provided a general answer not directly related to the User Question\\n • The AI Assistant said to reach out to support or provided an email address or contact information\\n • The AI Assistant provided options that require further action from the User to resolve the issue\\n `, + }}, + {{ type: "string", const: "no_sources", - description: `\n The AI Assistant did not use or cite any sources from the information sources to answer the User Question.\n `, - }, - { + description: `\\n The AI Assistant did not use or cite any sources from the information sources to answer the User Question.\\n `, + }}, + {{ type: "string", const: "other", - description: `\n The User Question is unclear or unrelated to the subject matter.\n `, - }, + description: `\\n The User Question is unclear or unrelated to the subject matter.\\n `, + }}, ], description: "A measure of how confidently the AI Assistant completely and directly answered the User Question.", - }, - }, + }}, + }}, required: ["explanation", "answerConfidence"], additionalProperties: false, -}; -const searchBarProps = { - baseSettings: { +}}; +const searchBarProps = {{ + baseSettings: {{ apiKey: '5805add2b45961017ac79c9d388aa34f5db49eb652e228e0', - customIcons: {search: {custom: "/icons/search.svg"}}, + customIcons: {{search: {{custom: "/icons/search.svg"}}}}, organizationDisplayName: 'Reflex', primaryBrandColor: '#6E56CF', - transformSource: (source) => { - const urlPatterns = { + transformSource: (source) => {{ + const urlPatterns = {{ blog: 'reflex.dev/blog', library: 'reflex.dev/docs/library', apiRef: 'reflex.dev/docs/api-reference', docs: 'reflex.dev/docs', - } + }} - function matchUrl(pattern) { + function matchUrl(pattern) {{ return source.url.includes(pattern) - } + }} - function getBreadcrumbs() { - if (matchUrl(urlPatterns.blog)) { + function getBreadcrumbs() {{ + if (matchUrl(urlPatterns.blog)) {{ return ['Blogs', ...source.breadcrumbs.slice(1)] - } - if (matchUrl(urlPatterns.library)) { + }} + if (matchUrl(urlPatterns.library)) {{ return ['Components', ...source.breadcrumbs.slice(1)] - } - if (matchUrl(urlPatterns.apiRef)) { + }} + if (matchUrl(urlPatterns.apiRef)) {{ return ['API Reference'] - } - if (matchUrl(urlPatterns.docs)) { + }} + if (matchUrl(urlPatterns.docs)) {{ return ['Docs', ...source.breadcrumbs.slice(1)] - } + }} return source.breadcrumbs - } + }} const breadcrumbs = getBreadcrumbs() - function getTabs() { - const tabMap = { + function getTabs() {{ + const tabMap = {{ [urlPatterns.blog]: 'Blogs', [urlPatterns.library]: 'Components', [urlPatterns.apiRef]: 'API Reference', [urlPatterns.docs]: 'Docs', - } + }} - for (const [pattern, tab] of Object.entries(tabMap)) { - if (matchUrl(pattern)) { + for (const [pattern, tab] of Object.entries(tabMap)) {{ + if (matchUrl(pattern)) {{ return [ ...(source.tabs ?? []), - // If the first breadcrumb is the same as the tab, use the remaining breadcrumbs - // This is only if you don't want breadcrumbs to include current tab, e.g. just "Blog Post" instead of "Blogs > Blog Post" in the Blogs tab - // The tab type accepts a string or an object with a breadcrumbs property i.e. breadcrumbs shown for this source in that tab [ tab, - { breadcrumbs: breadcrumbs[0] === tab ? breadcrumbs.slice(1) : breadcrumbs }, + {{ breadcrumbs: breadcrumbs[0] === tab ? breadcrumbs.slice(1) : breadcrumbs }}, ], ] - } - } + }} + }} return source.tabs - } + }} - return { + return {{ ...source, tabs: getTabs(), breadcrumbs, - } - }, - colorMode: { - forcedColorMode: resolvedColorMode, // options: 'light' or dark' - }, - theme: { - // Add inline styles using the recommended approach from the docs + }} + }}, + colorMode: {{ + forcedColorMode: resolvedColorMode, + }}, + theme: {{ styles: [ - { + {{ key: "custom-theme", type: "style", value: ` - [data-theme='light'] .ikp-search-bar__button { + [data-theme='light'] .ikp-search-bar__button {{ color: var(--m-slate-7); padding: 0.375rem 0.5rem; border-radius: 0.5rem; @@ -168,8 +166,8 @@ def add_hooks(self): line-height: 1.5rem; border: none; transition: none; - } - [data-theme='dark'] .ikp-search-bar__button { + }} + [data-theme='dark'] .ikp-search-bar__button {{ color: var(--m-slate-6); transition: none; height: 2rem !important; @@ -195,21 +193,21 @@ def add_hooks(self): font-style: normal; line-height: 1.5rem; border: none; - } - [data-theme='light'] .ikp-search-bar__button:hover { + }} + [data-theme='light'] .ikp-search-bar__button:hover {{ background: var(--m-slate-2); - } - [data-theme='dark'] .ikp-search-bar__button:hover { + }} + [data-theme='dark'] .ikp-search-bar__button:hover {{ background: var(--m-slate-10); - } - @media (min-width: 1024px) { - .ikp-search-bar__button { + }} + @media (min-width: 1024px) {{ + .ikp-search-bar__button {{ width: 10rem; - } - } + }} + }} [data-theme='light'] .ikp-search-bar__container, - [data-theme='dark'] .ikp-search-bar__container { + [data-theme='dark'] .ikp-search-bar__container {{ display: flex; justify-content: center; align-items: center; @@ -217,32 +215,32 @@ def add_hooks(self): max-height: 2rem; width: 10rem; max-width: 10rem; - } + }} - [data-theme='light'] .ikp-search-bar__button:hover { + [data-theme='light'] .ikp-search-bar__button:hover {{ background-color: var(--m-slate-2); - } - [data-theme='dark'] .ikp-search-bar__button:hover { + }} + [data-theme='dark'] .ikp-search-bar__button:hover {{ background-color: var(--m-slate-10); - } + }} - [data-theme='dark'] .ikp-modal__overlay { + [data-theme='dark'] .ikp-modal__overlay {{ background: rgba(18, 17, 19, 0.50); backdrop-filter: blur(20px); - } + }} - [data-part="modal__content"] { + [data-part="modal__content"] {{ font-family: "Instrument Sans", sans-serif; - } + }} - @media (max-width: 80em) { + @media (max-width: 80em) {{ [data-theme='light'] .ikp-search-bar__container, - [data-theme='dark'] .ikp-search-bar__container { + [data-theme='dark'] .ikp-search-bar__container {{ width: auto !important; - } + }} [data-theme='light'] .ikp-search-bar__button, - [data-theme='dark'] .ikp-search-bar__button { + [data-theme='dark'] .ikp-search-bar__button {{ padding: 2px 12px; display: block; height: 32px; @@ -250,37 +248,37 @@ def add_hooks(self): width: 32px; max-width: 6em; min-width: 0px; - } + }} - .ikp-search-bar__button { + .ikp-search-bar__button {{ align-items: center; justify-content: center; - } + }} .ikp-search-bar__kbd-wrapper, - .ikp-search-bar__text { + .ikp-search-bar__text {{ display: none; - } + }} - .ikp-search-bar__icon { + .ikp-search-bar__icon {{ padding: 0; margin-right: 2px; - } + }} - .ikp-search-bar__content-wrapper { + .ikp-search-bar__content-wrapper {{ justify-content: center; - } - } + }} + }} - .ikp-search-bar__icon { + .ikp-search-bar__icon {{ display: flex; - } + }} - .ikp-search-bar__icon svg { + .ikp-search-bar__icon svg {{ width: auto; - } + }} - .ikp-search-bar__kbd-wrapper { + .ikp-search-bar__kbd-wrapper {{ padding: 0px 0.25rem; justify-content: center; align-items: center; @@ -297,29 +295,30 @@ def add_hooks(self): font-style: normal; margin-left: auto; width: fit-content; - } + }} .ikp-search-bar__text, - .ikp-search-bar__icon { + .ikp-search-bar__icon {{ color: var(--m-slate-7, #67707E); font-weight: 500; font-style: normal; line-height: 1.5rem; font-size: 0.875rem; - } + }} + ${{{self.custom_style!s}}} `, - }, + }}, ], - } - }, - searchSettings: { // optional InkeepSearchSettings + }} + }}, + searchSettings: {{ tabs: ['All', 'Docs', 'Components', 'API Reference', 'Blogs', 'GitHub', 'Forums'].map((t) => [ t, - { isAlwaysVisible: true }, + {{ isAlwaysVisible: true }}, ]), placeholder: 'Search', - }, - aiChatSettings: { // optional typeof InkeepAIChatSettings + }}, + aiChatSettings: {{ aiAssistantAvatar: '/logos/small_logo.svg', chatSubjectName: 'Reflex', exampleQuestions: [ @@ -328,73 +327,75 @@ def add_hooks(self): 'Where can I deploy my apps?', ], getHelpOptions: [ - { - action: { + {{ + action: {{ type: "open_link", url: "https://reflex.dev/pricing" - }, - icon: { + }}, + icon: {{ builtIn: "LuCalendar" - }, + }}, name: "Get a custom demo" - }, - { - action: { + }}, + {{ + action: {{ type: "open_link", url: "https://github.com/reflex-dev/reflex/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=" - }, - icon: { + }}, + icon: {{ builtIn: "FaGithub" - }, + }}, name: "File an issue on Reflex's GitHub." - }, - { - action: { + }}, + {{ + action: {{ type: "open_link", url: "https://discord.gg/T5WSbC2YtQ" - }, - icon: { + }}, + icon: {{ builtIn: "FaDiscord" - }, + }}, name: "Ask on Reflex's Discord." - } + }} ], getTools: () => [ - { + {{ type: "function", - function: { + function: {{ name: "provideAnswerConfidence", description: "Determine how confident the AI assistant was and whether or not to escalate to humans.", parameters: escalationParams, - }, - renderMessageButtons: ({ args }) => { + }}, + renderMessageButtons: ({{ args }}) => {{ const confidence = args.answerConfidence; - if (["not_confident", "no_sources", "other"].includes(confidence)) { + if (["not_confident", "no_sources", "other"].includes(confidence)) {{ return [ - { + {{ label: "Contact Support", - action: { + action: {{ 'type': 'open_form', - }, - } + }}, + }} ]; - } + }} return []; - }, - }, + }}, + }}, ], - }, -};""", + }}, +}};""" + ), ] @classmethod - def create(cls): + def create(cls, custom_style: str = ""): """Create the search component.""" return super().create( InkeepSearchBar.create( special_props=[Var("{...searchBarProps}")], - ) + ), + custom_style=custom_style, ) diff --git a/pcweb/components/docpage/navbar/search.py b/pcweb/components/docpage/navbar/search.py index d4083520b..cfff95ae5 100644 --- a/pcweb/components/docpage/navbar/search.py +++ b/pcweb/components/docpage/navbar/search.py @@ -6,5 +6,5 @@ @rx.memo -def search_bar() -> rx.Component: - return inkeep() +def search_bar(custom_style: str = "") -> rx.Component: + return inkeep(custom_style=custom_style) diff --git a/pcweb/views/marketing_navbar.py b/pcweb/views/marketing_navbar.py index 017784f63..8ebdf6051 100644 --- a/pcweb/views/marketing_navbar.py +++ b/pcweb/views/marketing_navbar.py @@ -3,6 +3,7 @@ from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.docpage.navbar.buttons.sidebar import navbar_sidebar_button +from pcweb.components.docpage.navbar.search import search_bar from pcweb.components.icons.icons import get_icon from pcweb.components.marketing_button import button from pcweb.components.marquee import marquee @@ -98,7 +99,7 @@ def logo() -> rx.Component: class_name="shrink-0 hidden dark:block", ), to="/", - class_name="block shrink-0 mr-9", + class_name="block shrink-0 lg:mr-9", ) @@ -483,11 +484,21 @@ def navigation_menu() -> rx.Component: github(), custom_attrs={"role": "menuitem"}, ), + ui.navigation_menu.item( + search_bar( + custom_style=".ikp-search-bar__text { display: none !important; } " + "[data-theme='light'] .ikp-search-bar__container, " + "[data-theme='dark'] .ikp-search-bar__container { " + "width: auto !important; max-width: fit-content !important; min-width: fit-content !important; }" + ), + unstyled=True, + custom_attrs={"role": "menuitem"}, + ), ui.navigation_menu.item( rx.el.a( button( "Sign In", - ui.icon("Login01Icon", class_name="scale-x-[-1]"), + ui.icon("Login01Icon", class_name="scale-x-[-1] max-lg:hidden"), size="sm", variant="outline", native_button=False, From 569ae0cb26c8e833235df42074727f59e46d253e Mon Sep 17 00:00:00 2001 From: carlosabadia Date: Tue, 24 Feb 2026 16:49:14 +0100 Subject: [PATCH 2/4] update --- pcweb/components/docpage/navbar/inkeep.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcweb/components/docpage/navbar/inkeep.py b/pcweb/components/docpage/navbar/inkeep.py index 727d80654..407763d9e 100644 --- a/pcweb/components/docpage/navbar/inkeep.py +++ b/pcweb/components/docpage/navbar/inkeep.py @@ -24,7 +24,7 @@ def add_hooks(self) -> list[str | rx.Var]: """Add the hooks for the component.""" return [ "const { resolvedColorMode } = useContext(ColorModeContext)", - rx.Var( + ( f"""const escalationParams = {{ type: "object", properties: {{ From eb9689e26c49568f3a420cb2bd5bfa71b88130bc Mon Sep 17 00:00:00 2001 From: carlosabadia Date: Tue, 24 Feb 2026 16:53:44 +0100 Subject: [PATCH 3/4] bring back comment --- pcweb/components/docpage/navbar/inkeep.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pcweb/components/docpage/navbar/inkeep.py b/pcweb/components/docpage/navbar/inkeep.py index 407763d9e..cc9ff1681 100644 --- a/pcweb/components/docpage/navbar/inkeep.py +++ b/pcweb/components/docpage/navbar/inkeep.py @@ -114,6 +114,9 @@ def add_hooks(self) -> list[str | rx.Var]: if (matchUrl(pattern)) {{ return [ ...(source.tabs ?? []), + // If the first breadcrumb is the same as the tab, use the remaining breadcrumbs + // This is only if you don't want breadcrumbs to include current tab, e.g. just "Blog Post" instead of "Blogs > Blog Post" in the Blogs tab + // The tab type accepts a string or an object with a breadcrumbs property i.e. breadcrumbs shown for this source in that tab [ tab, {{ breadcrumbs: breadcrumbs[0] === tab ? breadcrumbs.slice(1) : breadcrumbs }}, From b7171c16f5e6b719fbca6d96cb7278dfe2612763 Mon Sep 17 00:00:00 2001 From: carlosabadia Date: Thu, 26 Feb 2026 18:53:00 +0100 Subject: [PATCH 4/4] update styles --- pcweb/components/docpage/navbar/search.py | 19 ++++++++++++++++--- pcweb/views/marketing_navbar.py | 17 +++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/pcweb/components/docpage/navbar/search.py b/pcweb/components/docpage/navbar/search.py index cfff95ae5..94646f199 100644 --- a/pcweb/components/docpage/navbar/search.py +++ b/pcweb/components/docpage/navbar/search.py @@ -1,10 +1,23 @@ """Search bar component for the navbar.""" +from typing import Optional + import reflex as rx from .inkeep import inkeep -@rx.memo -def search_bar(custom_style: str = "") -> rx.Component: - return inkeep(custom_style=custom_style) +def search_bar( + custom_style: str = "", trigger: Optional[rx.Component] = None +) -> rx.Component: + if trigger is None: + return inkeep(custom_style=custom_style) + + return rx.el.div( + trigger, + rx.el.div( + inkeep(custom_style=custom_style), + class_name="absolute inset-0 z-10 opacity-0 overflow-hidden", + ), + class_name="relative inline-flex", + ) diff --git a/pcweb/views/marketing_navbar.py b/pcweb/views/marketing_navbar.py index 8ebdf6051..b108d4009 100644 --- a/pcweb/views/marketing_navbar.py +++ b/pcweb/views/marketing_navbar.py @@ -486,14 +486,23 @@ def navigation_menu() -> rx.Component: ), ui.navigation_menu.item( search_bar( - custom_style=".ikp-search-bar__text { display: none !important; } " - "[data-theme='light'] .ikp-search-bar__container, " - "[data-theme='dark'] .ikp-search-bar__container { " - "width: auto !important; max-width: fit-content !important; min-width: fit-content !important; }" + trigger=button( + ui.icon("Search01Icon", class_name="size-4"), + "⌘+K", + size="sm", + variant="ghost", + class_name="group-hover:text-primary-10 dark:group-hover:text-primary-9", + native_button=False, + ), ), + class_name="group", unstyled=True, custom_attrs={"role": "menuitem"}, ), + class_name="flex flex-row gap-2 m-0 h-full list-none items-center", + custom_attrs={"role": "menubar"}, + ), + ui.navigation_menu.list( ui.navigation_menu.item( rx.el.a( button(