@farming-labs/theme 0.2.98 → 0.2.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ import { TanstackDocsLayoutProps } from "./tanstack-layout.mjs";
2
+ import { ComponentPropsWithoutRef } from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+ import { RootProvider } from "fumadocs-ui/provider/base";
5
+
6
+ //#region src/browser.d.ts
7
+ type BrowserDocsLayoutProps = Omit<TanstackDocsLayoutProps, "browserRuntime">;
8
+ /** Docs layout for framework-neutral browser adapters such as Farm.js. */
9
+ declare function BrowserDocsLayout(props: BrowserDocsLayoutProps): react_jsx_runtime0.JSX.Element;
10
+ type FumadocsProviderProps = ComponentPropsWithoutRef<typeof RootProvider>;
11
+ declare global {
12
+ interface Window {
13
+ __fdBrowserHistoryPatched?: boolean;
14
+ }
15
+ }
16
+ interface BrowserRootProviderProps extends FumadocsProviderProps {
17
+ /** Path rendered by the server, used to keep hydration deterministic. */
18
+ initialPathname?: string;
19
+ }
20
+ /** Framework-neutral provider for server-rendered React documentation adapters. */
21
+ declare function BrowserRootProvider({
22
+ children,
23
+ search,
24
+ initialPathname,
25
+ ...props
26
+ }: BrowserRootProviderProps): react_jsx_runtime0.JSX.Element;
27
+ //#endregion
28
+ export { BrowserDocsLayout, BrowserDocsLayoutProps, BrowserRootProvider, BrowserRootProviderProps };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { TanstackDocsLayout } from "./tanstack-layout.mjs";
4
+ import { useMemo, useSyncExternalStore } from "react";
5
+ import { FrameworkProvider } from "fumadocs-core/framework";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { RootProvider } from "fumadocs-ui/provider/base";
8
+
9
+ //#region src/browser.tsx
10
+ /** Docs layout for framework-neutral browser adapters such as Farm.js. */
11
+ function BrowserDocsLayout(props) {
12
+ return /* @__PURE__ */ jsx(TanstackDocsLayout, {
13
+ ...props,
14
+ browserRuntime: true
15
+ });
16
+ }
17
+ function patchHistoryEvents() {
18
+ if (typeof window === "undefined" || window.__fdBrowserHistoryPatched) return;
19
+ for (const method of ["pushState", "replaceState"]) {
20
+ const original = window.history[method];
21
+ window.history[method] = function patchedHistoryMethod(...args) {
22
+ const result = original.apply(this, args);
23
+ window.dispatchEvent(new Event("fd-location-change"));
24
+ return result;
25
+ };
26
+ }
27
+ window.__fdBrowserHistoryPatched = true;
28
+ }
29
+ function subscribeToLocation(onStoreChange) {
30
+ if (typeof window === "undefined") return () => {};
31
+ patchHistoryEvents();
32
+ window.addEventListener("popstate", onStoreChange);
33
+ window.addEventListener("fd-location-change", onStoreChange);
34
+ return () => {
35
+ window.removeEventListener("popstate", onStoreChange);
36
+ window.removeEventListener("fd-location-change", onStoreChange);
37
+ };
38
+ }
39
+ function getBrowserPathname() {
40
+ return typeof window === "undefined" ? "/" : window.location.pathname;
41
+ }
42
+ function useBrowserRouter() {
43
+ return useMemo(() => ({
44
+ push(url) {
45
+ window.location.assign(url);
46
+ },
47
+ refresh() {
48
+ window.location.reload();
49
+ }
50
+ }), []);
51
+ }
52
+ function useBrowserParams() {
53
+ return useMemo(() => ({}), []);
54
+ }
55
+ function BrowserLink({ prefetch: _prefetch, ...props }) {
56
+ return /* @__PURE__ */ jsx("a", { ...props });
57
+ }
58
+ /** Framework-neutral provider for server-rendered React documentation adapters. */
59
+ function BrowserRootProvider({ children, search, initialPathname = "/", ...props }) {
60
+ const useBrowserPathname = () => useSyncExternalStore(subscribeToLocation, getBrowserPathname, () => initialPathname);
61
+ return /* @__PURE__ */ jsx(FrameworkProvider, {
62
+ Link: BrowserLink,
63
+ usePathname: useBrowserPathname,
64
+ useParams: useBrowserParams,
65
+ useRouter: useBrowserRouter,
66
+ children: /* @__PURE__ */ jsx(RootProvider, {
67
+ search: {
68
+ ...search,
69
+ options: {
70
+ api: "/api/docs",
71
+ ...search?.options
72
+ }
73
+ },
74
+ ...props,
75
+ children
76
+ })
77
+ });
78
+ }
79
+
80
+ //#endregion
81
+ export { BrowserDocsLayout, BrowserRootProvider };
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
4
4
  import { useEffect } from "react";
