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('