@acmekit/docs-app 2.13.43

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/index.mjs ADDED
@@ -0,0 +1,490 @@
1
+ // src/docs-app.tsx
2
+ import { Suspense as Suspense2, useEffect as useEffect3, useRef as useRef3 } from "react";
3
+ import { BrowserRouter, Routes, Route } from "react-router-dom";
4
+ import { routes as routes2, components as components2, importers } from "virtual:acmekit/docs-routes";
5
+ import { config as config2 } from "virtual:acmekit/docs-config";
6
+ import { Loading as Loading2 } from "@acmekit/docs-ui";
7
+
8
+ // src/providers/DocsProviders.tsx
9
+ import { useMemo } from "react";
10
+ import {
11
+ RootProviders,
12
+ SiteConfigProvider,
13
+ ScrollControllerProvider,
14
+ SidebarProvider,
15
+ NotificationProvider,
16
+ PaginationProvider,
17
+ MainNavProvider,
18
+ SearchProvider,
19
+ AiAssistantProvider
20
+ } from "@acmekit/docs-ui";
21
+ import { jsx } from "react/jsx-runtime";
22
+ function DocsProviders({ children, config: config3 }) {
23
+ const navItems = useMemo(() => {
24
+ if (!config3.areas?.length) {
25
+ return [];
26
+ }
27
+ return config3.areas.map((area) => ({
28
+ type: "link",
29
+ link: `/${area.id}`,
30
+ title: area.title
31
+ }));
32
+ }, [config3.areas]);
33
+ return /* @__PURE__ */ jsx(RootProviders, { children: /* @__PURE__ */ jsx(
34
+ SiteConfigProvider,
35
+ {
36
+ config: {
37
+ baseUrl: "",
38
+ basePath: config3.basePath,
39
+ sidebars: config3.sidebars || [],
40
+ project: {
41
+ title: config3.title || "Documentation",
42
+ key: "docs"
43
+ },
44
+ logo: config3.logo || "",
45
+ version: config3.version
46
+ },
47
+ children: /* @__PURE__ */ jsx(ScrollControllerProvider, { scrollableSelector: "#main", children: /* @__PURE__ */ jsx(
48
+ SidebarProvider,
49
+ {
50
+ sidebars: config3.sidebars || [],
51
+ isSidebarStatic: true,
52
+ shouldHandlePathChange: true,
53
+ children: /* @__PURE__ */ jsx(NotificationProvider, { children: /* @__PURE__ */ jsx(PaginationProvider, { children: /* @__PURE__ */ jsx(MainNavProvider, { navItems, children: /* @__PURE__ */ jsx(SearchProvider, { children: /* @__PURE__ */ jsx(AiAssistantProvider, { children }) }) }) }) })
54
+ }
55
+ ) })
56
+ }
57
+ ) });
58
+ }
59
+
60
+ // src/components/Layout.tsx
61
+ import React2 from "react";
62
+ import { Outlet, useLocation } from "react-router-dom";
63
+ import {
64
+ Sidebar,
65
+ MainNav,
66
+ ContentMenu,
67
+ Breadcrumbs,
68
+ Footer,
69
+ useIsBrowser,
70
+ useLayout,
71
+ useSidebar,
72
+ useSiteConfig
73
+ } from "@acmekit/docs-ui";
74
+ import clsx from "clsx";
75
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
76
+ function Layout() {
77
+ const { isBrowser } = useIsBrowser();
78
+ const { desktopSidebarOpen, sidebars } = useSidebar();
79
+ const { mainContentRef, showCollapsedNavbar } = useLayout();
80
+ const { frontmatter } = useSiteConfig();
81
+ const { pathname } = useLocation();
82
+ const isAreasRoot = pathname === "/" && sidebars.length > 1;
83
+ React2.useEffect(() => {
84
+ if (!isBrowser) return;
85
+ const rootLayout = document.getElementById("root-layout");
86
+ if (desktopSidebarOpen && !isAreasRoot) {
87
+ rootLayout?.classList.add("lg:grid-cols-[221px_1fr]");
88
+ } else {
89
+ rootLayout?.classList.remove("lg:grid-cols-[221px_1fr]");
90
+ }
91
+ }, [desktopSidebarOpen, isBrowser, isAreasRoot]);
92
+ const showContentMenu = !frontmatter.hide_content_menu && !isAreasRoot;
93
+ return /* @__PURE__ */ jsxs(
94
+ "div",
95
+ {
96
+ className: clsx(
97
+ "bg-acmekit-bg-subtle font-base text-medium w-full",
98
+ "text-acmekit-fg-base",
99
+ "h-full overflow-hidden",
100
+ "grid grid-cols-1 lg:mx-auto",
101
+ !isAreasRoot && "lg:grid-cols-[221px_1fr]"
102
+ ),
103
+ id: "root-layout",
104
+ children: [
105
+ !isAreasRoot && /* @__PURE__ */ jsx2(Sidebar, {}),
106
+ /* @__PURE__ */ jsx2("div", { className: "relative h-screen flex", children: /* @__PURE__ */ jsx2(
107
+ "div",
108
+ {
109
+ className: clsx(
110
+ "relative max-w-full",
111
+ "h-full flex-1",
112
+ "flex flex-col",
113
+ "gap-docs_0.5 lg:py-docs_0.25 lg:mr-docs_0.25 scroll-m-docs_0.25",
114
+ !desktopSidebarOpen && "lg:ml-docs_0.25"
115
+ ),
116
+ children: /* @__PURE__ */ jsxs(
117
+ "div",
118
+ {
119
+ className: clsx(
120
+ "bg-acmekit-bg-base",
121
+ "flex-col items-center",
122
+ "h-full w-full",
123
+ "overflow-y-scroll overflow-x-hidden",
124
+ "md:rounded-docs_DEFAULT",
125
+ "shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark"
126
+ ),
127
+ id: "main",
128
+ ref: mainContentRef,
129
+ children: [
130
+ /* @__PURE__ */ jsx2(MainNav, {}),
131
+ /* @__PURE__ */ jsx2(
132
+ "div",
133
+ {
134
+ className: clsx(
135
+ "pt-docs_4 lg:pt-docs_6 pb-docs_8 lg:pb-docs_4",
136
+ showContentMenu && "grid grid-cols-1 lg:mx-auto",
137
+ desktopSidebarOpen && showContentMenu && "lg:grid-cols-[1fr_221px]"
138
+ ),
139
+ id: "content",
140
+ children: /* @__PURE__ */ jsx2("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs(
141
+ "div",
142
+ {
143
+ className: clsx(
144
+ "w-full h-fit",
145
+ "max-w-inner-content-xs sm:max-w-inner-content-sm",
146
+ "md:max-w-inner-content-md lg:max-w-inner-content-lg",
147
+ "xl:max-w-inner-content-xl xxl:max-w-inner-content-xxl",
148
+ "xxxl:max-w-inner-content-xxxl",
149
+ "px-docs_1 md:px-docs_4 lg:px-0"
150
+ ),
151
+ children: [
152
+ /* @__PURE__ */ jsx2(Breadcrumbs, {}),
153
+ /* @__PURE__ */ jsx2(Outlet, {}),
154
+ /* @__PURE__ */ jsx2(Footer, { showPagination: true })
155
+ ]
156
+ }
157
+ ) })
158
+ }
159
+ ),
160
+ showContentMenu && /* @__PURE__ */ jsx2(ContentMenu, {})
161
+ ]
162
+ }
163
+ )
164
+ }
165
+ ) })
166
+ ]
167
+ }
168
+ );
169
+ }
170
+
171
+ // src/components/DocPage.tsx
172
+ import { useEffect, useRef } from "react";
173
+ import { useLocation as useLocation2 } from "react-router-dom";
174
+ import { useSiteConfig as useSiteConfig2 } from "@acmekit/docs-ui";
175
+
176
+ // src/components/MDXContent.tsx
177
+ import { MDXProvider } from "@mdx-js/react";
178
+ import { MDXComponents } from "@acmekit/docs-ui";
179
+ import { jsx as jsx3 } from "react/jsx-runtime";
180
+ function MDXContent({ children }) {
181
+ return /* @__PURE__ */ jsx3(MDXProvider, { components: MDXComponents, children });
182
+ }
183
+
184
+ // src/components/DocPage.tsx
185
+ import { jsx as jsx4 } from "react/jsx-runtime";
186
+ function DocPage({ children, frontmatter }) {
187
+ const { setFrontmatter, setToc } = useSiteConfig2();
188
+ const location = useLocation2();
189
+ const articleRef = useRef(null);
190
+ useEffect(() => {
191
+ setFrontmatter({
192
+ ...frontmatter,
193
+ generate_toc: true
194
+ });
195
+ setToc([]);
196
+ }, [location.pathname, frontmatter]);
197
+ return /* @__PURE__ */ jsx4("article", { ref: articleRef, children: /* @__PURE__ */ jsx4(MDXContent, { children }) });
198
+ }
199
+
200
+ // src/components/HomePage.tsx
201
+ import { Suspense } from "react";
202
+ import { Link } from "react-router-dom";
203
+ import { Loading } from "@acmekit/docs-ui";
204
+ import { config } from "virtual:acmekit/docs-config";
205
+ import { routes, components } from "virtual:acmekit/docs-routes";
206
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
207
+ function HomePage() {
208
+ const rootRoute = routes.find((r) => r.path === "/");
209
+ const RootComponent = components["/"];
210
+ if (RootComponent) {
211
+ return /* @__PURE__ */ jsx5(DocPage, { frontmatter: rootRoute?.frontmatter, children: /* @__PURE__ */ jsx5(Suspense, { fallback: /* @__PURE__ */ jsx5(Loading, { count: 8 }), children: /* @__PURE__ */ jsx5(RootComponent, {}) }) });
212
+ }
213
+ if (config.areas?.length) {
214
+ const plugins = config.plugins || [];
215
+ return /* @__PURE__ */ jsxs2("div", { className: "py-docs_2", children: [
216
+ /* @__PURE__ */ jsx5("h1", { className: "text-docs-h1 font-docs-heading text-acmekit-fg-base mb-docs_0.5", children: config.title }),
217
+ /* @__PURE__ */ jsx5("p", { className: "text-body-regular text-acmekit-fg-muted mb-docs_2", children: "Browse the documentation areas below." }),
218
+ /* @__PURE__ */ jsx5("div", { className: "grid gap-docs_1 sm:grid-cols-2 lg:grid-cols-3", children: config.areas.map((area) => {
219
+ const sidebar = config.sidebars?.find(
220
+ (s) => s.sidebar_id === area.sidebar_id
221
+ );
222
+ const count = sidebar?.items?.length ?? 0;
223
+ return /* @__PURE__ */ jsxs2(
224
+ Link,
225
+ {
226
+ to: `/${area.id}`,
227
+ className: "group block rounded-docs_DEFAULT border border-acmekit-border-base bg-acmekit-bg-base p-docs_1.5 no-underline transition-all hover:shadow-elevation-card-hover dark:hover:shadow-elevation-card-hover-dark",
228
+ children: [
229
+ /* @__PURE__ */ jsx5("h3", { className: "text-compact-medium-plus text-acmekit-fg-base mb-docs_0.25 group-hover:text-acmekit-fg-interactive", children: area.title }),
230
+ /* @__PURE__ */ jsxs2("p", { className: "text-compact-small text-acmekit-fg-muted", children: [
231
+ count,
232
+ " ",
233
+ count === 1 ? "section" : "sections"
234
+ ] })
235
+ ]
236
+ },
237
+ area.id
238
+ );
239
+ }) }),
240
+ plugins.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
241
+ /* @__PURE__ */ jsx5("h2", { className: "text-docs-h2 font-docs-heading text-acmekit-fg-base mt-docs_2 mb-docs_1", children: "Plugins" }),
242
+ /* @__PURE__ */ jsx5("div", { className: "grid gap-docs_1 sm:grid-cols-2 lg:grid-cols-3", children: plugins.map((plugin) => {
243
+ const sidebar = config.sidebars?.find(
244
+ (s) => s.sidebar_id === plugin.sidebar_id
245
+ );
246
+ const count = sidebar?.items?.length ?? 0;
247
+ return /* @__PURE__ */ jsxs2(
248
+ Link,
249
+ {
250
+ to: `/plugins/${plugin.slug}`,
251
+ className: "group block rounded-docs_DEFAULT border border-acmekit-border-base bg-acmekit-bg-base p-docs_1.5 no-underline transition-all hover:shadow-elevation-card-hover dark:hover:shadow-elevation-card-hover-dark",
252
+ children: [
253
+ /* @__PURE__ */ jsx5("h3", { className: "text-compact-medium-plus text-acmekit-fg-base mb-docs_0.25 group-hover:text-acmekit-fg-interactive", children: plugin.title }),
254
+ plugin.description && /* @__PURE__ */ jsx5("p", { className: "text-compact-small text-acmekit-fg-muted mb-docs_0.25", children: plugin.description }),
255
+ /* @__PURE__ */ jsxs2("p", { className: "text-compact-small text-acmekit-fg-subtle", children: [
256
+ count,
257
+ " ",
258
+ count === 1 ? "page" : "pages"
259
+ ] })
260
+ ]
261
+ },
262
+ plugin.slug
263
+ );
264
+ }) })
265
+ ] })
266
+ ] });
267
+ }
268
+ const categories = (config.sidebars?.[0]?.items || []).filter(
269
+ (item) => item.type === "category" && item.children?.length
270
+ );
271
+ return /* @__PURE__ */ jsxs2("div", { className: "py-docs_2", children: [
272
+ /* @__PURE__ */ jsx5("h1", { className: "text-docs-h1 font-docs-heading text-acmekit-fg-base mb-docs_0.5", children: config.title }),
273
+ /* @__PURE__ */ jsx5("p", { className: "text-body-regular text-acmekit-fg-muted mb-docs_2", children: "Browse the documentation to get started." }),
274
+ categories.length > 0 && /* @__PURE__ */ jsx5("div", { className: "grid gap-docs_1 sm:grid-cols-2 lg:grid-cols-3", children: categories.map((cat) => {
275
+ const firstLink = cat.children?.find(
276
+ (c) => c.type === "link" && c.path
277
+ );
278
+ const targetPath = firstLink?.path || "/";
279
+ return /* @__PURE__ */ jsxs2(
280
+ Link,
281
+ {
282
+ to: targetPath,
283
+ className: "group block rounded-docs_DEFAULT border border-acmekit-border-base bg-acmekit-bg-base p-docs_1.5 no-underline transition-all hover:shadow-elevation-card-hover dark:hover:shadow-elevation-card-hover-dark",
284
+ children: [
285
+ /* @__PURE__ */ jsx5("h3", { className: "text-compact-medium-plus text-acmekit-fg-base mb-docs_0.25 group-hover:text-acmekit-fg-interactive", children: cat.title }),
286
+ /* @__PURE__ */ jsxs2("p", { className: "text-compact-small text-acmekit-fg-muted", children: [
287
+ cat.children?.length,
288
+ " ",
289
+ cat.children?.length === 1 ? "article" : "articles"
290
+ ] })
291
+ ]
292
+ },
293
+ cat.title
294
+ );
295
+ }) })
296
+ ] });
297
+ }
298
+
299
+ // src/components/RouteErrorBoundary.tsx
300
+ import React5 from "react";
301
+ import { ErrorPage } from "@acmekit/docs-ui";
302
+ import { jsx as jsx6 } from "react/jsx-runtime";
303
+ var RouteErrorBoundary = class extends React5.Component {
304
+ constructor() {
305
+ super(...arguments);
306
+ this.state = { hasError: false };
307
+ }
308
+ static getDerivedStateFromError() {
309
+ return { hasError: true };
310
+ }
311
+ render() {
312
+ if (this.state.hasError) {
313
+ return /* @__PURE__ */ jsx6(ErrorPage, {});
314
+ }
315
+ return this.props.children;
316
+ }
317
+ };
318
+
319
+ // src/components/SearchModal.tsx
320
+ import React6, { useCallback, useEffect as useEffect2, useRef as useRef2, useState } from "react";
321
+ import { useNavigate } from "react-router-dom";
322
+ import { SearchInput, useSearch } from "@acmekit/docs-ui";
323
+ import { searchIndex } from "virtual:acmekit/docs-search";
324
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
325
+ function scoreEntry(entry, words) {
326
+ let score = 0;
327
+ const title = entry.title.toLowerCase();
328
+ const desc = entry.description.toLowerCase();
329
+ const body = entry.body.toLowerCase();
330
+ for (const word of words) {
331
+ if (title.includes(word)) score += 3;
332
+ if (desc.includes(word)) score += 2;
333
+ if (body.includes(word)) score += 1;
334
+ }
335
+ return score;
336
+ }
337
+ function SearchModal() {
338
+ const { isOpen, setIsOpen, modalRef } = useSearch();
339
+ const [query, setQuery] = useState("");
340
+ const [activeIndex, setActiveIndex] = useState(0);
341
+ const navigate = useNavigate();
342
+ const searchWrapperRef = useRef2(null);
343
+ const results = React6.useMemo(() => {
344
+ if (!query.trim()) return [];
345
+ const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
346
+ return searchIndex.map((entry) => ({
347
+ path: entry.path,
348
+ title: entry.title,
349
+ description: entry.description,
350
+ score: scoreEntry(entry, words)
351
+ })).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, 20);
352
+ }, [query]);
353
+ useEffect2(() => {
354
+ setActiveIndex(0);
355
+ }, [results]);
356
+ useEffect2(() => {
357
+ function onKeyDown2(e) {
358
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
359
+ e.preventDefault();
360
+ setIsOpen((prev) => !prev);
361
+ }
362
+ }
363
+ document.addEventListener("keydown", onKeyDown2);
364
+ return () => document.removeEventListener("keydown", onKeyDown2);
365
+ }, [setIsOpen]);
366
+ useEffect2(() => {
367
+ const dialog = modalRef.current;
368
+ if (!dialog) return;
369
+ if (isOpen) {
370
+ dialog.showModal();
371
+ searchWrapperRef.current?.querySelector("input")?.focus();
372
+ } else {
373
+ dialog.close();
374
+ setQuery("");
375
+ }
376
+ }, [isOpen, modalRef]);
377
+ const navigateTo = useCallback(
378
+ (path) => {
379
+ navigate(path);
380
+ setIsOpen(false);
381
+ },
382
+ [navigate, setIsOpen]
383
+ );
384
+ function onKeyDown(e) {
385
+ if (e.key === "ArrowDown") {
386
+ e.preventDefault();
387
+ setActiveIndex((i) => Math.min(i + 1, results.length - 1));
388
+ } else if (e.key === "ArrowUp") {
389
+ e.preventDefault();
390
+ setActiveIndex((i) => Math.max(i - 1, 0));
391
+ } else if (e.key === "Enter" && results[activeIndex]) {
392
+ e.preventDefault();
393
+ navigateTo(results[activeIndex].path);
394
+ }
395
+ }
396
+ return /* @__PURE__ */ jsx7(
397
+ "dialog",
398
+ {
399
+ ref: modalRef,
400
+ className: "fixed inset-0 z-50 m-0 h-full w-full max-h-full max-w-full bg-transparent p-0 backdrop:bg-acmekit-bg-overlay",
401
+ onClick: (e) => {
402
+ if (e.target === e.currentTarget) setIsOpen(false);
403
+ },
404
+ onClose: () => setIsOpen(false),
405
+ children: /* @__PURE__ */ jsxs3("div", { className: "mx-auto mt-[15vh] w-full max-w-[540px] rounded-docs_DEFAULT border border-acmekit-border-base bg-acmekit-bg-base shadow-elevation-modal dark:shadow-elevation-modal-dark", children: [
406
+ /* @__PURE__ */ jsx7("div", { ref: searchWrapperRef, className: "p-docs_1", onKeyDown, children: /* @__PURE__ */ jsx7(
407
+ SearchInput,
408
+ {
409
+ value: query,
410
+ onChange: setQuery,
411
+ placeholder: "Search documentation...",
412
+ autoFocus: true
413
+ }
414
+ ) }),
415
+ results.length > 0 && /* @__PURE__ */ jsx7("ul", { className: "max-h-[50vh] overflow-y-auto border-t border-acmekit-border-base p-docs_0.5", children: results.map((result, i) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsxs3(
416
+ "button",
417
+ {
418
+ type: "button",
419
+ className: `w-full rounded-docs_sm px-docs_1 py-docs_0.5 text-left transition-colors ${i === activeIndex ? "bg-acmekit-bg-base-hover" : "hover:bg-acmekit-bg-base-hover"}`,
420
+ onMouseEnter: () => setActiveIndex(i),
421
+ onClick: () => navigateTo(result.path),
422
+ children: [
423
+ /* @__PURE__ */ jsx7("span", { className: "text-compact-small-plus text-acmekit-fg-base block", children: result.title }),
424
+ result.description && /* @__PURE__ */ jsx7("span", { className: "text-compact-x-small text-acmekit-fg-muted block truncate", children: result.description })
425
+ ]
426
+ }
427
+ ) }, result.path)) }),
428
+ query.trim() && results.length === 0 && /* @__PURE__ */ jsx7("div", { className: "border-t border-acmekit-border-base p-docs_1 text-center", children: /* @__PURE__ */ jsx7("span", { className: "text-compact-small text-acmekit-fg-muted", children: "No results found" }) })
429
+ ] })
430
+ }
431
+ );
432
+ }
433
+
434
+ // src/docs-app.tsx
435
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
436
+ function DocsApp({ plugins = [] }) {
437
+ const allRoutes = [...routes2];
438
+ const prefetched = useRef3(/* @__PURE__ */ new Set());
439
+ for (const plugin of plugins) {
440
+ if (plugin.routes) {
441
+ allRoutes.push(...plugin.routes);
442
+ }
443
+ }
444
+ useEffect3(() => {
445
+ function onMouseOver(e) {
446
+ const anchor = e.target.closest?.("a");
447
+ if (!anchor) return;
448
+ const href = anchor.getAttribute("href");
449
+ if (!href || prefetched.current.has(href)) return;
450
+ const importer = importers[href];
451
+ if (importer) {
452
+ prefetched.current.add(href);
453
+ importer();
454
+ }
455
+ }
456
+ document.addEventListener("mouseover", onMouseOver);
457
+ return () => document.removeEventListener("mouseover", onMouseOver);
458
+ }, []);
459
+ return /* @__PURE__ */ jsx8(BrowserRouter, { basename: config2.basePath, children: /* @__PURE__ */ jsx8(DocsProviders, { config: config2, children: /* @__PURE__ */ jsxs4("div", { className: "h-screen w-full overflow-hidden", children: [
460
+ /* @__PURE__ */ jsx8(RouteErrorBoundary, { children: /* @__PURE__ */ jsx8(Suspense2, { fallback: /* @__PURE__ */ jsx8(Loading2, { count: 8 }), children: /* @__PURE__ */ jsx8(Routes, { children: /* @__PURE__ */ jsxs4(Route, { element: /* @__PURE__ */ jsx8(Layout, {}), children: [
461
+ /* @__PURE__ */ jsx8(Route, { index: true, element: /* @__PURE__ */ jsx8(HomePage, {}) }),
462
+ allRoutes.map((route) => {
463
+ const Component = components2[route.path];
464
+ if (!Component) return null;
465
+ if (route.type === "mdx") {
466
+ return /* @__PURE__ */ jsx8(
467
+ Route,
468
+ {
469
+ path: route.path,
470
+ element: /* @__PURE__ */ jsx8(DocPage, { frontmatter: route.frontmatter, children: /* @__PURE__ */ jsx8(Suspense2, { fallback: /* @__PURE__ */ jsx8(Loading2, { count: 8 }), children: /* @__PURE__ */ jsx8(Component, {}) }) })
471
+ },
472
+ route.path
473
+ );
474
+ }
475
+ return /* @__PURE__ */ jsx8(
476
+ Route,
477
+ {
478
+ path: route.path,
479
+ element: /* @__PURE__ */ jsx8(Suspense2, { fallback: /* @__PURE__ */ jsx8(Loading2, { count: 8 }), children: /* @__PURE__ */ jsx8(Component, {}) })
480
+ },
481
+ route.path
482
+ );
483
+ })
484
+ ] }) }) }) }),
485
+ /* @__PURE__ */ jsx8(SearchModal, {})
486
+ ] }) }) });
487
+ }
488
+ export {
489
+ DocsApp
490
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@acmekit/docs-app",
3
+ "description": "React SPA for AcmeKit documentation.",
4
+ "version": "2.13.43",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./entry-server": "./src/entry-server.tsx",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/acmekit/acmekit",
20
+ "directory": "packages/docs/docs-app"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "package.json"
26
+ ],
27
+ "scripts": {
28
+ "build": "yarn run -T tsup",
29
+ "watch": "yarn run -T tsup --watch",
30
+ "test": "yarn run -T vitest --run"
31
+ },
32
+ "dependencies": {
33
+ "@acmekit/docs-shared": "^2.13.43",
34
+ "@acmekit/docs-ui": "^2.13.43",
35
+ "@mdx-js/react": "^3.0.0",
36
+ "clsx": "^2.0.0",
37
+ "react": "^18.2.0",
38
+ "react-dom": "^18.2.0",
39
+ "react-router-dom": "^6.26.2"
40
+ },
41
+ "devDependencies": {
42
+ "@types/react": "^18.2.0",
43
+ "@types/react-dom": "^18.2.0"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18.2.0",
47
+ "react-dom": "^18.2.0"
48
+ },
49
+ "packageManager": "yarn@3.2.1"
50
+ }
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import type { DocsRouteEntry } from "@acmekit/docs-shared"
3
+ import { buildRoutes } from "../routes/build-routes"
4
+
5
+ // Mock React component type
6
+ const MockComponentA = (() => null) as any
7
+ const MockComponentB = (() => null) as any
8
+ const MockComponentC = (() => null) as any
9
+
10
+ describe("buildRoutes", () => {
11
+ it("should filter routes to only those with matching components", () => {
12
+ const routes: DocsRouteEntry[] = [
13
+ {
14
+ path: "/a",
15
+ componentPath: "/src/a.mdx",
16
+ type: "mdx",
17
+ frontmatter: { title: "A" },
18
+ },
19
+ {
20
+ path: "/b",
21
+ componentPath: "/src/b.mdx",
22
+ type: "mdx",
23
+ frontmatter: { title: "B" },
24
+ },
25
+ {
26
+ path: "/c",
27
+ componentPath: "/src/c.tsx",
28
+ type: "tsx",
29
+ },
30
+ ]
31
+
32
+ const components = {
33
+ "/a": MockComponentA,
34
+ "/c": MockComponentC,
35
+ // /b is intentionally missing
36
+ }
37
+
38
+ const result = buildRoutes(routes, components)
39
+
40
+ expect(result).toHaveLength(2)
41
+ expect(result.map((r) => r.path)).toEqual(["/a", "/c"])
42
+ })
43
+
44
+ it("should attach component reference to each route", () => {
45
+ const routes: DocsRouteEntry[] = [
46
+ {
47
+ path: "/page",
48
+ componentPath: "/src/page.mdx",
49
+ type: "mdx",
50
+ frontmatter: { title: "Page" },
51
+ },
52
+ ]
53
+
54
+ const components = { "/page": MockComponentA }
55
+
56
+ const result = buildRoutes(routes, components)
57
+
58
+ expect(result[0].component).toBe(MockComponentA)
59
+ })
60
+
61
+ it("should preserve all original route properties", () => {
62
+ const routes: DocsRouteEntry[] = [
63
+ {
64
+ path: "/test",
65
+ componentPath: "/src/test.mdx",
66
+ type: "mdx",
67
+ frontmatter: { title: "Test", sidebar_position: 5 },
68
+ },
69
+ ]
70
+
71
+ const components = { "/test": MockComponentA }
72
+
73
+ const result = buildRoutes(routes, components)
74
+
75
+ expect(result[0].path).toBe("/test")
76
+ expect(result[0].componentPath).toBe("/src/test.mdx")
77
+ expect(result[0].type).toBe("mdx")
78
+ expect(result[0].frontmatter).toEqual({
79
+ title: "Test",
80
+ sidebar_position: 5,
81
+ })
82
+ })
83
+
84
+ it("should return empty array when no routes match", () => {
85
+ const routes: DocsRouteEntry[] = [
86
+ {
87
+ path: "/missing",
88
+ componentPath: "/src/missing.mdx",
89
+ type: "mdx",
90
+ },
91
+ ]
92
+
93
+ const result = buildRoutes(routes, {})
94
+
95
+ expect(result).toEqual([])
96
+ })
97
+
98
+ it("should return empty array for empty inputs", () => {
99
+ expect(buildRoutes([], {})).toEqual([])
100
+ })
101
+
102
+ it("should handle all routes having components", () => {
103
+ const routes: DocsRouteEntry[] = [
104
+ { path: "/a", componentPath: "/src/a.mdx", type: "mdx" },
105
+ { path: "/b", componentPath: "/src/b.mdx", type: "mdx" },
106
+ ]
107
+
108
+ const components = {
109
+ "/a": MockComponentA,
110
+ "/b": MockComponentB,
111
+ }
112
+
113
+ const result = buildRoutes(routes, components)
114
+
115
+ expect(result).toHaveLength(2)
116
+ })
117
+
118
+ it("should ignore extra components not in routes", () => {
119
+ const routes: DocsRouteEntry[] = [
120
+ { path: "/a", componentPath: "/src/a.mdx", type: "mdx" },
121
+ ]
122
+
123
+ const components = {
124
+ "/a": MockComponentA,
125
+ "/extra": MockComponentB,
126
+ "/unused": MockComponentC,
127
+ }
128
+
129
+ const result = buildRoutes(routes, components)
130
+
131
+ expect(result).toHaveLength(1)
132
+ expect(result[0].path).toBe("/a")
133
+ })
134
+ })