From 9e6a6b5fdc6b85ad6701775b7218ad882aeda004 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 29 Jul 2026 22:00:45 +0200 Subject: [PATCH] feat(webui): center optional global search --- webui/src/layout/Titlebar.tsx | 174 ++++++++++++++++++---------------- webui/src/styles/layout.css | 8 +- webui/src/types.ts | 1 + 3 files changed, 101 insertions(+), 82 deletions(-) diff --git a/webui/src/layout/Titlebar.tsx b/webui/src/layout/Titlebar.tsx index 3c72689..db0d21f 100644 --- a/webui/src/layout/Titlebar.tsx +++ b/webui/src/layout/Titlebar.tsx @@ -39,6 +39,14 @@ export default function Titlebar({ settings, auth, onAuthChange, maintenanceMode const ViewSelector = viewsRuntime?.Selector ?? null; const searchRuntime = usePlatformUiCapability("search.runtime"); const GlobalSearch = searchRuntime?.GlobalSearch ?? null; + const canUseGlobalSearch = Boolean( + searchRuntime && + ( + !searchRuntime.anyOf?.length || + hasAnyScope(auth, searchRuntime.anyOf) + ) + ); + const showGlobalSearch = Boolean(auth && GlobalSearch && canUseGlobalSearch); const activeTenant = auth?.active_tenant ?? auth?.tenant ?? null; const tenants = auth?.tenants ?? (activeTenant ? [activeTenant] : []); @@ -138,7 +146,7 @@ export default function Titlebar({ settings, auth, onAuthChange, maintenanceMode } return ( -
+
{!backendReachable ?
} - {auth && showContextSelectors && -
- {activeTenant && showTenantControl && -
- {translateText("i18n:govoplan-core.tenant_label_prefix")} - {canSwitchTenant ? - <> - - {tenantOpen && -
- {tenants.map((tenant) => { - const active = tenant.id === activeTenant.id; - return ( - ); - - })} - {tenantError && {tenantError}} -
- } - : - - {activeTenant.name} - } -
- } - {ViewSelector && - - } -
- } - -
- - {auth && GlobalSearch && - - } - - - - - {auth && notificationsAvailable && - - } - -
- - {accountOpen && -
- {auth ? +
+ {auth && showContextSelectors && +
+ {activeTenant && showTenantControl && +
+ {translateText("i18n:govoplan-core.tenant_label_prefix")} + {canSwitchTenant ? <> - {auth.user && -
- {auth.user.display_name || auth.user.email} - {auth.user.email} -
- } - - + + {tenantOpen && +
+ {tenants.map((tenant) => { + const active = tenant.id === activeTenant.id; + return ( + ); + + })} + {tenantError && {tenantError}} +
+ } : - + {activeTenant.name} }
+ } + {ViewSelector && + + } +
}
+ {auth && GlobalSearch && canUseGlobalSearch && +
+ +
+ } + +
+ + + + {auth && notificationsAvailable && + + } + +
+ + {accountOpen && +
+ {auth ? + <> + {auth.user && +
+ {auth.user.display_name || auth.user.email} + {auth.user.email} +
+ } + + + : + + + } +
+ } +
+
+ {loginOpen && setLoginOpen(false)} onLogin={handleLogin} />}
); diff --git a/webui/src/styles/layout.css b/webui/src/styles/layout.css index d0c0d06..6069904 100644 --- a/webui/src/styles/layout.css +++ b/webui/src/styles/layout.css @@ -26,7 +26,12 @@ .icon-nav-item:hover, .icon-nav-item.active { background: var(--rail-bg-active); color: var(--on-accent); border-left-color: var(--accent); } .icon-rail.compact { width: 58px; } .app-main { min-width: 0; min-height: 0; height: 100vh; display: grid; grid-template-rows: 64px 51px minmax(0, 1fr); } -.titlebar { position: relative; background: var(--titlebar-bg); border-bottom: var(--border-line); display: flex; align-items: center; padding: 0 18px; gap: 36px; z-index: 100; box-shadow: var(--shadow-chrome); } +.titlebar { position: relative; background: var(--titlebar-bg); border-bottom: var(--border-line); display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; padding: 0 18px; gap: 18px; z-index: 100; box-shadow: var(--shadow-chrome); } +.titlebar.has-global-search { grid-template-columns: minmax(0, 1fr) minmax(190px, min(360px, 28vw)) minmax(0, 1fr); } +.titlebar-leading { grid-column: 1; display: flex; align-items: center; min-width: 0; } +.titlebar-global-search { grid-column: 2; position: relative; width: 100%; min-width: 0; height: 34px; } +.titlebar-actions { grid-column: 2; display: flex; align-items: center; justify-self: end; min-width: 0; gap: 10px; } +.titlebar.has-global-search .titlebar-actions { grid-column: 3; } .titlebar-context-selectors { display: flex; align-items: center; min-width: 0; gap: 12px; } .tenant-selector { height: 40px; min-width: 210px; border: var(--border-line); border-radius: var(--radius-sm); background: var(--surface); display: flex; align-items: center; padding: 0 12px; gap: 5px; box-shadow: var(--shadow-xs); } .tenant-label, .tenant-caret, .muted { color: var(--muted); } @@ -139,6 +144,7 @@ .code-panel { background: var(--code-bg); color: var(--code-text); padding: 18px; border-radius: 4px; overflow: auto; } @media (max-width: 900px) { .api-mini { display: none; } + .titlebar.has-global-search { grid-template-columns: minmax(0, 1fr) 34px auto; } .workspace { grid-template-columns: 1fr; } .section-sidebar { display: none; } .wizard-card { grid-template-columns: 1fr; } diff --git a/webui/src/types.ts b/webui/src/types.ts index 697fbb2..9ceb2d5 100644 --- a/webui/src/types.ts +++ b/webui/src/types.ts @@ -428,6 +428,7 @@ export type GlobalSearchProps = { export type SearchRuntimeUiCapability = { GlobalSearch: ComponentType; + anyOf?: string[]; }; export type DashboardWidgetSize = "small" | "medium" | "wide" | "full";