@farming-labs/theme 0.0.28 → 0.0.30
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.
- package/dist/ai-search-dialog.mjs +3 -1
- package/dist/code-block-copy-wrapper.d.mts +1 -1
- package/dist/docs-ai-features.d.mts +4 -0
- package/dist/docs-ai-features.mjs +12 -7
- package/dist/docs-api.d.mts +4 -19
- package/dist/docs-api.mjs +82 -16
- package/dist/docs-command-search.d.mts +7 -1
- package/dist/docs-command-search.mjs +14 -4
- package/dist/docs-layout.d.mts +4 -2
- package/dist/docs-layout.mjs +139 -51
- package/dist/docs-page-client.d.mts +3 -0
- package/dist/docs-page-client.mjs +55 -17
- package/dist/i18n.d.mts +4 -0
- package/dist/i18n.mjs +20 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/locale-theme-control.mjs +286 -0
- package/dist/mdx.d.mts +1 -1
- package/package.json +2 -2
|
@@ -603,7 +603,9 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
603
603
|
setIsSearching(true);
|
|
604
604
|
const timer = setTimeout(async () => {
|
|
605
605
|
try {
|
|
606
|
-
const
|
|
606
|
+
const requestUrl = new URL(api, window.location.origin);
|
|
607
|
+
requestUrl.searchParams.set("query", searchQuery);
|
|
608
|
+
const res = await fetch(requestUrl.toString());
|
|
607
609
|
if (res.ok) {
|
|
608
610
|
setSearchResults(await res.json());
|
|
609
611
|
setActiveIndex(0);
|
|
@@ -3,6 +3,8 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
3
3
|
//#region src/docs-ai-features.d.ts
|
|
4
4
|
interface DocsAIFeaturesProps {
|
|
5
5
|
mode: "search" | "floating" | "sidebar-icon";
|
|
6
|
+
api?: string;
|
|
7
|
+
locale?: string;
|
|
6
8
|
position?: "bottom-right" | "bottom-left" | "bottom-center";
|
|
7
9
|
floatingStyle?: "panel" | "modal" | "popover" | "full-modal";
|
|
8
10
|
triggerComponentHtml?: string;
|
|
@@ -18,6 +20,8 @@ interface DocsAIFeaturesProps {
|
|
|
18
20
|
}
|
|
19
21
|
declare function DocsAIFeatures({
|
|
20
22
|
mode,
|
|
23
|
+
api,
|
|
24
|
+
locale,
|
|
21
25
|
position,
|
|
22
26
|
floatingStyle,
|
|
23
27
|
triggerComponentHtml,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
3
4
|
import { AIModalDialog, DocsSearchDialog, FloatingAIChat } from "./ai-search-dialog.mjs";
|
|
4
5
|
import { useEffect, useState } from "react";
|
|
6
|
+
import { useSearchParams } from "next/navigation";
|
|
5
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
|
|
7
9
|
//#region src/docs-ai-features.tsx
|
|
@@ -19,8 +21,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
19
21
|
* This component is rendered inside the docs layout so the user's root layout
|
|
20
22
|
* never needs to be modified — AI features work purely from `docs.config.ts`.
|
|
21
23
|
*/
|
|
22
|
-
function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
24
|
+
function DocsAIFeatures({ mode, api = "/api/docs", locale, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
25
|
+
const localizedApi = withLangInUrl(api, resolveClientLocale(useSearchParams(), locale));
|
|
23
26
|
if (mode === "search") return /* @__PURE__ */ jsx(SearchModeAI, {
|
|
27
|
+
api: localizedApi,
|
|
24
28
|
suggestedQuestions,
|
|
25
29
|
aiLabel,
|
|
26
30
|
loaderVariant,
|
|
@@ -29,6 +33,7 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
29
33
|
defaultModelId
|
|
30
34
|
});
|
|
31
35
|
if (mode === "sidebar-icon") return /* @__PURE__ */ jsx(SidebarIconModeAI, {
|
|
36
|
+
api: localizedApi,
|
|
32
37
|
suggestedQuestions,
|
|
33
38
|
aiLabel,
|
|
34
39
|
loaderVariant,
|
|
@@ -37,7 +42,7 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
37
42
|
defaultModelId
|
|
38
43
|
});
|
|
39
44
|
return /* @__PURE__ */ jsx(FloatingAIChat, {
|
|
40
|
-
api:
|
|
45
|
+
api: localizedApi,
|
|
41
46
|
position,
|
|
42
47
|
floatingStyle,
|
|
43
48
|
triggerComponentHtml,
|
|
@@ -49,7 +54,7 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
49
54
|
defaultModelId
|
|
50
55
|
});
|
|
51
56
|
}
|
|
52
|
-
function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
57
|
+
function SearchModeAI({ api, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
53
58
|
const [open, setOpen] = useState(false);
|
|
54
59
|
useEffect(() => {
|
|
55
60
|
function handler(e) {
|
|
@@ -81,7 +86,7 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingCompo
|
|
|
81
86
|
return /* @__PURE__ */ jsx(DocsSearchDialog, {
|
|
82
87
|
open,
|
|
83
88
|
onOpenChange: setOpen,
|
|
84
|
-
api
|
|
89
|
+
api,
|
|
85
90
|
suggestedQuestions,
|
|
86
91
|
aiLabel,
|
|
87
92
|
loaderVariant,
|
|
@@ -90,7 +95,7 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingCompo
|
|
|
90
95
|
defaultModelId
|
|
91
96
|
});
|
|
92
97
|
}
|
|
93
|
-
function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
98
|
+
function SidebarIconModeAI({ api, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
94
99
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
95
100
|
const [aiOpen, setAiOpen] = useState(false);
|
|
96
101
|
useEffect(() => {
|
|
@@ -122,7 +127,7 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loading
|
|
|
122
127
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DocsSearchDialog, {
|
|
123
128
|
open: searchOpen,
|
|
124
129
|
onOpenChange: setSearchOpen,
|
|
125
|
-
api
|
|
130
|
+
api,
|
|
126
131
|
suggestedQuestions,
|
|
127
132
|
aiLabel,
|
|
128
133
|
loaderVariant,
|
|
@@ -132,7 +137,7 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loading
|
|
|
132
137
|
}), /* @__PURE__ */ jsx(AIModalDialog, {
|
|
133
138
|
open: aiOpen,
|
|
134
139
|
onOpenChange: setAiOpen,
|
|
135
|
-
api
|
|
140
|
+
api,
|
|
136
141
|
suggestedQuestions,
|
|
137
142
|
aiLabel,
|
|
138
143
|
loaderVariant,
|
package/dist/docs-api.d.mts
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
|
+
import { DocsI18nConfig } from "@farming-labs/docs";
|
|
2
|
+
|
|
1
3
|
//#region src/docs-api.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Unified docs API handler for @farming-labs/theme.
|
|
4
|
-
*
|
|
5
|
-
* A single route handler that serves **both** search and AI chat:
|
|
6
|
-
*
|
|
7
|
-
* - `GET /api/docs?query=…` → full-text search over indexed MDX pages
|
|
8
|
-
* - `POST /api/docs` → RAG-powered "Ask AI" (searches relevant docs,
|
|
9
|
-
* then streams an LLM response using the docs as context)
|
|
10
|
-
*
|
|
11
|
-
* This replaces the old `createDocsSearchAPI` — one handler, one route.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
16
|
-
* import { createDocsAPI } from "@farming-labs/theme/api";
|
|
17
|
-
* export const { GET, POST } = createDocsAPI();
|
|
18
|
-
* export const revalidate = false;
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
4
|
interface AIProviderConfig {
|
|
22
5
|
baseUrl: string;
|
|
23
6
|
apiKey?: string;
|
|
@@ -49,6 +32,8 @@ interface DocsAPIOptions {
|
|
|
49
32
|
language?: string;
|
|
50
33
|
/** AI chat configuration */
|
|
51
34
|
ai?: AIOptions;
|
|
35
|
+
/** i18n config (optional) */
|
|
36
|
+
i18n?: DocsI18nConfig;
|
|
52
37
|
}
|
|
53
38
|
/**
|
|
54
39
|
* Create a unified docs API route handler.
|
package/dist/docs-api.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getNextAppDir } from "./get-app-dir.mjs";
|
|
2
|
+
import { withLangInUrl } from "./i18n.mjs";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import matter from "gray-matter";
|
|
6
|
+
import { resolveDocsI18n, resolveDocsLocale } from "@farming-labs/docs";
|
|
5
7
|
import { createSearchAPI } from "fumadocs-core/search/server";
|
|
6
8
|
|
|
7
9
|
//#region src/docs-api.ts
|
|
@@ -40,6 +42,25 @@ function readEntry(root) {
|
|
|
40
42
|
}
|
|
41
43
|
return "docs";
|
|
42
44
|
}
|
|
45
|
+
function readI18nConfig(root) {
|
|
46
|
+
for (const ext of FILE_EXTS) {
|
|
47
|
+
const configPath = path.join(root, `docs.config.${ext}`);
|
|
48
|
+
if (!fs.existsSync(configPath)) continue;
|
|
49
|
+
try {
|
|
50
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
51
|
+
if (!content.includes("i18n")) continue;
|
|
52
|
+
const localesMatch = content.match(/i18n\s*:\s*\{[\s\S]*?locales\s*:\s*\[([^\]]+)\]/);
|
|
53
|
+
if (!localesMatch) continue;
|
|
54
|
+
const locales = localesMatch[1].split(",").map((l) => l.trim().replace(/^['\"`]|['\"`]$/g, "")).filter(Boolean);
|
|
55
|
+
if (locales.length === 0) continue;
|
|
56
|
+
return {
|
|
57
|
+
locales,
|
|
58
|
+
defaultLocale: content.match(/i18n\s*:\s*\{[\s\S]*?defaultLocale\s*:\s*["']([^"']+)["']/)?.[1]
|
|
59
|
+
};
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
43
64
|
/**
|
|
44
65
|
* Read AI config from docs.config by parsing the file for the `ai` block.
|
|
45
66
|
* This avoids importing the config (which may use JSX/React).
|
|
@@ -73,7 +94,7 @@ function stripMdx(raw) {
|
|
|
73
94
|
const { content } = matter(raw);
|
|
74
95
|
return content.replace(/^(import|export)\s.*$/gm, "").replace(/<[^>]+\/>/g, "").replace(/<\/?[A-Z][^>]*>/g, "").replace(/<\/?[a-z][^>]*>/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
75
96
|
}
|
|
76
|
-
function scanDocsDir(docsDir, entry) {
|
|
97
|
+
function scanDocsDir(docsDir, entry, locale) {
|
|
77
98
|
const indexes = [];
|
|
78
99
|
function scan(dir, slugParts) {
|
|
79
100
|
if (!fs.existsSync(dir)) return;
|
|
@@ -84,7 +105,7 @@ function scanDocsDir(docsDir, entry) {
|
|
|
84
105
|
const title = data.title || slugParts[slugParts.length - 1]?.replace(/-/g, " ") || "Documentation";
|
|
85
106
|
const description = data.description;
|
|
86
107
|
const content = stripMdx(raw);
|
|
87
|
-
const url = slugParts.length === 0 ? `/${entry}` : `/${entry}/${slugParts.join("/")}
|
|
108
|
+
const url = withLangInUrl(slugParts.length === 0 ? `/${entry}` : `/${entry}/${slugParts.join("/")}`, locale);
|
|
88
109
|
indexes.push({
|
|
89
110
|
title,
|
|
90
111
|
description,
|
|
@@ -250,40 +271,85 @@ function createDocsAPI(options) {
|
|
|
250
271
|
const root = process.cwd();
|
|
251
272
|
const entry = options?.entry ?? readEntry(root);
|
|
252
273
|
const appDir = getNextAppDir(root);
|
|
253
|
-
const docsDir = path.join(root, appDir, entry);
|
|
254
274
|
const language = options?.language ?? "english";
|
|
275
|
+
const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
|
|
255
276
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
256
277
|
const llmsConfig = readLlmsTxtConfig(root);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (
|
|
278
|
+
function resolveLocaleFromRequest(request) {
|
|
279
|
+
if (!i18n) return void 0;
|
|
280
|
+
const direct = resolveDocsLocale(new URL(request.url).searchParams, i18n);
|
|
281
|
+
if (direct) return direct;
|
|
282
|
+
const referrer = request.headers.get("referer") ?? request.headers.get("referrer");
|
|
283
|
+
if (referrer) try {
|
|
284
|
+
const fromRef = resolveDocsLocale(new URL(referrer).searchParams, i18n);
|
|
285
|
+
if (fromRef) return fromRef;
|
|
286
|
+
} catch {}
|
|
287
|
+
return i18n.defaultLocale;
|
|
288
|
+
}
|
|
289
|
+
function resolveContextFromRequest(request) {
|
|
290
|
+
if (!i18n) return {
|
|
291
|
+
entryPath: entry,
|
|
292
|
+
docsDir: path.join(root, appDir, entry)
|
|
293
|
+
};
|
|
294
|
+
const locale = resolveLocaleFromRequest(request) ?? i18n.defaultLocale;
|
|
295
|
+
return {
|
|
296
|
+
entryPath: entry,
|
|
297
|
+
locale,
|
|
298
|
+
docsDir: path.join(root, appDir, entry, locale)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const indexesByLocale = /* @__PURE__ */ new Map();
|
|
302
|
+
const searchApiByLocale = /* @__PURE__ */ new Map();
|
|
303
|
+
const llmsCacheByLocale = /* @__PURE__ */ new Map();
|
|
304
|
+
function getIndexes(ctx) {
|
|
305
|
+
const key = ctx.locale ?? "__default__";
|
|
306
|
+
const cached = indexesByLocale.get(key);
|
|
307
|
+
if (cached) return cached;
|
|
308
|
+
const next = scanDocsDir(ctx.docsDir, ctx.entryPath, ctx.locale);
|
|
309
|
+
indexesByLocale.set(key, next);
|
|
310
|
+
return next;
|
|
311
|
+
}
|
|
312
|
+
function getSearchAPI(ctx) {
|
|
313
|
+
const key = ctx.locale ?? "__default__";
|
|
314
|
+
const cached = searchApiByLocale.get(key);
|
|
315
|
+
if (cached) return cached;
|
|
316
|
+
const api = createSearchAPI("simple", {
|
|
317
|
+
language,
|
|
318
|
+
indexes: getIndexes(ctx)
|
|
319
|
+
});
|
|
320
|
+
searchApiByLocale.set(key, api);
|
|
321
|
+
return api;
|
|
322
|
+
}
|
|
323
|
+
function getLlmsContent(ctx) {
|
|
324
|
+
const key = ctx.locale ?? "__default__";
|
|
325
|
+
const cached = llmsCacheByLocale.get(key);
|
|
326
|
+
if (cached) return cached;
|
|
327
|
+
const next = generateLlmsTxt(getIndexes(ctx), {
|
|
261
328
|
siteTitle: llmsConfig.siteTitle ?? "Documentation",
|
|
262
329
|
siteDescription: llmsConfig.siteDescription,
|
|
263
330
|
baseUrl: llmsConfig.baseUrl ?? ""
|
|
264
331
|
});
|
|
265
|
-
|
|
332
|
+
llmsCacheByLocale.set(key, next);
|
|
333
|
+
return next;
|
|
266
334
|
}
|
|
267
|
-
const searchAPI = createSearchAPI("simple", {
|
|
268
|
-
language,
|
|
269
|
-
indexes
|
|
270
|
-
});
|
|
271
335
|
return {
|
|
272
336
|
GET(request) {
|
|
337
|
+
const ctx = resolveContextFromRequest(request);
|
|
273
338
|
const format = new URL(request.url).searchParams.get("format");
|
|
274
|
-
if (format === "llms") return new Response(getLlmsContent().llmsTxt, { headers: {
|
|
339
|
+
if (format === "llms") return new Response(getLlmsContent(ctx).llmsTxt, { headers: {
|
|
275
340
|
"Content-Type": "text/plain; charset=utf-8",
|
|
276
341
|
"Cache-Control": "public, max-age=3600"
|
|
277
342
|
} });
|
|
278
|
-
if (format === "llms-full") return new Response(getLlmsContent().llmsFullTxt, { headers: {
|
|
343
|
+
if (format === "llms-full") return new Response(getLlmsContent(ctx).llmsFullTxt, { headers: {
|
|
279
344
|
"Content-Type": "text/plain; charset=utf-8",
|
|
280
345
|
"Cache-Control": "public, max-age=3600"
|
|
281
346
|
} });
|
|
282
|
-
return
|
|
347
|
+
return getSearchAPI(ctx).GET(request);
|
|
283
348
|
},
|
|
284
349
|
async POST(request) {
|
|
285
350
|
if (!aiConfig.enabled) return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
|
|
286
|
-
|
|
351
|
+
const ctx = resolveContextFromRequest(request);
|
|
352
|
+
return handleAskAI(request, getIndexes(ctx), getSearchAPI(ctx), aiConfig);
|
|
287
353
|
}
|
|
288
354
|
};
|
|
289
355
|
}
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* fuzzy-search experience. Styled entirely via omni-* CSS classes
|
|
6
6
|
* so each theme provides its own visual variant.
|
|
7
7
|
*/
|
|
8
|
-
declare function DocsCommandSearch(
|
|
8
|
+
declare function DocsCommandSearch({
|
|
9
|
+
api,
|
|
10
|
+
locale
|
|
11
|
+
}: {
|
|
12
|
+
api?: string;
|
|
13
|
+
locale?: string;
|
|
14
|
+
}): any;
|
|
9
15
|
//#endregion
|
|
10
16
|
export { DocsCommandSearch };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
3
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
5
|
import { createPortal } from "react-dom";
|
|
6
|
+
import { useSearchParams } from "next/navigation";
|
|
5
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
|
|
7
9
|
//#region src/docs-command-search.tsx
|
|
@@ -297,7 +299,7 @@ function labelForType(type) {
|
|
|
297
299
|
* fuzzy-search experience. Styled entirely via omni-* CSS classes
|
|
298
300
|
* so each theme provides its own visual variant.
|
|
299
301
|
*/
|
|
300
|
-
function DocsCommandSearch() {
|
|
302
|
+
function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
301
303
|
const [open, setOpen] = useState(false);
|
|
302
304
|
const [query, setQuery] = useState("");
|
|
303
305
|
const [debouncedQuery, setDebouncedQuery] = useState("");
|
|
@@ -306,6 +308,8 @@ function DocsCommandSearch() {
|
|
|
306
308
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
307
309
|
const [recents, setRecents] = useState([]);
|
|
308
310
|
const [mounted, setMounted] = useState(false);
|
|
311
|
+
const activeLocale = resolveClientLocale(useSearchParams(), locale);
|
|
312
|
+
const searchApi = useMemo(() => withLangInUrl(api, activeLocale), [activeLocale, api]);
|
|
309
313
|
const inputRef = useRef(null);
|
|
310
314
|
const listRef = useRef(null);
|
|
311
315
|
useEffect(() => {
|
|
@@ -357,7 +361,9 @@ function DocsCommandSearch() {
|
|
|
357
361
|
setLoading(true);
|
|
358
362
|
(async () => {
|
|
359
363
|
try {
|
|
360
|
-
const
|
|
364
|
+
const requestUrl = new URL(searchApi, window.location.origin);
|
|
365
|
+
requestUrl.searchParams.set("query", debouncedQuery);
|
|
366
|
+
const res = await fetch(requestUrl.toString());
|
|
361
367
|
if (!res.ok || cancelled) return;
|
|
362
368
|
const items = (await res.json()).map((r) => {
|
|
363
369
|
const label = stripHtml(r.content);
|
|
@@ -366,7 +372,7 @@ function DocsCommandSearch() {
|
|
|
366
372
|
id: r.id,
|
|
367
373
|
label,
|
|
368
374
|
subtitle: labelForType(r.type),
|
|
369
|
-
url: r.url,
|
|
375
|
+
url: withLangInUrl(r.url, activeLocale),
|
|
370
376
|
icon: iconForType(r.type),
|
|
371
377
|
score,
|
|
372
378
|
indices
|
|
@@ -383,7 +389,11 @@ function DocsCommandSearch() {
|
|
|
383
389
|
return () => {
|
|
384
390
|
cancelled = true;
|
|
385
391
|
};
|
|
386
|
-
}, [
|
|
392
|
+
}, [
|
|
393
|
+
activeLocale,
|
|
394
|
+
debouncedQuery,
|
|
395
|
+
searchApi
|
|
396
|
+
]);
|
|
387
397
|
useEffect(() => {
|
|
388
398
|
if (open) setTimeout(() => inputRef.current?.focus(), 10);
|
|
389
399
|
else {
|
package/dist/docs-layout.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DocsConfig, PageFrontmatter } from "@farming-labs/docs";
|
|
2
1
|
import { ReactNode } from "react";
|
|
2
|
+
import { DocsConfig, PageFrontmatter } from "@farming-labs/docs";
|
|
3
3
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/docs-layout.d.ts
|
|
@@ -29,7 +29,9 @@ declare function createDocsMetadata(config: DocsConfig): Record<string, unknown>
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
declare function createPageMetadata(config: DocsConfig, page: Pick<PageFrontmatter, "title" | "description" | "ogImage" | "openGraph" | "twitter">, baseUrl?: string): Record<string, unknown>;
|
|
32
|
-
declare function createDocsLayout(config: DocsConfig
|
|
32
|
+
declare function createDocsLayout(config: DocsConfig, options?: {
|
|
33
|
+
locale?: string;
|
|
34
|
+
}): ({
|
|
33
35
|
children
|
|
34
36
|
}: {
|
|
35
37
|
children: ReactNode;
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { getNextAppDir } from "./get-app-dir.mjs";
|
|
2
2
|
import { serializeIcon } from "./serialize-icon.mjs";
|
|
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 { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
6
7
|
import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
8
|
+
import { LocaleThemeControl } from "./locale-theme-control.mjs";
|
|
7
9
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
8
10
|
import fs from "node:fs";
|
|
9
11
|
import path from "node:path";
|
|
10
12
|
import matter from "gray-matter";
|
|
13
|
+
import { Suspense } from "react";
|
|
11
14
|
import { buildPageOpenGraph, buildPageTwitter } from "@farming-labs/docs";
|
|
12
15
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
16
|
|
|
@@ -35,9 +38,35 @@ function hasChildPages(dir) {
|
|
|
35
38
|
}
|
|
36
39
|
return false;
|
|
37
40
|
}
|
|
38
|
-
function
|
|
41
|
+
function getDocsI18n(config) {
|
|
42
|
+
return config.i18n;
|
|
43
|
+
}
|
|
44
|
+
function resolveDocsI18nConfig(i18n) {
|
|
45
|
+
if (!i18n || !Array.isArray(i18n.locales)) return null;
|
|
46
|
+
const locales = Array.from(new Set(i18n.locales.map((item) => item.trim()).filter(Boolean)));
|
|
47
|
+
if (locales.length === 0) return null;
|
|
48
|
+
return {
|
|
49
|
+
locales,
|
|
50
|
+
defaultLocale: i18n.defaultLocale && locales.includes(i18n.defaultLocale) ? i18n.defaultLocale : locales[0]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function resolveDocsLocaleContext(config, locale) {
|
|
54
|
+
const entryBase = config.entry ?? "docs";
|
|
39
55
|
const appDir = getNextAppDir(process.cwd());
|
|
40
|
-
const
|
|
56
|
+
const i18n = resolveDocsI18nConfig(getDocsI18n(config));
|
|
57
|
+
if (!i18n) return {
|
|
58
|
+
entryPath: entryBase,
|
|
59
|
+
docsDir: path.join(process.cwd(), appDir, entryBase)
|
|
60
|
+
};
|
|
61
|
+
const resolvedLocale = locale && i18n.locales.includes(locale) ? locale : i18n.defaultLocale;
|
|
62
|
+
return {
|
|
63
|
+
entryPath: entryBase,
|
|
64
|
+
locale: resolvedLocale,
|
|
65
|
+
docsDir: path.join(process.cwd(), appDir, entryBase, resolvedLocale)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function buildTree(config, ctx, flat = false) {
|
|
69
|
+
const docsDir = ctx.docsDir;
|
|
41
70
|
const icons = config.icons;
|
|
42
71
|
const ordering = config.ordering;
|
|
43
72
|
const rootChildren = [];
|
|
@@ -46,7 +75,7 @@ function buildTree(config, flat = false) {
|
|
|
46
75
|
rootChildren.push({
|
|
47
76
|
type: "page",
|
|
48
77
|
name: data.title ?? "Documentation",
|
|
49
|
-
url: `/${
|
|
78
|
+
url: `/${ctx.entryPath}`,
|
|
50
79
|
icon: resolveIcon(data.icon, icons)
|
|
51
80
|
});
|
|
52
81
|
}
|
|
@@ -57,7 +86,7 @@ function buildTree(config, flat = false) {
|
|
|
57
86
|
if (!fs.existsSync(pagePath)) return null;
|
|
58
87
|
const data = readFrontmatter(pagePath);
|
|
59
88
|
const slug = [...baseSlug, name];
|
|
60
|
-
const url = `/${
|
|
89
|
+
const url = `/${ctx.entryPath}/${slug.join("/")}`;
|
|
61
90
|
const icon = resolveIcon(data.icon, icons);
|
|
62
91
|
const displayName = data.title ?? name.replace(/-/g, " ");
|
|
63
92
|
if (hasChildPages(full)) {
|
|
@@ -140,13 +169,32 @@ function buildTree(config, flat = false) {
|
|
|
140
169
|
children: rootChildren
|
|
141
170
|
};
|
|
142
171
|
}
|
|
172
|
+
function localizeTreeUrls(tree, locale) {
|
|
173
|
+
function mapNode(node) {
|
|
174
|
+
if (node.type === "page") return {
|
|
175
|
+
...node,
|
|
176
|
+
url: withLangInUrl(node.url, locale)
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
...node,
|
|
180
|
+
index: node.index ? {
|
|
181
|
+
...node.index,
|
|
182
|
+
url: withLangInUrl(node.index.url, locale)
|
|
183
|
+
} : void 0,
|
|
184
|
+
children: node.children.map(mapNode)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...tree,
|
|
189
|
+
children: tree.children.map(mapNode)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
143
192
|
/**
|
|
144
193
|
* Scan all page.mdx files under the docs entry directory and build
|
|
145
194
|
* a map of URL pathname → formatted last-modified date string.
|
|
146
195
|
*/
|
|
147
|
-
function buildLastModifiedMap(
|
|
148
|
-
const
|
|
149
|
-
const docsDir = path.join(process.cwd(), appDir, entry);
|
|
196
|
+
function buildLastModifiedMap(ctx) {
|
|
197
|
+
const docsDir = ctx.docsDir;
|
|
150
198
|
const map = {};
|
|
151
199
|
function formatDate(date) {
|
|
152
200
|
return date.toLocaleDateString("en-US", {
|
|
@@ -159,7 +207,7 @@ function buildLastModifiedMap(entry) {
|
|
|
159
207
|
if (!fs.existsSync(dir)) return;
|
|
160
208
|
const pagePath = path.join(dir, "page.mdx");
|
|
161
209
|
if (fs.existsSync(pagePath)) {
|
|
162
|
-
const url = slugParts.length === 0 ? `/${
|
|
210
|
+
const url = slugParts.length === 0 ? `/${ctx.entryPath}` : `/${ctx.entryPath}/${slugParts.join("/")}`;
|
|
163
211
|
map[url] = formatDate(fs.statSync(pagePath).mtime);
|
|
164
212
|
}
|
|
165
213
|
for (const name of fs.readdirSync(dir)) {
|
|
@@ -174,9 +222,8 @@ function buildLastModifiedMap(entry) {
|
|
|
174
222
|
* Scan all page.mdx files and build a map of URL pathname → description
|
|
175
223
|
* from the frontmatter `description` field.
|
|
176
224
|
*/
|
|
177
|
-
function buildDescriptionMap(
|
|
178
|
-
const
|
|
179
|
-
const docsDir = path.join(process.cwd(), appDir, entry);
|
|
225
|
+
function buildDescriptionMap(ctx) {
|
|
226
|
+
const docsDir = ctx.docsDir;
|
|
180
227
|
const map = {};
|
|
181
228
|
function scan(dir, slugParts) {
|
|
182
229
|
if (!fs.existsSync(dir)) return;
|
|
@@ -184,7 +231,7 @@ function buildDescriptionMap(entry) {
|
|
|
184
231
|
if (fs.existsSync(pagePath)) {
|
|
185
232
|
const desc = readFrontmatter(pagePath).description;
|
|
186
233
|
if (desc) {
|
|
187
|
-
const url = slugParts.length === 0 ? `/${
|
|
234
|
+
const url = slugParts.length === 0 ? `/${ctx.entryPath}` : `/${ctx.entryPath}/${slugParts.join("/")}`;
|
|
188
235
|
map[url] = desc;
|
|
189
236
|
}
|
|
190
237
|
}
|
|
@@ -380,12 +427,16 @@ function LayoutStyle({ layout }) {
|
|
|
380
427
|
}
|
|
381
428
|
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: parts.join("\n") } });
|
|
382
429
|
}
|
|
383
|
-
function createDocsLayout(config) {
|
|
430
|
+
function createDocsLayout(config, options) {
|
|
384
431
|
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
385
432
|
const tocEnabled = tocConfig?.enabled !== false;
|
|
386
433
|
const tocStyle = tocConfig?.style;
|
|
434
|
+
const localeContext = resolveDocsLocaleContext(config, options?.locale);
|
|
435
|
+
const i18n = resolveDocsI18nConfig(getDocsI18n(config));
|
|
436
|
+
const activeLocale = localeContext.locale ?? i18n?.defaultLocale;
|
|
437
|
+
const docsApiUrl = withLangInUrl("/api/docs", activeLocale);
|
|
387
438
|
const navTitle = config.nav?.title ?? "Docs";
|
|
388
|
-
const navUrl = config.nav?.url ?? `/${
|
|
439
|
+
const navUrl = withLangInUrl(config.nav?.url ?? `/${localeContext.entryPath}`, activeLocale);
|
|
389
440
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
390
441
|
const toggleConfig = typeof config.themeToggle === "object" ? config.themeToggle : void 0;
|
|
391
442
|
const forcedTheme = themeSwitch.enabled === false && toggleConfig?.default && toggleConfig.default !== "system" ? toggleConfig.default : void 0;
|
|
@@ -434,13 +485,32 @@ function createDocsLayout(config) {
|
|
|
434
485
|
aiModels = rawModelConfig.models ?? aiModels;
|
|
435
486
|
aiDefaultModelId = rawModelConfig.defaultModel ?? rawModelConfig.models?.[0]?.id ?? aiDefaultModelId;
|
|
436
487
|
}
|
|
437
|
-
const lastModifiedMap = buildLastModifiedMap(
|
|
438
|
-
const descriptionMap = buildDescriptionMap(
|
|
488
|
+
const lastModifiedMap = buildLastModifiedMap(localeContext);
|
|
489
|
+
const descriptionMap = buildDescriptionMap(localeContext);
|
|
439
490
|
return function DocsLayoutWrapper({ children }) {
|
|
440
|
-
const tree = buildTree(config, !!sidebarFlat);
|
|
491
|
+
const tree = buildTree(config, localeContext, !!sidebarFlat);
|
|
492
|
+
const localizedTree = i18n ? localizeTreeUrls(tree, activeLocale) : tree;
|
|
441
493
|
const finalSidebarProps = { ...sidebarProps };
|
|
494
|
+
const sidebarFooter = sidebarProps.footer;
|
|
495
|
+
if (i18n) finalSidebarProps.footer = /* @__PURE__ */ jsxs("div", {
|
|
496
|
+
style: {
|
|
497
|
+
display: "flex",
|
|
498
|
+
flexDirection: "column",
|
|
499
|
+
gap: 12
|
|
500
|
+
},
|
|
501
|
+
children: [sidebarFooter, /* @__PURE__ */ jsx(Suspense, {
|
|
502
|
+
fallback: null,
|
|
503
|
+
children: /* @__PURE__ */ jsx(LocaleThemeControl, {
|
|
504
|
+
locales: i18n.locales,
|
|
505
|
+
defaultLocale: i18n.defaultLocale,
|
|
506
|
+
locale: activeLocale,
|
|
507
|
+
showThemeToggle: themeSwitch.enabled !== false,
|
|
508
|
+
themeMode: themeSwitch.mode
|
|
509
|
+
})
|
|
510
|
+
})]
|
|
511
|
+
});
|
|
442
512
|
if (sidebarComponentFn) finalSidebarProps.component = sidebarComponentFn({
|
|
443
|
-
tree,
|
|
513
|
+
tree: localizedTree,
|
|
444
514
|
collapsible: sidebarProps.collapsible !== false,
|
|
445
515
|
flat: !!sidebarFlat
|
|
446
516
|
});
|
|
@@ -448,12 +518,15 @@ function createDocsLayout(config) {
|
|
|
448
518
|
id: "nd-docs-layout",
|
|
449
519
|
style: { display: "contents" },
|
|
450
520
|
children: /* @__PURE__ */ jsxs(DocsLayout, {
|
|
451
|
-
tree,
|
|
521
|
+
tree: localizedTree,
|
|
452
522
|
nav: {
|
|
453
523
|
title: navTitle,
|
|
454
524
|
url: navUrl
|
|
455
525
|
},
|
|
456
|
-
themeSwitch
|
|
526
|
+
themeSwitch: i18n ? {
|
|
527
|
+
...themeSwitch,
|
|
528
|
+
enabled: false
|
|
529
|
+
} : themeSwitch,
|
|
457
530
|
sidebar: finalSidebarProps,
|
|
458
531
|
...aiMode === "sidebar-icon" && aiEnabled ? { searchToggle: { components: { lg: /* @__PURE__ */ jsx(SidebarSearchWithAI, {}) } } } : {},
|
|
459
532
|
children: [
|
|
@@ -461,38 +534,53 @@ function createDocsLayout(config) {
|
|
|
461
534
|
/* @__PURE__ */ jsx(TypographyStyle, { typography }),
|
|
462
535
|
/* @__PURE__ */ jsx(LayoutStyle, { layout: layoutDimensions }),
|
|
463
536
|
forcedTheme && /* @__PURE__ */ jsx(ForcedThemeScript, { theme: forcedTheme }),
|
|
464
|
-
!staticExport && /* @__PURE__ */ jsx(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
537
|
+
!staticExport && /* @__PURE__ */ jsx(Suspense, {
|
|
538
|
+
fallback: null,
|
|
539
|
+
children: /* @__PURE__ */ jsx(DocsCommandSearch, {
|
|
540
|
+
api: docsApiUrl,
|
|
541
|
+
locale: activeLocale
|
|
542
|
+
})
|
|
543
|
+
}),
|
|
544
|
+
aiEnabled && /* @__PURE__ */ jsx(Suspense, {
|
|
545
|
+
fallback: null,
|
|
546
|
+
children: /* @__PURE__ */ jsx(DocsAIFeatures, {
|
|
547
|
+
mode: aiMode,
|
|
548
|
+
api: docsApiUrl,
|
|
549
|
+
locale: activeLocale,
|
|
550
|
+
position: aiPosition,
|
|
551
|
+
floatingStyle: aiFloatingStyle,
|
|
552
|
+
triggerComponentHtml: aiTriggerComponentHtml,
|
|
553
|
+
suggestedQuestions: aiSuggestedQuestions,
|
|
554
|
+
aiLabel,
|
|
555
|
+
loaderVariant: aiLoaderVariant,
|
|
556
|
+
loadingComponentHtml: aiLoadingComponentHtml,
|
|
557
|
+
models: aiModels,
|
|
558
|
+
defaultModelId: aiDefaultModelId
|
|
559
|
+
})
|
|
476
560
|
}),
|
|
477
|
-
/* @__PURE__ */ jsx(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
561
|
+
/* @__PURE__ */ jsx(Suspense, {
|
|
562
|
+
fallback: children,
|
|
563
|
+
children: /* @__PURE__ */ jsx(DocsPageClient, {
|
|
564
|
+
tocEnabled,
|
|
565
|
+
tocStyle,
|
|
566
|
+
breadcrumbEnabled,
|
|
567
|
+
entry: localeContext.entryPath,
|
|
568
|
+
locale: activeLocale,
|
|
569
|
+
copyMarkdown: copyMarkdownEnabled,
|
|
570
|
+
openDocs: openDocsEnabled,
|
|
571
|
+
openDocsProviders,
|
|
572
|
+
pageActionsPosition,
|
|
573
|
+
pageActionsAlignment,
|
|
574
|
+
githubUrl,
|
|
575
|
+
githubBranch,
|
|
576
|
+
githubDirectory,
|
|
577
|
+
lastModifiedMap,
|
|
578
|
+
lastUpdatedEnabled,
|
|
579
|
+
lastUpdatedPosition,
|
|
580
|
+
llmsTxtEnabled,
|
|
581
|
+
descriptionMap,
|
|
582
|
+
children
|
|
583
|
+
})
|
|
496
584
|
})
|
|
497
585
|
]
|
|
498
586
|
})
|
|
@@ -14,6 +14,8 @@ interface DocsPageClientProps {
|
|
|
14
14
|
breadcrumbEnabled?: boolean;
|
|
15
15
|
/** The docs entry folder name (e.g. "docs") — used to strip from breadcrumb */
|
|
16
16
|
entry?: string;
|
|
17
|
+
/** Active locale (used for llms.txt links) */
|
|
18
|
+
locale?: string;
|
|
17
19
|
copyMarkdown?: boolean;
|
|
18
20
|
openDocs?: boolean;
|
|
19
21
|
openDocsProviders?: SerializedProvider[];
|
|
@@ -46,6 +48,7 @@ declare function DocsPageClient({
|
|
|
46
48
|
tocStyle,
|
|
47
49
|
breadcrumbEnabled,
|
|
48
50
|
entry,
|
|
51
|
+
locale,
|
|
49
52
|
copyMarkdown,
|
|
50
53
|
openDocs,
|
|
51
54
|
openDocsProviders,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { PageActions } from "./page-actions.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
|
+
import { DocsBody, DocsPage, EditOnGitHub } from "fumadocs-ui/layouts/docs/page";
|
|
6
7
|
import { createPortal } from "react-dom";
|
|
7
|
-
import { usePathname, useRouter } from "next/navigation";
|
|
8
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
8
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
|
|
10
11
|
//#region src/docs-page-client.tsx
|
|
@@ -12,26 +13,28 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
12
13
|
* Path-based breadcrumb that shows only parent / current folder.
|
|
13
14
|
* Skips the entry segment (e.g. "docs"). Parent is clickable.
|
|
14
15
|
*/
|
|
15
|
-
function PathBreadcrumb({ pathname, entry }) {
|
|
16
|
+
function PathBreadcrumb({ pathname, entry, locale }) {
|
|
16
17
|
const router = useRouter();
|
|
17
18
|
const segments = pathname.split("/").filter(Boolean);
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const entryParts = entry.split("/").filter(Boolean);
|
|
20
|
+
const contentSegments = segments.slice(entryParts.length);
|
|
21
|
+
if (contentSegments.length < 2) return null;
|
|
22
|
+
const parentSegment = contentSegments[contentSegments.length - 2];
|
|
23
|
+
const currentSegment = contentSegments[contentSegments.length - 1];
|
|
21
24
|
const parentLabel = parentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22
25
|
const currentLabel = currentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
23
|
-
const
|
|
26
|
+
const localizedParentUrl = withLangInUrl("/" + [...segments.slice(0, entryParts.length), ...contentSegments.slice(0, -1)].join("/"), locale);
|
|
24
27
|
return /* @__PURE__ */ jsxs("nav", {
|
|
25
28
|
className: "fd-breadcrumb",
|
|
26
29
|
"aria-label": "Breadcrumb",
|
|
27
30
|
children: [/* @__PURE__ */ jsx("span", {
|
|
28
31
|
className: "fd-breadcrumb-item",
|
|
29
32
|
children: /* @__PURE__ */ jsx("a", {
|
|
30
|
-
href:
|
|
33
|
+
href: localizedParentUrl,
|
|
31
34
|
className: "fd-breadcrumb-parent fd-breadcrumb-link",
|
|
32
35
|
onClick: (e) => {
|
|
33
36
|
e.preventDefault();
|
|
34
|
-
router.push(
|
|
37
|
+
router.push(localizedParentUrl);
|
|
35
38
|
},
|
|
36
39
|
children: parentLabel
|
|
37
40
|
})
|
|
@@ -59,14 +62,36 @@ function PathBreadcrumb({ pathname, entry }) {
|
|
|
59
62
|
* No directory: https://github.com/user/repo/edit/main/app/docs/cli/page.mdx
|
|
60
63
|
* With directory: https://github.com/farming-labs/docs/edit/main/website/app/docs/cli/page.mdx
|
|
61
64
|
*/
|
|
62
|
-
function buildGithubFileUrl(githubUrl, branch, pathname, directory) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
+
function buildGithubFileUrl(githubUrl, branch, pathname, entry, locale, directory) {
|
|
66
|
+
const normalizedEntry = entry.replace(/^\/+|\/+$/g, "") || "docs";
|
|
67
|
+
const entryParts = normalizedEntry.split("/").filter(Boolean);
|
|
68
|
+
const pathnameParts = pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
|
|
69
|
+
const slugParts = pathnameParts.slice(0, entryParts.length).join("/") === entryParts.join("/") ? pathnameParts.slice(entryParts.length) : pathnameParts;
|
|
70
|
+
const dirPrefix = directory ? `${directory}/` : "";
|
|
71
|
+
const basePath = `app/${normalizedEntry}`;
|
|
72
|
+
const relativePath = [locale, slugParts.join("/")].filter(Boolean).join("/");
|
|
73
|
+
return `${githubUrl}/edit/${branch}/${`${dirPrefix}${basePath}${relativePath ? `/${relativePath}` : ""}/page.mdx`}`;
|
|
74
|
+
}
|
|
75
|
+
function localizeInternalLinks(root, locale) {
|
|
76
|
+
const anchors = root.querySelectorAll("a[href]:not([data-fd-lang-localized=\"true\"])");
|
|
77
|
+
for (const anchor of anchors) {
|
|
78
|
+
const href = anchor.getAttribute("href");
|
|
79
|
+
if (!href || href.startsWith("#")) continue;
|
|
80
|
+
if (/^(mailto:|tel:|javascript:)/i.test(href)) continue;
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(href, window.location.origin);
|
|
83
|
+
if (url.origin !== window.location.origin) continue;
|
|
84
|
+
anchor.href = withLangInUrl(url.pathname + url.search + url.hash, locale);
|
|
85
|
+
anchor.dataset.fdLangLocalized = "true";
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
65
88
|
}
|
|
66
|
-
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, children }) {
|
|
89
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, children }) {
|
|
67
90
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
68
91
|
const [toc, setToc] = useState([]);
|
|
69
92
|
const pathname = usePathname();
|
|
93
|
+
const activeLocale = resolveClientLocale(useSearchParams(), locale);
|
|
94
|
+
const llmsLangParam = activeLocale ? `&lang=${encodeURIComponent(activeLocale)}` : "";
|
|
70
95
|
const [actionsPortalTarget, setActionsPortalTarget] = useState(null);
|
|
71
96
|
const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
|
|
72
97
|
useEffect(() => {
|
|
@@ -103,8 +128,20 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
103
128
|
if (desc) desc.remove();
|
|
104
129
|
};
|
|
105
130
|
}, [pageDescription, pathname]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const timer = requestAnimationFrame(() => {
|
|
133
|
+
const container = document.getElementById("nd-page");
|
|
134
|
+
if (!container) return;
|
|
135
|
+
localizeInternalLinks(container, activeLocale);
|
|
136
|
+
});
|
|
137
|
+
return () => cancelAnimationFrame(timer);
|
|
138
|
+
}, [
|
|
139
|
+
activeLocale,
|
|
140
|
+
children,
|
|
141
|
+
pathname
|
|
142
|
+
]);
|
|
106
143
|
const showActions = copyMarkdown || openDocs;
|
|
107
|
-
const githubFileUrl = githubUrl ? buildGithubFileUrl(githubUrl, githubBranch, pathname, githubDirectory) : void 0;
|
|
144
|
+
const githubFileUrl = githubUrl ? buildGithubFileUrl(githubUrl, githubBranch, pathname, entry, activeLocale, githubDirectory) : void 0;
|
|
108
145
|
const normalizedPath = pathname.replace(/\/$/, "") || "/";
|
|
109
146
|
const lastModified = lastUpdatedEnabled ? lastModifiedMap?.[normalizedPath] : void 0;
|
|
110
147
|
const showLastUpdatedBelowTitle = !!lastModified && lastUpdatedPosition === "below-title";
|
|
@@ -171,7 +208,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
171
208
|
children: [
|
|
172
209
|
breadcrumbEnabled && /* @__PURE__ */ jsx(PathBreadcrumb, {
|
|
173
210
|
pathname,
|
|
174
|
-
entry
|
|
211
|
+
entry,
|
|
212
|
+
locale: activeLocale
|
|
175
213
|
}),
|
|
176
214
|
showActions && actionsPortalTarget && createPortal(/* @__PURE__ */ jsx(PageActions, {
|
|
177
215
|
copyMarkdown,
|
|
@@ -194,13 +232,13 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
194
232
|
llmsTxtEnabled && /* @__PURE__ */ jsxs("span", {
|
|
195
233
|
className: "fd-llms-txt-links",
|
|
196
234
|
children: [/* @__PURE__ */ jsx("a", {
|
|
197
|
-
href:
|
|
235
|
+
href: `/api/docs?format=llms${llmsLangParam}`,
|
|
198
236
|
target: "_blank",
|
|
199
237
|
rel: "noopener noreferrer",
|
|
200
238
|
className: "fd-llms-txt-link",
|
|
201
239
|
children: "llms.txt"
|
|
202
240
|
}), /* @__PURE__ */ jsx("a", {
|
|
203
|
-
href:
|
|
241
|
+
href: `/api/docs?format=llms-full${llmsLangParam}`,
|
|
204
242
|
target: "_blank",
|
|
205
243
|
rel: "noopener noreferrer",
|
|
206
244
|
className: "fd-llms-txt-link",
|
package/dist/i18n.d.mts
ADDED
package/dist/i18n.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/i18n.ts
|
|
2
|
+
function withLangInUrl(url, locale) {
|
|
3
|
+
if (!url || url.startsWith("#")) return url;
|
|
4
|
+
const isProtocolRelative = url.startsWith("//");
|
|
5
|
+
const isAbsolute = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url) || isProtocolRelative;
|
|
6
|
+
const parsed = new URL(url, "https://farming-labs.local");
|
|
7
|
+
if (locale) parsed.searchParams.set("lang", locale);
|
|
8
|
+
else parsed.searchParams.delete("lang");
|
|
9
|
+
if (isAbsolute) {
|
|
10
|
+
if (isProtocolRelative) return `//${parsed.host}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
11
|
+
return parsed.toString();
|
|
12
|
+
}
|
|
13
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
14
|
+
}
|
|
15
|
+
function resolveClientLocale(searchParams, fallback) {
|
|
16
|
+
return (searchParams.get("lang") ?? searchParams.get("locale")) || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { resolveClientLocale, withLangInUrl };
|
package/dist/index.d.mts
CHANGED
|
@@ -4,9 +4,10 @@ import { createDocsLayout, createDocsMetadata, createPageMetadata } from "./docs
|
|
|
4
4
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
5
5
|
import { RootProvider } from "./provider.mjs";
|
|
6
6
|
import { PageActions } from "./page-actions.mjs";
|
|
7
|
+
import { withLangInUrl } from "./i18n.mjs";
|
|
7
8
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
8
9
|
import { AIConfig, BreadcrumbConfig, CopyMarkdownConfig, DocsConfig, DocsMetadata, DocsNav, DocsTheme, FontStyle, OGConfig, OpenDocsConfig, OpenDocsProvider, PageActionsConfig, PageFrontmatter, SidebarConfig, ThemeToggleConfig, TypographyConfig, UIConfig, createTheme, deepMerge, defineDocs, extendTheme } from "@farming-labs/docs";
|
|
9
10
|
import { DocsBody, DocsPage } from "fumadocs-ui/layouts/docs/page";
|
|
10
11
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
|
11
12
|
import { CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, Pre } from "fumadocs-ui/components/codeblock";
|
|
12
|
-
export { type AIConfig, type BreadcrumbConfig, CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, type CopyMarkdownConfig, DocsBody, DocsCommandSearch, type DocsConfig, DocsLayout, type DocsMetadata, type DocsNav, DocsPage, DocsPageClient, type DocsTheme, type FontStyle, DefaultUIDefaults as FumadocsUIDefaults, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, PageActions, type PageActionsConfig, type PageFrontmatter, Pre, RootProvider, type SidebarConfig, Tab, Tabs, type ThemeToggleConfig, type TypographyConfig, type UIConfig, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs };
|
|
13
|
+
export { type AIConfig, type BreadcrumbConfig, CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, type CopyMarkdownConfig, DocsBody, DocsCommandSearch, type DocsConfig, DocsLayout, type DocsMetadata, type DocsNav, DocsPage, DocsPageClient, type DocsTheme, type FontStyle, DefaultUIDefaults as FumadocsUIDefaults, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, PageActions, type PageActionsConfig, type PageFrontmatter, Pre, RootProvider, type SidebarConfig, Tab, Tabs, type ThemeToggleConfig, type TypographyConfig, type UIConfig, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs, withLangInUrl };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PageActions } from "./page-actions.mjs";
|
|
2
|
+
import { withLangInUrl } from "./i18n.mjs";
|
|
2
3
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
3
4
|
import { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
4
5
|
import { createDocsLayout, createDocsMetadata, createPageMetadata } from "./docs-layout.mjs";
|
|
@@ -10,4 +11,4 @@ import { DocsBody, DocsPage } from "fumadocs-ui/layouts/docs/page";
|
|
|
10
11
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
|
11
12
|
import { CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, Pre } from "fumadocs-ui/components/codeblock";
|
|
12
13
|
|
|
13
|
-
export { CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, DocsBody, DocsCommandSearch, DocsLayout, DocsPage, DocsPageClient, DefaultUIDefaults as FumadocsUIDefaults, PageActions, Pre, RootProvider, Tab, Tabs, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs };
|
|
14
|
+
export { CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, DocsBody, DocsCommandSearch, DocsLayout, DocsPage, DocsPageClient, DefaultUIDefaults as FumadocsUIDefaults, PageActions, Pre, RootProvider, Tab, Tabs, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs, withLangInUrl };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/locale-theme-control.tsx
|
|
9
|
+
function SunIcon() {
|
|
10
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
11
|
+
width: "16",
|
|
12
|
+
height: "16",
|
|
13
|
+
viewBox: "0 0 24 24",
|
|
14
|
+
fill: "none",
|
|
15
|
+
stroke: "currentColor",
|
|
16
|
+
strokeWidth: "2",
|
|
17
|
+
strokeLinecap: "round",
|
|
18
|
+
strokeLinejoin: "round",
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsx("circle", {
|
|
21
|
+
cx: "12",
|
|
22
|
+
cy: "12",
|
|
23
|
+
r: "5"
|
|
24
|
+
}),
|
|
25
|
+
/* @__PURE__ */ jsx("line", {
|
|
26
|
+
x1: "12",
|
|
27
|
+
y1: "1",
|
|
28
|
+
x2: "12",
|
|
29
|
+
y2: "3"
|
|
30
|
+
}),
|
|
31
|
+
/* @__PURE__ */ jsx("line", {
|
|
32
|
+
x1: "12",
|
|
33
|
+
y1: "21",
|
|
34
|
+
x2: "12",
|
|
35
|
+
y2: "23"
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ jsx("line", {
|
|
38
|
+
x1: "4.22",
|
|
39
|
+
y1: "4.22",
|
|
40
|
+
x2: "5.64",
|
|
41
|
+
y2: "5.64"
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsx("line", {
|
|
44
|
+
x1: "18.36",
|
|
45
|
+
y1: "18.36",
|
|
46
|
+
x2: "19.78",
|
|
47
|
+
y2: "19.78"
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ jsx("line", {
|
|
50
|
+
x1: "1",
|
|
51
|
+
y1: "12",
|
|
52
|
+
x2: "3",
|
|
53
|
+
y2: "12"
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ jsx("line", {
|
|
56
|
+
x1: "21",
|
|
57
|
+
y1: "12",
|
|
58
|
+
x2: "23",
|
|
59
|
+
y2: "12"
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ jsx("line", {
|
|
62
|
+
x1: "4.22",
|
|
63
|
+
y1: "19.78",
|
|
64
|
+
x2: "5.64",
|
|
65
|
+
y2: "18.36"
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ jsx("line", {
|
|
68
|
+
x1: "18.36",
|
|
69
|
+
y1: "5.64",
|
|
70
|
+
x2: "19.78",
|
|
71
|
+
y2: "4.22"
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function MoonIcon() {
|
|
77
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
78
|
+
width: "16",
|
|
79
|
+
height: "16",
|
|
80
|
+
viewBox: "0 0 24 24",
|
|
81
|
+
fill: "none",
|
|
82
|
+
stroke: "currentColor",
|
|
83
|
+
strokeWidth: "2",
|
|
84
|
+
strokeLinecap: "round",
|
|
85
|
+
strokeLinejoin: "round",
|
|
86
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function ChevronDownIcon() {
|
|
90
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
91
|
+
width: "14",
|
|
92
|
+
height: "14",
|
|
93
|
+
viewBox: "0 0 24 24",
|
|
94
|
+
fill: "none",
|
|
95
|
+
stroke: "currentColor",
|
|
96
|
+
strokeWidth: "2",
|
|
97
|
+
strokeLinecap: "round",
|
|
98
|
+
strokeLinejoin: "round",
|
|
99
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "6 9 12 15 18 9" })
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function LocaleThemeControl({ locales, defaultLocale, locale, showThemeToggle = true, themeMode = "light-dark" }) {
|
|
103
|
+
const router = useRouter();
|
|
104
|
+
const pathname = usePathname();
|
|
105
|
+
const searchParams = useSearchParams();
|
|
106
|
+
const [mounted, setMounted] = useState(false);
|
|
107
|
+
const [themeValue, setThemeValue] = useState("system");
|
|
108
|
+
const [resolvedTheme, setResolvedTheme] = useState("light");
|
|
109
|
+
const activeLocale = useMemo(() => resolveClientLocale(searchParams, locale ?? defaultLocale) ?? defaultLocale, [
|
|
110
|
+
defaultLocale,
|
|
111
|
+
locale,
|
|
112
|
+
searchParams
|
|
113
|
+
]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
setMounted(true);
|
|
116
|
+
}, []);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!mounted || !showThemeToggle) return;
|
|
119
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
120
|
+
const updateThemeState = () => {
|
|
121
|
+
let storedTheme = "system";
|
|
122
|
+
try {
|
|
123
|
+
const raw = localStorage.getItem("theme");
|
|
124
|
+
if (raw === "light" || raw === "dark" || raw === "system") storedTheme = raw;
|
|
125
|
+
} catch {}
|
|
126
|
+
const nextResolved = storedTheme === "system" ? media.matches ? "dark" : "light" : storedTheme;
|
|
127
|
+
setThemeValue(storedTheme);
|
|
128
|
+
setResolvedTheme(nextResolved);
|
|
129
|
+
};
|
|
130
|
+
const observer = new MutationObserver(updateThemeState);
|
|
131
|
+
observer.observe(document.documentElement, {
|
|
132
|
+
attributes: true,
|
|
133
|
+
attributeFilter: ["class"]
|
|
134
|
+
});
|
|
135
|
+
media.addEventListener("change", updateThemeState);
|
|
136
|
+
updateThemeState();
|
|
137
|
+
return () => {
|
|
138
|
+
observer.disconnect();
|
|
139
|
+
media.removeEventListener("change", updateThemeState);
|
|
140
|
+
};
|
|
141
|
+
}, [mounted, showThemeToggle]);
|
|
142
|
+
function onLocaleChange(nextLocale) {
|
|
143
|
+
const params = searchParams.toString();
|
|
144
|
+
const current = `${pathname}${params ? `?${params}` : ""}`;
|
|
145
|
+
router.push(withLangInUrl(current, nextLocale));
|
|
146
|
+
}
|
|
147
|
+
if (!mounted) return null;
|
|
148
|
+
const toggleContainerStyle = {
|
|
149
|
+
display: "inline-flex",
|
|
150
|
+
alignItems: "center",
|
|
151
|
+
borderRadius: 9999,
|
|
152
|
+
border: "1px solid var(--color-fd-border)",
|
|
153
|
+
padding: 4,
|
|
154
|
+
gap: 2
|
|
155
|
+
};
|
|
156
|
+
const getToggleItemStyle = (active) => ({
|
|
157
|
+
display: "inline-flex",
|
|
158
|
+
alignItems: "center",
|
|
159
|
+
justifyContent: "center",
|
|
160
|
+
width: 26,
|
|
161
|
+
height: 26,
|
|
162
|
+
borderRadius: 9999,
|
|
163
|
+
border: "none",
|
|
164
|
+
background: active ? "var(--color-fd-accent)" : "transparent",
|
|
165
|
+
color: active ? "var(--color-fd-accent-foreground)" : "var(--color-fd-muted-foreground)",
|
|
166
|
+
cursor: "pointer"
|
|
167
|
+
});
|
|
168
|
+
function applyTheme(nextTheme) {
|
|
169
|
+
const resolved = nextTheme === "system" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : nextTheme;
|
|
170
|
+
document.documentElement.classList.remove("light", "dark");
|
|
171
|
+
document.documentElement.classList.add(resolved);
|
|
172
|
+
document.documentElement.style.colorScheme = resolved;
|
|
173
|
+
try {
|
|
174
|
+
localStorage.setItem("theme", nextTheme);
|
|
175
|
+
} catch {}
|
|
176
|
+
setThemeValue(nextTheme);
|
|
177
|
+
setResolvedTheme(resolved);
|
|
178
|
+
}
|
|
179
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
180
|
+
className: "fd-sidebar-locale-theme-control",
|
|
181
|
+
style: {
|
|
182
|
+
display: "flex",
|
|
183
|
+
alignItems: "center",
|
|
184
|
+
justifyContent: "space-between",
|
|
185
|
+
gap: 12,
|
|
186
|
+
width: "100%"
|
|
187
|
+
},
|
|
188
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
189
|
+
style: {
|
|
190
|
+
position: "relative",
|
|
191
|
+
display: "inline-flex",
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
flexShrink: 0
|
|
194
|
+
},
|
|
195
|
+
children: [/* @__PURE__ */ jsx("select", {
|
|
196
|
+
id: "fd-locale-select",
|
|
197
|
+
value: activeLocale,
|
|
198
|
+
onChange: (e) => onLocaleChange(e.target.value),
|
|
199
|
+
"aria-label": "Select language",
|
|
200
|
+
style: {
|
|
201
|
+
appearance: "none",
|
|
202
|
+
WebkitAppearance: "none",
|
|
203
|
+
MozAppearance: "none",
|
|
204
|
+
minWidth: 84,
|
|
205
|
+
height: 36,
|
|
206
|
+
borderRadius: 9999,
|
|
207
|
+
border: "1px solid var(--color-fd-border)",
|
|
208
|
+
background: "var(--color-fd-card, var(--color-fd-background))",
|
|
209
|
+
color: "var(--color-fd-foreground)",
|
|
210
|
+
padding: "0 36px 0 14px",
|
|
211
|
+
fontSize: 12,
|
|
212
|
+
fontWeight: 600,
|
|
213
|
+
letterSpacing: "0.04em",
|
|
214
|
+
lineHeight: 1,
|
|
215
|
+
cursor: "pointer",
|
|
216
|
+
boxShadow: "0 1px 2px rgba(15, 23, 42, 0.08)"
|
|
217
|
+
},
|
|
218
|
+
children: locales.map((item) => /* @__PURE__ */ jsx("option", {
|
|
219
|
+
value: item,
|
|
220
|
+
children: item.toUpperCase()
|
|
221
|
+
}, item))
|
|
222
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
223
|
+
"aria-hidden": "true",
|
|
224
|
+
style: {
|
|
225
|
+
position: "absolute",
|
|
226
|
+
right: 12,
|
|
227
|
+
display: "inline-flex",
|
|
228
|
+
alignItems: "center",
|
|
229
|
+
justifyContent: "center",
|
|
230
|
+
color: "var(--color-fd-muted-foreground)",
|
|
231
|
+
pointerEvents: "none"
|
|
232
|
+
},
|
|
233
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
|
|
234
|
+
})]
|
|
235
|
+
}), showThemeToggle && (themeMode === "light-dark" ? /* @__PURE__ */ jsxs("button", {
|
|
236
|
+
type: "button",
|
|
237
|
+
"aria-label": "Toggle theme",
|
|
238
|
+
onClick: () => applyTheme(resolvedTheme === "light" ? "dark" : "light"),
|
|
239
|
+
style: toggleContainerStyle,
|
|
240
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
241
|
+
style: getToggleItemStyle(resolvedTheme === "light"),
|
|
242
|
+
children: /* @__PURE__ */ jsx(SunIcon, {})
|
|
243
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
244
|
+
style: getToggleItemStyle(resolvedTheme === "dark"),
|
|
245
|
+
children: /* @__PURE__ */ jsx(MoonIcon, {})
|
|
246
|
+
})]
|
|
247
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
248
|
+
style: toggleContainerStyle,
|
|
249
|
+
children: [
|
|
250
|
+
/* @__PURE__ */ jsx("button", {
|
|
251
|
+
type: "button",
|
|
252
|
+
"aria-label": "light",
|
|
253
|
+
style: getToggleItemStyle(themeValue === "light"),
|
|
254
|
+
onClick: () => applyTheme("light"),
|
|
255
|
+
children: /* @__PURE__ */ jsx(SunIcon, {})
|
|
256
|
+
}),
|
|
257
|
+
/* @__PURE__ */ jsx("button", {
|
|
258
|
+
type: "button",
|
|
259
|
+
"aria-label": "dark",
|
|
260
|
+
style: getToggleItemStyle(themeValue === "dark"),
|
|
261
|
+
onClick: () => applyTheme("dark"),
|
|
262
|
+
children: /* @__PURE__ */ jsx(MoonIcon, {})
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ jsx("button", {
|
|
265
|
+
type: "button",
|
|
266
|
+
"aria-label": "system",
|
|
267
|
+
style: getToggleItemStyle(themeValue === "system"),
|
|
268
|
+
onClick: () => applyTheme("system"),
|
|
269
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
270
|
+
style: {
|
|
271
|
+
display: "inline-flex",
|
|
272
|
+
alignItems: "center",
|
|
273
|
+
justifyContent: "center",
|
|
274
|
+
fontSize: 11,
|
|
275
|
+
fontWeight: 600
|
|
276
|
+
},
|
|
277
|
+
children: "Auto"
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
]
|
|
281
|
+
}))]
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
export { LocaleThemeControl };
|
package/dist/mdx.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MDXImg } from "./mdx-img.mjs";
|
|
2
|
-
import { CodeBlockCopyData } from "@farming-labs/docs";
|
|
3
2
|
import React from "react";
|
|
3
|
+
import { CodeBlockCopyData } from "@farming-labs/docs";
|
|
4
4
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
5
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
|
6
6
|
import * as fumadocs_ui_components_codeblock0 from "fumadocs-ui/components/codeblock";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"tsdown": "^0.20.3",
|
|
105
105
|
"typescript": "^5.9.3",
|
|
106
106
|
"vitest": "^3.2.4",
|
|
107
|
-
"@farming-labs/docs": "0.0.
|
|
107
|
+
"@farming-labs/docs": "0.0.30"
|
|
108
108
|
},
|
|
109
109
|
"peerDependencies": {
|
|
110
110
|
"@farming-labs/docs": ">=0.0.1",
|