5
- import { emitDocsAnalyticsEvent, resolveDocsAnalyticsConfig } from "@farming-labs/docs";
5
+ import { emitDocsAnalyticsEvent, resolveDocsAnalyticsConfig } from "@farming-labs/docs/browser";
6
6
 
7
7
  //#region src/docs-client-hooks.tsx
8
8
  function useWindowHook(key, handler) {
@@ -256,6 +256,7 @@ function DocsCommandSearch({ api = "/api/docs", locale, analytics = false }) {
256
256
  const searchApi = useMemo(() => withLangInUrl(api, activeLocale), [activeLocale, api]);
257
257
  const inputRef = useRef(null);
258
258
  const listRef = useRef(null);
259
+ const restoreFocusRef = useRef(null);
259
260
  const searchCacheRef = useRef(/* @__PURE__ */ new Map());
260
261
  const setOpenWithAnalytics = useCallback((nextOpen, trigger) => {
261
262
  setOpen(nextOpen);
@@ -404,13 +405,23 @@ function DocsCommandSearch({ api = "/api/docs", locale, analytics = false }) {
404
405
  searchApi
405
406
  ]);
406
407
  useEffect(() => {
407
- if (open) setTimeout(() => inputRef.current?.focus(), 10);
408
- else {
408
+ if (open) {
409
+ const activeElement = document.activeElement;
410
+ restoreFocusRef.current = activeElement instanceof HTMLElement ? activeElement : null;
411
+ const focusTimer = window.setTimeout(() => inputRef.current?.focus(), 10);
412
+ return () => window.clearTimeout(focusTimer);
413
+ } else {
409
414
  setQuery("");
410
415
  setResults([]);
411
416
  setFilter("all");
412
417
  setFilterOpen(false);
413
418
  setActiveIndex(0);
419
+ const restoreTarget = restoreFocusRef.current;
420
+ restoreFocusRef.current = null;
421
+ if (restoreTarget?.isConnected) {
422
+ const focusFrame = window.requestAnimationFrame(() => restoreTarget.focus({ preventScroll: true }));
423
+ return () => window.cancelAnimationFrame(focusFrame);
424
+ }
414
425
  }
415
426
  }, [open]);
416
427
  useEffect(() => {
@@ -1,11 +1,12 @@
1
1
  import { serializeIcon } from "./serialize-icon.mjs";
2
+ import { resolveOpenDocsProviders } from "./open-docs-providers.mjs";
2
3
  import { withLangInUrl } from "./i18n.mjs";
3
4
  import { DocsPageClient } from "./docs-page-client.mjs";
4
5
  import { DocsAIFeatures } from "./docs-ai-features.mjs";
5
6
  import { resolveDocsCloudAIClientRequest } from "./docs-cloud-ai-client.mjs";
6
7
  import { DocsCommandSearch } from "./docs-command-search.mjs";
7
- import { resolveOpenDocsProviders } from "./open-docs-providers.mjs";
8
- import { resolvePageReadingTime, resolveReadingTimeOptions } from "./reading-time.mjs";
8
+ import { resolveReadingTimeOptions } from "./reading-time-options.mjs";
9
+ import { resolvePageReadingTime } from "./reading-time.mjs";
9
10
  import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
10
11
  import { LocaleThemeControl } from "./locale-theme-control.mjs";
11
12
  import fs from "node:fs";
@@ -405,7 +406,10 @@ function buildDescriptionMap(config, ctx) {
405
406
  scan(docsDir, []);
406
407
  return map;
407
408
  }
408
- /** Build titles only for pages whose MDX body does not already author an h1. */
409
+ /**
410
+ * Build titles only for pages whose MDX body does not already author an h1.
411
+ * This lets the page frame render the frontmatter title exactly once.
412
+ */
409
413
  function buildGeneratedTitleMap(config, ctx) {
410
414
  const docsDir = ctx.docsDir;
411
415
  const map = {};
@@ -17,6 +17,8 @@ interface SerializedProvider {
17
17
  }
18
18
  interface DocsPageClientProps {
19
19
  tocEnabled: boolean;
20
+ /** Expose the llms.txt action in a runtime-owned header slot. */
21
+ showLlmsInHeader?: boolean;
20
22
  tocStyle?: "default" | "directional";
21
23
  breadcrumbEnabled?: boolean;
22
24
  changelogBasePath?: string;
@@ -84,6 +86,8 @@ interface DocsPageClientProps {
84
86
  descriptionMap?: Record<string, string>;
85
87
  /** Frontmatter description to display below the page title (overrides descriptionMap) */
86
88
  description?: string;
89
+ /** The first authored paragraph already contains the frontmatter description. */
90
+ descriptionInBody?: boolean;
87
91
  /** Built-in page feedback prompt configuration */
88
92
  feedbackEnabled?: boolean;
89
93
  feedbackQuestion?: string;
@@ -100,6 +104,7 @@ interface DocsPageClientProps {
100
104
  }
101
105
  declare function DocsPageClient({
102
106
  tocEnabled,
107
+ showLlmsInHeader,
103
108
  tocStyle,
104
109
  breadcrumbEnabled,
105
110
  changelogBasePath,
@@ -139,6 +144,7 @@ declare function DocsPageClient({
139
144
  generatedTitleMap,
140
145
  descriptionMap,
141
146
  description,
147
+ descriptionInBody,
142
148
  feedbackEnabled,
143
149
  feedbackQuestion,
144
150
  feedbackPlaceholder,
@@ -255,7 +255,7 @@ function findThreadlineTocActionsContainer() {
255
255
  }
256
256
  return toc.parentElement ?? toc;
257
257
  }
258
- function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, copyMarkdownFormat, copyMarkdownIncludeTitle, copyMarkdownLabel, copyMarkdownCopiedLabel, openDocs = false, openDocsProviders, openDocsTarget, openDocsPrompt, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, readingTimeFormat = "long", previousPage, nextPage, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedLabel = "Last updated", lastUpdatedPosition = "footer", llmsTxtEnabled = false, generatedTitleMap, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackRequireComment, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackSuccessMessage, feedbackErrorMessage, feedbackOnFeedback, analytics = false, children }) {
258
+ function DocsPageClient({ tocEnabled, showLlmsInHeader = false, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, copyMarkdownFormat, copyMarkdownIncludeTitle, copyMarkdownLabel, copyMarkdownCopiedLabel, openDocs = false, openDocsProviders, openDocsTarget, openDocsPrompt, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, readingTimeFormat = "long", previousPage, nextPage, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedLabel = "Last updated", lastUpdatedPosition = "footer", llmsTxtEnabled = false, generatedTitleMap, descriptionMap, description, descriptionInBody = false, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackRequireComment, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackSuccessMessage, feedbackErrorMessage, feedbackOnFeedback, analytics = false, children }) {
259
259
  const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
260
260
  const [toc, setToc] = useState([]);
261
261
  const [titlePortalHost, setTitlePortalHost] = useState(null);
@@ -268,6 +268,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
268
268
  const activeLocale = resolveClientLocale(searchParams, locale);
269
269
  const resolvedPublicPath = normalizePublicDocsPath(publicPath, entry);
270
270
  const llmsLangQuery = activeLocale ? `?lang=${encodeURIComponent(activeLocale)}` : "";
271
+ const shouldShowLlmsInHeader = llmsTxtEnabled && showLlmsInHeader;
271
272
  const normalizedPath = (browserPathname || pathname).replace(/\/$/, "") || "/";
272
273
  const pageTitle = generatedTitleMap?.[normalizedPath];
273
274
  const pageDescription = description ?? descriptionMap?.[normalizedPath];
@@ -509,13 +510,38 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
509
510
  }
510
511
  const host = document.createElement("div");
511
512
  host.className = "fd-title-decorations-host";
513
+ const placeHost = () => {
514
+ let anchor = title;
515
+ if (descriptionInBody) {
516
+ let sibling = title.nextElementSibling;
517
+ while (sibling) {
518
+ if (sibling === host || sibling.matches(".not-prose, .fd-title-decorations-host")) {
519
+ sibling = sibling.nextElementSibling;
520
+ continue;
521
+ }
522
+ if (sibling.matches("p")) anchor = sibling;
523
+ break;
524
+ }
525
+ }
526
+ if (anchor.nextElementSibling !== host) anchor.insertAdjacentElement("afterend", host);
527
+ };
512
528
  title.insertAdjacentElement("afterend", host);
529
+ placeHost();
530
+ const observer = new MutationObserver(placeHost);
531
+ observer.observe(title.parentElement ?? title, { childList: true });
532
+ const animationFrame = window.requestAnimationFrame(placeHost);
513
533
  setTitlePortalHost(host);
514
534
  return () => {
535
+ window.cancelAnimationFrame(animationFrame);
536
+ observer.disconnect();
515
537
  host.remove();
516
538
  setTitlePortalHost(null);
517
539
  };
518
- }, [needsTitleDecorationsPortal, pathname]);
540
+ }, [
541
+ descriptionInBody,
542
+ needsTitleDecorationsPortal,
543
+ pathname
544
+ ]);
519
545
  const titleDecorations = needsTitleDecorationsPortal ? /* @__PURE__ */ jsx(TitleDecorations, {
520
546
  description: titleDescription,
521
547
  belowTitle: belowTitleBlock
@@ -560,10 +586,11 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
560
586
  llmsTxtEnabled && /* @__PURE__ */ jsx("a", {
561
587
  href: `/llms.txt${llmsLangQuery}`,
562
588
  className: "fd-agent-llms-directive",
563
- style: agentLlmsDirectiveStyle,
564
- tabIndex: -1,
565
- "aria-hidden": "true",
566
- children: "llms.txt"
589
+ "data-visible-in-header": shouldShowLlmsInHeader ? "true" : void 0,
590
+ style: shouldShowLlmsInHeader ? void 0 : agentLlmsDirectiveStyle,
591
+ tabIndex: shouldShowLlmsInHeader ? void 0 : -1,
592
+ "aria-hidden": shouldShowLlmsInHeader ? void 0 : true,
593
+ children: shouldShowLlmsInHeader ? "LLMS.TXT" : "llms.txt"
567
594
  }, "llms-txt"),
568
595
  titleControlsPortal,
569
596
  tocActionsPortal,
package/dist/mdx.mjs CHANGED
@@ -5,7 +5,7 @@ import { HoverLink } from "./hover-link.mjs";
5
5
  import { extractPromptText } from "./prompt-text.mjs";
6
6
  import { Prompt } from "./prompt.mjs";
7
7
  import React from "react";
8
- import { resolveDocsAudienceExposure } from "@farming-labs/docs";
8
+ import { resolveDocsAudienceExposure } from "@farming-labs/docs/browser";
9
9
  import { Tab, Tabs } from "fumadocs-ui/components/tabs";
10
10
  import { CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger } from "fumadocs-ui/components/codeblock";
11
11
  import defaultMdxComponents from "fumadocs-ui/mdx";
@@ -0,0 +1,14 @@
1
+ //#region src/open-docs-provider-icons.ts
2
+ /** Monochrome provider marks used by the built-in Open in… presets. */
3
+ const OPEN_DOCS_PROVIDER_ICONS = {
4
+ chatgpt: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z\"/></svg>",
5
+ claude: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z\"/></svg>",
6
+ cursor: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23\"/></svg>",
7
+ gemini: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"/></svg>",
8
+ copilot: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z\"/></svg>",
9
+ perplexity: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z\"/></svg>",
10
+ github: "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"/></svg>"
11
+ };
12
+
13
+ //#endregion
14
+ export { OPEN_DOCS_PROVIDER_ICONS };
@@ -1,3 +1,5 @@
1
+ import { OPEN_DOCS_PROVIDER_ICONS } from "./open-docs-provider-icons.mjs";
2
+
1
3
  //#region src/open-docs-providers.ts
2
4
  const PROMPT_PROVIDER_TEMPLATES = {
3
5
  chatgpt: "https://chatgpt.com/?q={prompt}",
@@ -10,36 +12,43 @@ const PROMPT_PROVIDER_TEMPLATES = {
10
12
  const OPEN_DOCS_PROVIDER_PRESETS = {
11
13
  chatgpt: {
12
14
  name: "ChatGPT",
15
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.chatgpt,
13
16
  urlTemplate: "https://chatgpt.com/?q={prompt}",
14
17
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.chatgpt
15
18
  },
16
19
  claude: {
17
20
  name: "Claude",
21
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.claude,
18
22
  urlTemplate: "https://claude.ai/new?q={prompt}",
19
23
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.claude
20
24
  },
21
25
  cursor: {
22
26
  name: "Cursor",
27
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.cursor,
23
28
  urlTemplate: "https://cursor.com/link/prompt?text={prompt}",
24
29
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.cursor
25
30
  },
26
31
  gemini: {
27
32
  name: "Gemini",
33
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.gemini,
28
34
  urlTemplate: "https://gemini.google.com/app?q={prompt}",
29
35
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.gemini
30
36
  },
31
37
  copilot: {
32
38
  name: "Copilot",
39
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.copilot,
33
40
  urlTemplate: "https://github.com/copilot?prompt={prompt}",
34
41
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.copilot
35
42
  },
36
43
  perplexity: {
37
44
  name: "Perplexity",
45
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.perplexity,
38
46
  urlTemplate: "https://www.perplexity.ai/search/?q={prompt}",
39
47
  promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.perplexity
40
48
  },
41
49
  github: {
42
50
  name: "GitHub",
51
+ iconHtml: OPEN_DOCS_PROVIDER_ICONS.github,
43
52
  urlTemplate: "{githubUrl}",
44
53
  promptUrlTemplate: "{githubUrl}",
45
54
  target: "github"
@@ -48,6 +57,11 @@ const OPEN_DOCS_PROVIDER_PRESETS = {
48
57
  function normalizeProviderName(name) {
49
58
  return name.trim().toLowerCase();
50
59
  }
60
+ function resolveOpenDocsProviderIcon(name) {
61
+ if (!name) return void 0;
62
+ const normalizedName = normalizeProviderName(name);
63
+ return OPEN_DOCS_PROVIDER_PRESETS[normalizedName === "github copilot" ? "copilot" : normalizedName]?.iconHtml;
64
+ }
51
65
  function resolveOpenDocsProviders(providers, options = {}) {
52
66
  if (!providers || providers.length === 0) return void 0;
53
67
  const serialized = providers.map((provider) => resolveOpenDocsProvider(provider, options)).filter((provider) => provider !== void 0);
@@ -60,6 +74,7 @@ function resolveOpenDocsProvider(provider, options = {}) {
60
74
  if (!preset) return void 0;
61
75
  return {
62
76
  name: preset.name,
77
+ iconHtml: preset.iconHtml,
63
78
  urlTemplate: preset.urlTemplate,
64
79
  promptUrlTemplate: preset.promptUrlTemplate,
65
80
  target: preset.target ?? options.target,
@@ -75,11 +90,11 @@ function resolveOpenDocsProvider(provider, options = {}) {
75
90
  name,
76
91
  urlTemplate,
77
92
  promptUrlTemplate: provider.promptUrlTemplate ?? cursorAppTemplate ?? preset?.promptUrlTemplate,
78
- iconHtml: options.serializeIcon?.(provider.icon) ?? (typeof provider.icon === "string" ? provider.icon : void 0),
93
+ iconHtml: options.serializeIcon?.(provider.icon) ?? (typeof provider.icon === "string" ? provider.icon : void 0) ?? preset?.iconHtml,
79
94
  target: provider.target ?? preset?.target ?? options.target ?? (hasCustomUrlTemplate ? "page" : void 0),
80
95
  prompt: provider.prompt ?? options.prompt
81
96
  };
82
97
  }
83
98
 
84
99
  //#endregion
85
- export { resolveOpenDocsProviders };
100
+ export { resolveOpenDocsProviderIcon, resolveOpenDocsProviders };
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
4
+ import { resolveOpenDocsProviderIcon, resolveOpenDocsProviders } from "./open-docs-providers.mjs";
4
5
  import { sanitizeIconHtml } from "./safe-icon-html.mjs";
5
6
  import { useCallback, useEffect, useRef, useState } from "react";
6
7
  import { usePathname } from "fumadocs-core/framework";
@@ -107,13 +108,7 @@ const SparklesIcon = () => /* @__PURE__ */ jsxs("svg", {
107
108
  /* @__PURE__ */ jsx("path", { d: "M19 11.5 20 15l3.5 1-3.5 1-1 3.5-1-3.5-3.5-1 3.5-1Z" })
108
109
  ]
109
110
  });
110
- const DEFAULT_PROVIDERS = [{
111
- name: "ChatGPT",
112
- urlTemplate: "https://chatgpt.com/?q={prompt}"
113
- }, {
114
- name: "Claude",
115
- urlTemplate: "https://claude.ai/new?q={prompt}"
116
- }];
111
+ const DEFAULT_PROVIDERS = resolveOpenDocsProviders(["chatgpt", "claude"]) ?? [];
117
112
  const DEFAULT_OPEN_DOCS_TARGET = "markdown";
118
113
  const DEFAULT_OPEN_DOCS_PROMPT = "Read this documentation: {url}";
119
114
  function pageUrlToMarkdownUrl(pageUrl) {
@@ -328,7 +323,7 @@ function PageActions({ copyMarkdown, copyMarkdownFormat = "markdown", copyMarkdo
328
323
  className: "fd-page-action-menu",
329
324
  role: "menu",
330
325
  children: resolvedProviders.map((provider) => {
331
- const iconHtml = sanitizeIconHtml(provider.iconHtml);
326
+ const iconHtml = sanitizeIconHtml(provider.iconHtml ?? resolveOpenDocsProviderIcon(provider.name));
332
327
  return /* @__PURE__ */ jsxs("button", {
333
328
  type: "button",
334
329
  role: "menuitem",
@@ -62,6 +62,7 @@ declare const PixelBorderUIDefaults: {
62
62
  toc: {
63
63
  enabled: boolean;
64
64
  depth: number;
65
+ style: "directional";
65
66
  };
66
67
  header: {
67
68
  height: number;
@@ -65,7 +65,8 @@ const PixelBorderUIDefaults = {
65
65
  sidebarWidth: 286,
66
66
  toc: {
67
67
  enabled: true,
68
- depth: 3
68
+ depth: 3,
69
+ style: "directional"
69
70
  },
70
71
  header: {
71
72
  height: 56,
@@ -0,0 +1,27 @@
1
+ //#region src/reading-time-options.ts
2
+ function resolveReadingTimeOptions(readingTime) {
3
+ if (readingTime === true) return {
4
+ enabled: true,
5
+ format: "long",
6
+ includeCode: false
7
+ };
8
+ if (readingTime === false || readingTime === void 0 || readingTime === null) return {
9
+ enabled: false,
10
+ format: "long",
11
+ includeCode: false
12
+ };
13
+ if (typeof readingTime !== "object") return {
14
+ enabled: false,
15
+ format: "long",
16
+ includeCode: false
17
+ };
18
+ return {
19
+ enabled: readingTime.enabled !== false,
20
+ wordsPerMinute: typeof readingTime.wordsPerMinute === "number" && Number.isFinite(readingTime.wordsPerMinute) ? readingTime.wordsPerMinute : void 0,
21
+ format: readingTime.format === "short" ? "short" : "long",
22
+ includeCode: readingTime.includeCode === true
23
+ };
24
+ }
25
+
26
+ //#endregion
27
+ export { resolveReadingTimeOptions };
@@ -1,3 +1,4 @@
1
+ import { resolveReadingTimeOptions } from "./reading-time-options.mjs";
1
2
  import matter from "gray-matter";
2
3
 
3
4
  //#region src/reading-time.ts
@@ -16,29 +17,6 @@ function estimateReadingTimeMinutes(content, wordsPerMinute, options) {
16
17
  const wordCount = stripNonReadingContent(content, options).match(/\b[\p{L}\p{N}][\p{L}\p{N}'’-]*\b/gu)?.length ?? 0;
17
18
  return Math.max(1, Math.ceil(wordCount / normalizeWordsPerMinute(wordsPerMinute)));
18
19
  }
19
- function resolveReadingTimeOptions(readingTime) {
20
- if (readingTime === true) return {
21
- enabled: true,
22
- format: "long",
23
- includeCode: false
24
- };
25
- if (readingTime === false || readingTime === void 0 || readingTime === null) return {
26
- enabled: false,
27
- format: "long",
28
- includeCode: false
29
- };
30
- if (typeof readingTime !== "object") return {
31
- enabled: false,
32
- format: "long",
33
- includeCode: false
34
- };
35
- return {
36
- enabled: readingTime.enabled !== false,
37
- wordsPerMinute: typeof readingTime.wordsPerMinute === "number" && Number.isFinite(readingTime.wordsPerMinute) ? readingTime.wordsPerMinute : void 0,
38
- format: readingTime.format === "short" ? "short" : "long",
39
- includeCode: readingTime.includeCode === true
40
- };
41
- }
42
20
  function resolveReadingTimeFromContent(frontmatter, content, wordsPerMinute, options) {
43
21
  const pageData = frontmatter ?? {};
44
22
  if (pageData.readingTime === false) return null;
@@ -51,4 +29,4 @@ function resolvePageReadingTime(frontmatter, content, options) {
51
29
  }
52
30
 
53
31
  //#endregion
54
- export { resolvePageReadingTime, resolveReadingTimeOptions };
32
+ export { resolvePageReadingTime };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { useSidebar } from "fumadocs-ui/components/sidebar/base";
6
+
7
+ //#region src/tablet-sidebar-bridge.tsx
8
+ const tabletSidebarQuery = "(min-width: 768px) and (max-width: 1023px)";
9
+ /**
10
+ * Extends Fumadocs' sidebar state through the tablet breakpoint without
11
+ * intercepting its trigger or cloning the configured navigation tree.
12
+ */
13
+ function TabletSidebarBridge() {
14
+ const { open, setOpen } = useSidebar();
15
+ const [isTablet, setIsTablet] = useState(false);
16
+ const closeButtonRef = useRef(null);
17
+ const returnFocusRef = useRef(null);
18
+ const visible = isTablet && open;
19
+ useEffect(() => {
20
+ const media = window.matchMedia(tabletSidebarQuery);
21
+ const update = () => setIsTablet(media.matches);
22
+ update();
23
+ media.addEventListener("change", update);
24
+ return () => media.removeEventListener("change", update);
25
+ }, []);
26
+ useEffect(() => {
27
+ if (!visible) return;
28
+ returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
29
+ closeButtonRef.current?.focus();
30
+ const closeOnEscape = (event) => {
31
+ if (event.key !== "Escape") return;
32
+ event.preventDefault();
33
+ setOpen(false);
34
+ };
35
+ window.addEventListener("keydown", closeOnEscape);
36
+ return () => {
37
+ window.removeEventListener("keydown", closeOnEscape);
38
+ returnFocusRef.current?.focus();
39
+ returnFocusRef.current = null;
40
+ };
41
+ }, [setOpen, visible]);
42
+ if (!isTablet) return null;
43
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
44
+ hidden: true,
45
+ "aria-hidden": "true",
46
+ "data-fd-tablet-sidebar-state": visible ? "open" : "closed"
47
+ }), visible && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
48
+ type: "button",
49
+ tabIndex: -1,
50
+ "aria-hidden": "true",
51
+ className: "fd-tablet-sidebar-backdrop",
52
+ onClick: () => setOpen(false)
53
+ }), /* @__PURE__ */ jsx("button", {
54
+ ref: closeButtonRef,
55
+ type: "button",
56
+ "aria-label": "Close Sidebar",
57
+ className: "fd-tablet-sidebar-close",
58
+ onClick: () => setOpen(false),
59
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
60
+ })] })] });
61
+ }
62
+
63
+ //#endregion
64
+ export { TabletSidebarBridge };
@@ -27,8 +27,11 @@ interface TreeRoot {
27
27
  interface TanstackDocsLayoutProps {
28
28
  config: DocsConfig;
29
29
  tree: TreeRoot;
30
+ /** Enables browser-adapter shell affordances that are not part of a theme preset. */
31
+ browserRuntime?: boolean;
30
32
  locale?: string;
31
33
  description?: string;
34
+ descriptionInBody?: boolean;
32
35
  readingTime?: number | null;
33
36
  lastModified?: string;
34
37
  previousPage?: {
@@ -46,8 +49,10 @@ interface TanstackDocsLayoutProps {
46
49
  declare function TanstackDocsLayout({
47
50
  config,
48
51
  tree,
52
+ browserRuntime,
49
53
  locale,
50
54
  description,
55
+ descriptionInBody,
51
56
  readingTime,
52
57
  lastModified,
53
58
  previousPage,