swarm-ui: move dynamic agent-term tabs into the top nav bar
mara, PR review: "the dynamic tab should be in the top bar, not a new one below". Moves the .shell-tabs group from its own sticky row under the header into .shell-nav itself, right after the nav indicator. Also adds a third re-measure effect for the sliding nav indicator, keyed on tabs.length: with tabs inline in the same flex row the indicator measures, closing a background tab (no navigation) can shrink the row without the hop effect's own re-measure ever firing. Same reflow-not-navigation reasoning as the existing resize-listener effect.
This commit is contained in:
parent
7bf4cfbc4c
commit
88c386c96c
2 changed files with 55 additions and 46 deletions
|
|
@ -214,9 +214,13 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
// Every subsequent change (a real navigation) still animates normally.
|
||||
const [indicatorSettledOnce, setIndicatorSettledOnce] = useState(false);
|
||||
|
||||
// Open agent-terminal tabs — the row below `.shell-nav` that
|
||||
// grows/shrinks as tabs open/close, entirely separate from the fixed
|
||||
// `NAV_ITEMS` list above.
|
||||
// Open agent-terminal tabs — rendered inline in `.shell-nav` itself
|
||||
// (mara, PR review: "the dynamic tab should be in the top bar, not a
|
||||
// new one below" — an earlier cut had these as their own row under
|
||||
// the header; see the git history on this file for that version and
|
||||
// why it was reverted). Entirely separate from the fixed `NAV_ITEMS`
|
||||
// list above, so they get their own small component (`DynamicTabLink`)
|
||||
// rather than reusing `NavLink`.
|
||||
const { tabs, close: closeTab } = useDynamicTabs();
|
||||
|
||||
// Applied once here, not inside `SettingsMenu` — every route mounts
|
||||
|
|
@ -313,6 +317,18 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
return () => window.removeEventListener("resize", onResize);
|
||||
}, []);
|
||||
|
||||
// Same re-measure, for the same "a reflow, not a navigation" reason —
|
||||
// now that the dynamic tabs render inline in `.shell-nav` (see the
|
||||
// `useDynamicTabs()` comment above), *closing* a background tab (one
|
||||
// that isn't the active route) shrinks the row without any navigation
|
||||
// firing to trigger the hop effect's own re-measure. Opening one
|
||||
// doesn't need this: `useDynamicTabs().open()` always navigates, so
|
||||
// the hop effect already covers that case.
|
||||
useEffect(() => {
|
||||
if (settledIndexRef.current === -1) return;
|
||||
setIndicator(measureIndex(settledIndexRef.current));
|
||||
}, [tabs.length]);
|
||||
|
||||
// Fetched once here, not per-page: every route mounts inside one
|
||||
// `<Shell>`, and the swarm's name doesn't change within a page
|
||||
// visit. A fetch failure is silently ignored — `swarmName` just stays
|
||||
|
|
@ -387,6 +403,30 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
backgroundColor: indicator?.accent ?? "transparent",
|
||||
}}
|
||||
/>
|
||||
{/* Open agent-terminal tabs — inline in the same nav row as
|
||||
the static routes (mara's review call, see the
|
||||
`useDynamicTabs()` comment above). Only rendered when
|
||||
there's something to show — an always-present empty group
|
||||
would just be dead chrome for the far more common case of
|
||||
no open terminal tabs. `role="group"`, not a second `<nav>`:
|
||||
there's already exactly one nav landmark in this header. */}
|
||||
{tabs.length > 0 && (
|
||||
<div
|
||||
class="shell-tabs"
|
||||
role="group"
|
||||
aria-label="open agent terminals"
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<DynamicTabLink
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
label={tab.label}
|
||||
active={tab.href === location}
|
||||
onClose={() => closeTab(tab.href)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
{/* Single `margin-left: auto` on the wrapper, not on each menu
|
||||
individually — two adjacent flex items both set to
|
||||
|
|
@ -398,26 +438,6 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
<UserMenu />
|
||||
</div>
|
||||
</header>
|
||||
{/* Second row, only when there's something to show — an always-
|
||||
present empty bar would just be dead chrome for the far more
|
||||
common case of no open terminal tabs. Own `<nav>`, not folded
|
||||
into `.shell-nav` above: these come and go independently of
|
||||
the static route list and shouldn't shift `NAV_ITEMS`'
|
||||
measured positions (`measureIndex` above) by changing the
|
||||
header's height mid-sweep. */}
|
||||
{tabs.length > 0 && (
|
||||
<nav class="shell-tabs" aria-label="open agent terminals">
|
||||
{tabs.map((tab) => (
|
||||
<DynamicTabLink
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
label={tab.label}
|
||||
active={tab.href === location}
|
||||
onClose={() => closeTab(tab.href)}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
{/* Keyed by route so it remounts (and replays its entrance
|
||||
animation) on every navigation — see the file-top comment.
|
||||
`shell-body-wide` is additive (see `WIDE_BODY_ROUTES` above),
|
||||
|
|
|
|||
Loading…
Reference in a new issue