fix(docs): restore search overlay behavior (#134)

This commit is contained in:
Luca Martial 2026-05-18 10:50:34 -04:00 committed by GitHub
parent 6a60977eb5
commit 611f830fe0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 85 additions and 4 deletions

View file

@ -0,0 +1,4 @@
import { source } from "@/lib/source";
import { createFromSource } from "fumadocs-core/search/server";
export const { GET } = createFromSource(source);

View file

@ -69,7 +69,11 @@
--color-fd-muted-foreground: #7a8d96;
}
html, body {
/* Keep html overflow at the default `visible` so body's overflow
propagates to the viewport (per CSS Overflow spec). That lets
`react-remove-scroll-bar` lock viewport scroll via body alone while
leaving the sticky sidebar placeholder anchored to the viewport. */
body {
overflow-x: clip;
}
@ -778,8 +782,8 @@ body::after {
mix-blend-mode: overlay;
}
/* Make sure content stays above background */
body > * {
/* Make sure page content stays above the decorative background. */
.ktx-site-shell {
position: relative;
z-index: 2;
}

View file

@ -41,7 +41,9 @@ export default function RootLayout({ children }: { children: ReactNode }) {
suppressHydrationWarning
>
<body>
<RootProvider>{children}</RootProvider>
<RootProvider search={{ options: { api: "/ktx/api/search" } }}>
<div className="ktx-site-shell">{children}</div>
</RootProvider>
</body>
</html>
);

View file

@ -111,3 +111,21 @@ test("/ktx/docs redirects to the docs introduction", async () => {
`${docsBasePath}/docs/getting-started/introduction`,
);
});
test("/ktx/api/search returns docs search results", async () => {
const response = await fetch(
`${docsSiteUrl}${docsBasePath}/api/search?query=setup`,
);
assert.equal(response.status, 200);
const results = await response.json();
assert.ok(Array.isArray(results), "search response should be an array");
assert.ok(
results.some(
(result) =>
typeof result.url === "string" && result.url.startsWith("/docs/"),
),
"search should return at least one docs result",
);
});

View file

@ -0,0 +1,53 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
const docsSiteDir = join(dirname(fileURLToPath(import.meta.url)), "..");
async function readDocsFile(path) {
return readFile(join(docsSiteDir, path), "utf8");
}
test("root provider uses the base-path-aware search API", async () => {
const layout = await readDocsFile("app/layout.tsx");
assert.match(layout, /search=\{\{/);
assert.match(layout, /api:\s*"\/ktx\/api\/search"/);
});
test("site background stacking does not target every body child", async () => {
const css = await readDocsFile("app/global.css");
assert.doesNotMatch(css, /body\s*>\s*\*\s*\{[^}]*z-index/s);
assert.match(css, /\.ktx-site-shell\s*\{[^}]*z-index:\s*2/s);
});
test("search lock relies on body overflow propagation, not html or sidebar overrides", async () => {
const css = await readDocsFile("app/global.css");
// Body still clips horizontal overflow defensively.
assert.match(css, /(^|\s)body\s*\{[^}]*overflow-x:\s*clip/s);
// html must keep its default `visible` overflow so body's lock
// (`overflow: hidden` from react-remove-scroll-bar) propagates to the
// viewport. Locking html directly breaks `position: sticky` on the
// sidebar placeholder.
assert.doesNotMatch(css, /(^|\s)html\s*,?\s*\{[^}]*overflow(-y|\s*:)\s*(hidden|clip)/s);
assert.doesNotMatch(
css,
/html:has\(body\[data-scroll-locked\]\)[^{]*\{[^}]*overflow:\s*(hidden|clip)/s,
);
// No site-specific overrides to body's data-scroll-locked overflow or
// to the sidebar placeholder when locked.
assert.doesNotMatch(
css,
/html\s+body\[data-scroll-locked\][^{]*\{[^}]*overflow:/s,
);
assert.doesNotMatch(
css,
/body\[data-scroll-locked\]\s+\[data-sidebar-placeholder\][^{]*\{[^}]*position:\s*fixed/s,
);
});