From 16d7810f92adbbc6d2a3663420bb18e4aad44391 Mon Sep 17 00:00:00 2001 From: kev1n77 Date: Wed, 9 Sep 2026 01:52:17 +0800 Subject: [PATCH] fix(ui): align global search dialog composition Share the 800 by 460 search scene contract between Lab Patterns and the product. Scope the 30px query height to its shell, replace scope buttons with the public small TabGroup, and remove superseded query and responsive spacing overrides. Keep the results scrollbar at the dialog edge and shrink insets in narrow windows. Lab: Patterns > search command surface, with scope selection and scrolling results. Product: Ctrl K global search. Validation: design-system:check, check:web, and 16 tests across globalSearchArchitecture, globalSearchShortcut and globalSearchResultPresentation passed. Native desktop confirmed target geometry, keyboard scope navigation, empty state and Escape close; full IME cancellation, narrow-window and long-result combinations remain manual follow-ups. Remote: no four-scenario end-to-end verification. Search providers and activation adapters are unchanged; embedded search keeps its regular query size. No persisted shape changes. --- .../design-lab/src/pages/PatternsPage.tsx | 20 +- design-system/apps/design-lab/src/styles.css | 24 +- .../packages/design-tokens/README.md | 5 + .../design-tokens/src/system.tokens.json | 8 + .../ui/generated/design-system.css | 6 + .../app/global-search/GlobalSearchRoot.scss | 227 ++---------------- .../app/global-search/GlobalSearchRoot.tsx | 42 ++-- .../globalSearchArchitecture.test.ts | 3 +- 8 files changed, 88 insertions(+), 247 deletions(-) diff --git a/design-system/apps/design-lab/src/pages/PatternsPage.tsx b/design-system/apps/design-lab/src/pages/PatternsPage.tsx index 0c065e54c3..e16b89ac43 100644 --- a/design-system/apps/design-lab/src/pages/PatternsPage.tsx +++ b/design-system/apps/design-lab/src/pages/PatternsPage.tsx @@ -23,6 +23,8 @@ import { NavigationPanelSection, PageHeader, SearchField, + ScrollArea, + TabGroup, SegmentedControl, Select, StatusPill, @@ -156,19 +158,21 @@ export function PatternsPage({ colorScheme, contrast, density, tokenOverrides }: - - } description={t("patterns.search.description")} title={t("patterns.search.title")} /> - } onClear={() => setQuery("")} onValueChange={setQuery} placeholder={t("patterns.search.searchPlaceholder")} shortcut={Ctrl K} value={query} /> - + +
+
} onClear={() => setQuery("")} onValueChange={setQuery} placeholder={t("patterns.search.searchPlaceholder")} shortcut={Ctrl K} value={query} />
+ +
+
- {visibleActions.map((action) => } size="md">{t(action.title)})} + {scope !== "files" && visibleActions.map((action) => } size="md">{t(action.title)})}
-
+ {scope !== "commands" &&
{t("patterns.search.recent")} , id: "open-readme", label: t("patterns.actions.openFiles") }]} appearance="surface" label="README.md" leading={}>design-system/README.md , id: "open-package", label: t("patterns.actions.openFiles") }]} appearance="surface" label="package.json" leading={}>design-system/packages/ui/package.json -
- +
} +
diff --git a/design-system/apps/design-lab/src/styles.css b/design-system/apps/design-lab/src/styles.css index b7a4bada7a..4a10123b8a 100644 --- a/design-system/apps/design-lab/src/styles.css +++ b/design-system/apps/design-lab/src/styles.css @@ -2917,16 +2917,30 @@ body, } .pattern-command { - max-inline-size: 900px; + inline-size: min(100%, var(--openbitfun-layout-search-dialog-width)); + block-size: min(var(--openbitfun-layout-search-dialog-height), calc(100dvh - 2 * var(--openbitfun-overlay-dialog-viewport-gutter))); + min-block-size: 0; + padding: var(--openbitfun-layout-search-dialog-padding); } -.pattern-command > [data-openbitfun-component="search-field"] { +.pattern-command-header { + display: grid; + flex: 0 0 auto; + min-inline-size: 0; + gap: var(--openbitfun-layout-search-dialog-scope-gap); +} + +.pattern-command-query { + --openbitfun-control-height-sm: var(--openbitfun-layout-search-dialog-query-height); +} + +.pattern-command-query > [data-openbitfun-component="search-field"] { inline-size: 100%; } -.pattern-command [data-openbitfun-part="body"] { - display: grid; - gap: var(--openbitfun-space-6); +.pattern-command-results { + flex: 1 1 auto; + min-block-size: 0; } .pattern-action-grid { diff --git a/design-system/packages/design-tokens/README.md b/design-system/packages/design-tokens/README.md index 0de4e90791..d2d06bf37f 100644 --- a/design-system/packages/design-tokens/README.md +++ b/design-system/packages/design-tokens/README.md @@ -64,3 +64,8 @@ its established role. `type.overline.*` owns extra-small uppercase annotations; these modifiers keep product styles semantic without changing their resolved metrics during migration. `type.modifier.leading.support` provides the compact 1.45 supporting-text rhythm used when an 11px role must align to a 16px line. + +`layout.searchDialog` owns the shared Lab/product search composition: 800 × 460 +when space permits, 20px inset and query-to-scope gap, and a 30px query row. +Only the query shell scopes `control.height.sm`; general Input and Button sizes +retain their defaults. Results scroll within the available viewport. diff --git a/design-system/packages/design-tokens/src/system.tokens.json b/design-system/packages/design-tokens/src/system.tokens.json index 015906bf08..004db2417a 100644 --- a/design-system/packages/design-tokens/src/system.tokens.json +++ b/design-system/packages/design-tokens/src/system.tokens.json @@ -678,6 +678,14 @@ }, "layout": { "$type": "dimension", + "searchDialog": { + "width": { "$value": "{overlay.dialog.maxInlineSizeXlarge}" }, + "height": { "$value": "460px" }, + "padding": { "$value": "{space.5}" }, + "queryHeight": { "$value": "30px" }, + "scopeGap": { "$value": "{space.5}" }, + "resultsGap": { "$value": "30px" } + }, "overflowText": { "fadeExtent": { "$value": "{space.4}" } }, diff --git a/src/apps/data-migrator/ui/generated/design-system.css b/src/apps/data-migrator/ui/generated/design-system.css index f3815e8ba6..54d105c7cb 100644 --- a/src/apps/data-migrator/ui/generated/design-system.css +++ b/src/apps/data-migrator/ui/generated/design-system.css @@ -261,6 +261,12 @@ --openbitfun-layout-navigation-panel-section-gap: var(--openbitfun-space-1); --openbitfun-layout-navigation-panel-surface-padding: var(--openbitfun-space-2); --openbitfun-layout-overflow-text-fade-extent: var(--openbitfun-space-4); + --openbitfun-layout-search-dialog-height: 460px; + --openbitfun-layout-search-dialog-padding: var(--openbitfun-space-5); + --openbitfun-layout-search-dialog-query-height: 30px; + --openbitfun-layout-search-dialog-results-gap: 30px; + --openbitfun-layout-search-dialog-scope-gap: var(--openbitfun-space-5); + --openbitfun-layout-search-dialog-width: var(--openbitfun-overlay-dialog-max-inline-size-xlarge); --openbitfun-layout-spinner-matrix-cell-lg: 8px; --openbitfun-layout-spinner-matrix-cell-md: 6px; --openbitfun-layout-spinner-matrix-cell-sm: 4px; diff --git a/src/web-ui/src/app/global-search/GlobalSearchRoot.scss b/src/web-ui/src/app/global-search/GlobalSearchRoot.scss index d9f12192a9..85f5718c65 100644 --- a/src/web-ui/src/app/global-search/GlobalSearchRoot.scss +++ b/src/web-ui/src/app/global-search/GlobalSearchRoot.scss @@ -1,10 +1,10 @@ @use '../styles/nav-panel-font-scope.scss' as nav-font; .global-search-dialog { - // Fits the default two-row action grid plus one workspace and assistant. - // Query and drilldown views keep the results region scrollable when needed. + // The results viewport absorbs short windows and longer result sets. + inline-size: var(--openbitfun-layout-search-dialog-width); block-size: min( - 480px, + var(--openbitfun-layout-search-dialog-height), calc(100vh - 2 * var(--openbitfun-overlay-dialog-viewport-gutter)) ); } @@ -33,66 +33,7 @@ &__header { flex: 0 0 auto; - padding: 16px 18px 10px; - } - - &__query { - display: flex; - align-items: center; - min-height: 40px; - padding: 0 10px 0 12px; - gap: 8px; - border: 1px solid var(--openbitfun-color-border-subtle); - border-radius: 9px; - background: var(--global-search-raised-surface); - box-shadow: none; - - &:focus-within { - border-color: var(--openbitfun-color-border-default); - box-shadow: none; - } - - input { - flex: 1; - min-width: 0; - height: 100%; - border: 0; - outline: 0; - background: transparent; - color: var(--openbitfun-color-content-primary); - font: inherit; - font-size: var(--openbitfun-type-label-md-font-size); - font-weight: var(--openbitfun-type-body-sm-font-weight); - line-height: var(--openbitfun-type-meta-line-height); - - &::placeholder { - color: var(--openbitfun-color-content-muted); - opacity: 1; - } - - // The query shell owns the visible focus state. This selector also - // outranks AppLayout's app-wide `*:focus-visible` outline. - &:focus, - &:focus-visible { - outline: none; - outline-offset: 0; - box-shadow: none; - } - } - } - - &__scope-bar { - display: flex; - align-items: center; - min-height: 28px; - margin-top: 10px; - gap: 12px; - } - - &__scopes { - display: flex; - align-items: center; - gap: 4px; + padding: 0; } &__results { @@ -465,14 +406,7 @@ } @media (max-width: 980px) { - .global-search-dialog { - block-size: calc(100vh - 2 * var(--openbitfun-overlay-dialog-viewport-gutter)); - } - - .global-search--modal { - .global-search__header { - padding: 26px 26px 18px; - } +.global-search--modal { .global-search__results { padding-inline: 26px; @@ -490,35 +424,6 @@ @media (max-width: 700px) { .global-search--modal { - .global-search__header { - padding: 18px 18px 14px; - } - - .global-search__query { - min-height: 56px; - padding-inline: 14px; - - input { - font-size: var(--openbitfun-type-body-lg-font-size); - } - } - - .global-search__query-icon { - width: 20px; - height: 20px; - padding-right: 9px; - } - - .global-search__scope-bar { - min-height: 38px; - margin-top: 14px; - } - - .global-search__scope { - min-height: 36px; - padding-inline: 13px; - font-size: var(--openbitfun-type-label-md-font-size); - } .global-search__prefix-hint, .global-search__result-context, @@ -565,18 +470,6 @@ @media (max-height: 850px) and (min-width: 701px) { .global-search--modal { - .global-search__header { - padding-top: 24px; - padding-bottom: 16px; - } - - .global-search__query { - min-height: 58px; - } - - .global-search__scope-bar { - margin-top: 14px; - } .global-search__result--action { min-height: 112px; @@ -603,60 +496,20 @@ color: var(--openbitfun-color-content-primary); font-family:var(--openbitfun-type-body-sm-font-family); - .global-search__header { - padding: 0; - } - - .global-search__query-system-shell { - width: 100%; - } - + .global-search__query-system-shell, .global-search__query--system { - display: inline-flex; width: 100%; - min-height: 0; - padding: 0; - gap: 0; - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; - - &:focus-within { - border-color: transparent; - box-shadow: none; - } - - input { - color: var(--openbitfun-color-content-primary); - font-family:var(--openbitfun-type-body-sm-font-family); - font-size: var(--openbitfun-type-body-md-font-size); - font-weight: var(--openbitfun-type-body-sm-font-weight); - - &::placeholder { - color: var(--openbitfun-color-content-muted); - } - } } .global-search__scope-bar { - min-height: var(--openbitfun-control-height-sm); - margin-top: var(--openbitfun-space-4); - gap: var(--openbitfun-space-2); - } - - .global-search__scopes { - gap: var(--openbitfun-space-2); - } - - .global-search__scope--system { - min-height: var(--openbitfun-control-height-sm); - font-size: var(--openbitfun-type-body-md-font-size); + display: flex; + min-width: 0; + margin-top: var(--openbitfun-layout-search-dialog-scope-gap); } .global-search__results { min-height: 0; - margin-top: var(--openbitfun-space-4); + margin-top: var(--openbitfun-layout-search-dialog-results-gap); padding: 0; color: var(--openbitfun-color-content-primary); scrollbar-gutter: auto; @@ -860,14 +713,8 @@ } @media (max-width: 980px) { - .global-search-modal-content { - padding: var(--openbitfun-space-4); - } .global-search--modal { - .global-search__header { - padding: 0; - } .global-search__results { padding: 0; @@ -889,29 +736,6 @@ } .global-search--modal { - .global-search__header { - padding: 0; - } - - .global-search__query--system { - min-height: 0; - padding: 0; - - input { - font-size: var(--openbitfun-type-body-md-font-size); - } - } - - .global-search__scope-bar { - min-height: var(--openbitfun-control-height-sm); - margin-top: var(--openbitfun-space-4); - } - - .global-search__scope--system { - min-height: var(--openbitfun-control-height-sm); - padding-inline: var(--openbitfun-space-4); - font-size: var(--openbitfun-type-label-md-font-size); - } .global-search__results { display: block !important; @@ -938,21 +762,6 @@ @media (max-height: 850px) and (min-width: 701px) { .global-search--modal { - .global-search__header { - padding: 0; - } - - .global-search__query--system { - min-height: 0; - } - - .global-search__scope-bar { - margin-top: var(--openbitfun-space-3); - } - - .global-search__results { - margin-top: var(--openbitfun-space-4); - } .global-search__footer { flex-basis: var(--openbitfun-control-height-sm); @@ -967,12 +776,18 @@ * to the viewport's content-bearing regions. */ .global-search-modal-content { - --global-search-modal-inline-end-inset: var(--openbitfun-overlay-dialog-content-padding-lg); + --global-search-modal-inline-end-inset: var(--openbitfun-layout-search-dialog-padding); + + padding: var(--openbitfun-layout-search-dialog-padding); padding-inline-end: 0; } .global-search--modal { + .global-search__query-system-shell { + --openbitfun-control-height-sm: var(--openbitfun-layout-search-dialog-query-height); + } + > .global-search__header, > .global-search__results, > .global-search__footer { @@ -980,15 +795,11 @@ } } -@media (max-width: 980px) { - .global-search-modal-content { - --global-search-modal-inline-end-inset: var(--openbitfun-space-4); - } -} - @media (max-width: 700px) { .global-search-modal-content { --global-search-modal-inline-end-inset: var(--openbitfun-space-3); + padding: var(--openbitfun-space-3); + padding-inline-end: 0; } } diff --git a/src/web-ui/src/app/global-search/GlobalSearchRoot.tsx b/src/web-ui/src/app/global-search/GlobalSearchRoot.tsx index afc18c0edc..e2899b2334 100644 --- a/src/web-ui/src/app/global-search/GlobalSearchRoot.tsx +++ b/src/web-ui/src/app/global-search/GlobalSearchRoot.tsx @@ -10,7 +10,7 @@ import React, { import { isImeOwnedKeyboardEvent } from '@/shared/utils/ime'; import { OverflowText, ActionCard, - Button, + TabGroup, Icon, KeyHint, SearchField, @@ -387,13 +387,13 @@ export const GlobalSearchContent: React.FC = ({ inputRef.current?.focus(); } : undefined} clearLabel={query ? tCommon('nav.search.clear') : undefined} - leadingIcon={} + leadingIcon={} shortcut={query ? undefined : ( {searchShortcutHint.key} )} - size="md" + size={variant === 'modal' ? 'sm' : 'md'} placeholder={tCommon('nav.search.inputPlaceholder')} aria-label={tCommon('nav.search.inputLabel')} role="combobox" @@ -407,28 +407,20 @@ export const GlobalSearchContent: React.FC = ({
-
- {(['all', 'actions', 'content'] as const).map((candidate) => { - const selected = effectiveScope === candidate; - return ( - - ); - })} -
+ ({ + value: candidate, + label: tCommon(`nav.search.scopes.${candidate}`), + disabled: parsedQuery.scopeForcedByPrefix && candidate !== 'actions', + }))} + onValueChange={(candidate) => { + setScope(candidate as GlobalSearchScope); + setDrilldownGroup(null); + }} + />
diff --git a/src/web-ui/src/app/global-search/globalSearchArchitecture.test.ts b/src/web-ui/src/app/global-search/globalSearchArchitecture.test.ts index 7e20e87cb2..9b0e078325 100644 --- a/src/web-ui/src/app/global-search/globalSearchArchitecture.test.ts +++ b/src/web-ui/src/app/global-search/globalSearchArchitecture.test.ts @@ -32,7 +32,8 @@ describe('global search ownership', () => { expect(canvasShortcuts).toContain('enabled: enabled && missionControlEnabled'); expect(globalSearch).toContain('className="global-search__query global-search__query--system"'); expect(globalSearch).toContain('shortcut={query ? undefined : ('); - expect(globalSearch).toContain('className={`global-search__scope global-search__scope--system'); + expect(globalSearch).toContain('