feat(ui): center search in a 3-zone top bar

Implements the v2 design's top-bar reorganization in the Tauri
launcher. The bar was previously a flat flex row that let the search
field drift left or right depending on filter / sort widths; now it's
a 3-column CSS grid with the search field pinned to the geometric
center of the window.

- `.topbar` becomes `display: grid` with
  `grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr)` and a
  16 px column gap. The middle (auto) column holds only the search,
  capped at `flex: 0 1 360px` so it cannot push into the side columns.
- The left zone is `flex; justify-content: space-between`: brand
  pins far-left, filter pills hug the search. The filter pills are
  now grouped with the search semantically (they scope it) instead of
  floating next to the brand.
- The right zone mirrors that: sort hugs the search, kebab pins
  far-right, with the directory button between them.
- A `@container launcher (max-width: 1100px)` rule collapses the
  layout back to a single nowrap flex row at narrow widths — the
  geometric centering doesn't read at small widths and would force
  awkward truncation, so we abandon it rather than fight it. The
  launcher root opts into container queries via
  `container-type: inline-size; container-name: launcher`.

`TopBar.tsx` now wraps the existing children in `.topbar-left`,
`.topbar-center`, `.topbar-right` (plus `.topbar-left-trail` /
`.topbar-right-lead` for the inner space-between alignment), but each
control component is otherwise untouched.

Test Plan

- `just frontend-test` — passes.
- `npx tsc --noEmit` from the frontend crate — clean.
- Manual: run `just run`, confirm the search input's horizontal center
  matches the window's horizontal center across the standard launcher
  width. Shrink the window below 1100 px and confirm the row collapses
  to a single left-to-right strip with no overlap or wrapping.
This commit is contained in:
2026-05-21 18:27:25 +02:00
parent b169a05c31
commit 095bc9b9ff
2 changed files with 80 additions and 11 deletions
@@ -36,11 +36,21 @@ export const TopBar = ({
kebabItems,
}: Props) => (
<header className="topbar">
<Brand peerCount={peerCount} />
<SegmentedFilters value={filter} onChange={setFilter} counts={counts} />
<SearchField value={query} onChange={setQuery} />
<SortMenu value={sort} onChange={setSort} />
<DirectoryButton path={gameDir} onClick={onPickDirectory} />
<KebabMenu items={kebabItems} />
<div className="topbar-left">
<Brand peerCount={peerCount} />
<div className="topbar-left-trail">
<SegmentedFilters value={filter} onChange={setFilter} counts={counts} />
</div>
</div>
<div className="topbar-center">
<SearchField value={query} onChange={setQuery} />
</div>
<div className="topbar-right">
<div className="topbar-right-lead">
<SortMenu value={sort} onChange={setSort} />
</div>
<DirectoryButton path={gameDir} onClick={onPickDirectory} />
<KebabMenu items={kebabItems} />
</div>
</header>
);
@@ -6,6 +6,8 @@
flex-direction: column;
background: var(--bg-0);
color: var(--t-1);
container-type: inline-size;
container-name: launcher;
overflow: hidden;
position: relative;
isolation: isolate;
@@ -56,7 +58,7 @@
}
}
/* Top bar */
/* Top bar — three visual zones with search at the geometric center */
.topbar {
position: relative;
z-index: 10;
@@ -64,12 +66,69 @@
-webkit-backdrop-filter: blur(20px) saturate(140%);
backdrop-filter: blur(20px) saturate(140%);
border-bottom: 1px solid var(--bd-1);
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: center;
column-gap: 16px;
padding: 14px 24px;
min-height: 64px;
}
.topbar-left {
display: flex;
align-items: center;
gap: 18px;
padding: 14px 24px;
flex-wrap: nowrap;
min-height: 64px;
justify-content: space-between;
gap: 16px;
min-width: 0;
}
.topbar-left-trail {
display: flex;
align-items: center;
min-width: 0;
}
.topbar-center {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
}
.topbar-center .search {
flex: 0 1 360px;
min-width: 0;
}
.topbar-right {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.topbar-right-lead {
display: flex;
align-items: center;
min-width: 0;
}
/* Below ~1100px of launcher width the geometric centering stops reading —
collapse the three zones into a single left-to-right flowing row. */
@container launcher (max-width: 1100px) {
.topbar {
display: flex;
flex-wrap: nowrap;
gap: 16px;
}
.topbar-left,
.topbar-center,
.topbar-right {
justify-content: flex-start;
flex: 0 0 auto;
gap: 12px;
}
.topbar-center {
flex: 1 1 200px;
}
.topbar-center .search {
flex: 1 1 auto;
}
}
/* Brand */