Skip to content

fix(search): keep a constant gap between the search modal and the header - #1297

Draft
tmashara wants to merge 4 commits into
masterfrom
gro-841-search-modal-offset
Draft

fix(search): keep a constant gap between the search modal and the header#1297
tmashara wants to merge 4 commits into
masterfrom
gro-841-search-modal-offset

Conversation

@tmashara

@tmashara tmashara commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Mirrors metabase/metabase.github.io#6422 — same architecture.

Inkeep fixes its modal at top: 5%, so the gap under our header scaled with the viewport — 46px at 920px tall, 24px at 473px, and the panel ran 57px off the bottom of a short viewport.

Extracts the widget into src/components/search/ — markup, init script and shadow CSS together, inlined via theme.styles: [{ type: "style" }] instead of a /css/inkeep.css fetched into the shadow root. learn/top-bar.html becomes TopBar.astro so the widget can be a real component rather than a liquid partial; it had a single consumer and its domain != "docs" / show_search == "false" branches were already dead.

The offset arithmetic moves out of CSS into header-offset.ts, which measures the header and publishes --inkeep-modal-top. This matters more here than on marketing: --navigation-header-height is not defined in this repo at all — it arrives from a stylesheet on the marketing origin — and the shared-chrome header snapshot carries a live .promo-banner.

Verified against a real build of this site: with the banner up the gap is 24px, and it stays 24px once the banner is dismissed. Also measured there that a ResizeObserver does not fire when the banner is removed — only a childList mutation does — and there's a test that fails if that observer is dropped.

The offset is scoped :not([data-mobile]): below 768px the widget goes full-screen via inset: 0 at a specificity our top could not reach, while our max-height still applied and stranded a dead strip at the bottom.

Adds happy-dom and the render-astro harness so the component and its offset logic are tested rather than the CSS text.

GRO-841 · Slack thread

🤖 Generated with Claude Code

Mirrors metabase/metabase.github.io — both repos ship `/css/inkeep.css`, so
the two files must stay identical.

The Inkeep widget fixes its modal at `top: 5%` inside an overlay whose
`backdrop-filter` makes it the containing block, so our `margin-top` override
produced a gap that scaled with the viewport: 46px at 920px tall, 24px at
473px, and the panel ran 57px past the bottom of a short viewport.

Anchor `top` to `--navigation-header-height` instead so the spacing is
constant at every height, and clamp `max-height` so the results list scrolls
rather than overflowing the screen. The mobile 85px header value flows through
the same custom property.

The `.ikp-modal__overlay` padding was dead code — the content is out of flow
and positioned from the overlay's padding box, so it never contributed.

Adds vitest to guard the offset, since the repo had no test runner.

GRO-841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear

linear Bot commented Aug 25, 2026

Copy link
Copy Markdown

GRO-841

tmashara and others added 2 commits August 25, 2026 11:51
…ponent

Mirrors metabase/metabase.github.io. Markup, init script and shadow-DOM CSS now
live together in `src/components/search/`, inlined via
`theme.styles: [{ type: "style" }]` instead of a `/css/inkeep.css` fetched into
the shadow root — which also drops a render-blocking round trip, since cxkit
gates the whole widget on its linked sheets loading.

The offset arithmetic moves out of CSS into `header-offset.ts`, which measures
the header and publishes `--inkeep-modal-top`. This matters more here than on
marketing: `--navigation-header-height` is not defined in this repo at all, it
arrives from a stylesheet on the marketing origin, and the shared-chrome header
snapshot carries a live `.promo-banner`. Verified against a real build — with
the banner up the gap is 24px, and it stays 24px once the banner is dismissed.

The offset is scoped `:not([data-mobile])`. Below 768px the widget goes
full-screen through `inset: 0` at a specificity our `top` could not reach, while
our `max-height` still applied and stranded a dead strip at the bottom.

`learn/top-bar.html` becomes `TopBar.astro` so the widget can be a real
component rather than a liquid partial; it had a single consumer, and its
`domain != "docs"` and `show_search == "false"` branches were already dead.

Adds happy-dom and the render-astro harness so the component and its offset
logic are tested rather than the CSS text.

GRO-841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…server

Measured on a real build of this site: removing `.promo-banner` shrinks
`.navigation-header` from 130px to 82px, and a ResizeObserver on either that
element or the outer `header.bootstrap` does not fire at all. Only a childList
mutation does. The banner is a direct child, so no subtree is needed.

The ResizeObserver stays for the 77/85px breakpoint and font reflow.

GRO-841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tmashara
tmashara marked this pull request as draft August 25, 2026 12:32
The header is sticky at `top: -48px` so the promo banner scrolls out while the
nav row stays pinned: its bottom edge moves between 130px and 82px, and neither
observer fires for it. Measured against a real build of this site, which has a
live banner — opening the modal after scrolling put it 72px too low.

Reads through on scroll rather than scheduling a frame: the browser already caps
scroll events at one per frame, and requestAnimationFrame does not run in a
hidden tab.

GRO-841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant