boltdocs 1.0.4 → 1.3.1

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.
Files changed (121) hide show
  1. package/dist/{SearchDialog-R36WKAQ7.mjs → SearchDialog-5EDRACEG.mjs} +1 -1
  2. package/dist/{SearchDialog-PYF3QMYG.css → SearchDialog-X57WPTNN.css} +54 -126
  3. package/dist/cache-EHR7SXRU.mjs +12 -0
  4. package/dist/chunk-GSYECEZY.mjs +381 -0
  5. package/dist/{chunk-TWSRXUFF.mjs → chunk-NS7WHDYA.mjs} +229 -418
  6. package/dist/client/index.css +54 -126
  7. package/dist/client/index.d.mts +5 -4
  8. package/dist/client/index.d.ts +5 -4
  9. package/dist/client/index.js +555 -580
  10. package/dist/client/index.mjs +304 -16
  11. package/dist/client/ssr.css +54 -126
  12. package/dist/client/ssr.js +257 -580
  13. package/dist/client/ssr.mjs +1 -1
  14. package/dist/{config-D2XmHJYe.d.mts → config-BD5ZHz15.d.mts} +7 -0
  15. package/dist/{config-D2XmHJYe.d.ts → config-BD5ZHz15.d.ts} +7 -0
  16. package/dist/node/index.d.mts +2 -2
  17. package/dist/node/index.d.ts +2 -2
  18. package/dist/node/index.js +477 -123
  19. package/dist/node/index.mjs +114 -142
  20. package/package.json +2 -2
  21. package/src/client/app/index.tsx +344 -373
  22. package/src/client/app/preload.tsx +56 -56
  23. package/src/client/index.ts +40 -40
  24. package/src/client/ssr.tsx +51 -51
  25. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -76
  26. package/src/client/theme/components/CodeBlock/index.ts +1 -1
  27. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -154
  28. package/src/client/theme/components/PackageManagerTabs/index.ts +1 -1
  29. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -64
  30. package/src/client/theme/components/Playground/Playground.tsx +124 -124
  31. package/src/client/theme/components/Playground/index.ts +1 -1
  32. package/src/client/theme/components/Playground/playground.css +168 -168
  33. package/src/client/theme/components/Video/Video.tsx +84 -84
  34. package/src/client/theme/components/Video/index.ts +1 -1
  35. package/src/client/theme/components/Video/video.css +41 -41
  36. package/src/client/theme/components/mdx/Admonition.tsx +80 -80
  37. package/src/client/theme/components/mdx/Badge.tsx +31 -31
  38. package/src/client/theme/components/mdx/Button.tsx +50 -50
  39. package/src/client/theme/components/mdx/Card.tsx +80 -80
  40. package/src/client/theme/components/mdx/List.tsx +57 -57
  41. package/src/client/theme/components/mdx/Tabs.tsx +94 -94
  42. package/src/client/theme/components/mdx/index.ts +18 -18
  43. package/src/client/theme/components/mdx/mdx-components.css +424 -405
  44. package/src/client/theme/icons/bun.tsx +62 -62
  45. package/src/client/theme/icons/deno.tsx +20 -20
  46. package/src/client/theme/icons/discord.tsx +12 -12
  47. package/src/client/theme/icons/github.tsx +15 -15
  48. package/src/client/theme/icons/npm.tsx +13 -13
  49. package/src/client/theme/icons/pnpm.tsx +72 -72
  50. package/src/client/theme/icons/twitter.tsx +12 -12
  51. package/src/client/theme/styles/markdown.css +343 -343
  52. package/src/client/theme/styles/variables.css +162 -162
  53. package/src/client/theme/styles.css +37 -38
  54. package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -10
  55. package/src/client/theme/ui/BackgroundGradient/index.ts +1 -1
  56. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -68
  57. package/src/client/theme/ui/Breadcrumbs/index.ts +1 -1
  58. package/src/client/theme/ui/Footer/footer.css +32 -32
  59. package/src/client/theme/ui/Head/Head.tsx +69 -69
  60. package/src/client/theme/ui/Head/index.ts +1 -1
  61. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -125
  62. package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -1
  63. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -98
  64. package/src/client/theme/ui/Layout/Layout.tsx +202 -213
  65. package/src/client/theme/ui/Layout/base.css +76 -76
  66. package/src/client/theme/ui/Layout/index.ts +2 -2
  67. package/src/client/theme/ui/Layout/pagination.css +72 -72
  68. package/src/client/theme/ui/Layout/responsive.css +36 -40
  69. package/src/client/theme/ui/Link/Link.tsx +254 -202
  70. package/src/client/theme/ui/Link/index.ts +2 -2
  71. package/src/client/theme/ui/Loading/Loading.tsx +10 -10
  72. package/src/client/theme/ui/Loading/index.ts +1 -1
  73. package/src/client/theme/ui/Loading/loading.css +30 -30
  74. package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -27
  75. package/src/client/theme/ui/Navbar/Navbar.tsx +145 -145
  76. package/src/client/theme/ui/Navbar/index.ts +2 -2
  77. package/src/client/theme/ui/Navbar/navbar.css +233 -233
  78. package/src/client/theme/ui/NotFound/NotFound.tsx +19 -20
  79. package/src/client/theme/ui/NotFound/index.ts +1 -1
  80. package/src/client/theme/ui/NotFound/not-found.css +64 -64
  81. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +235 -192
  82. package/src/client/theme/ui/OnThisPage/index.ts +1 -1
  83. package/src/client/theme/ui/OnThisPage/toc.css +132 -132
  84. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -18
  85. package/src/client/theme/ui/PoweredBy/index.ts +1 -1
  86. package/src/client/theme/ui/PoweredBy/powered-by.css +76 -76
  87. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -199
  88. package/src/client/theme/ui/SearchDialog/index.ts +1 -1
  89. package/src/client/theme/ui/SearchDialog/search.css +152 -152
  90. package/src/client/theme/ui/Sidebar/Sidebar.tsx +204 -200
  91. package/src/client/theme/ui/Sidebar/index.ts +1 -1
  92. package/src/client/theme/ui/Sidebar/sidebar.css +236 -269
  93. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -69
  94. package/src/client/theme/ui/ThemeToggle/index.ts +1 -1
  95. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -136
  96. package/src/client/theme/ui/VersionSwitcher/index.ts +1 -1
  97. package/src/client/types.ts +50 -50
  98. package/src/client/utils.ts +26 -26
  99. package/src/node/cache.ts +408 -94
  100. package/src/node/config.ts +192 -185
  101. package/src/node/index.ts +21 -21
  102. package/src/node/mdx.ts +120 -41
  103. package/src/node/plugin/entry.ts +58 -58
  104. package/src/node/plugin/html.ts +55 -55
  105. package/src/node/plugin/index.ts +193 -190
  106. package/src/node/plugin/types.ts +11 -11
  107. package/src/node/routes/cache.ts +28 -24
  108. package/src/node/routes/index.ts +167 -152
  109. package/src/node/routes/parser.ts +153 -127
  110. package/src/node/routes/sorter.ts +42 -42
  111. package/src/node/routes/types.ts +49 -49
  112. package/src/node/ssg/index.ts +114 -110
  113. package/src/node/ssg/meta.ts +34 -34
  114. package/src/node/ssg/options.ts +13 -13
  115. package/src/node/ssg/sitemap.ts +54 -54
  116. package/src/node/utils.ts +134 -134
  117. package/tsconfig.json +20 -20
  118. package/tsup.config.ts +22 -22
  119. package/dist/Playground-B2FA34BC.mjs +0 -6
  120. package/dist/chunk-WPT4MWTQ.mjs +0 -89
  121. package/src/client/theme/styles/home.css +0 -60
@@ -1,373 +1,344 @@
1
- import React, { useEffect, useState } from "react";
2
- import ReactDOM from "react-dom/client";
3
- import {
4
- BrowserRouter,
5
- Routes,
6
- Route,
7
- Outlet,
8
- useLocation,
9
- } from "react-router-dom";
10
- import { ThemeLayout } from "../theme/ui/Layout";
11
- import { NotFound } from "../theme/ui/NotFound";
12
- import { Loading } from "../theme/ui/Loading";
13
- import { MDXProvider } from "@mdx-js/react";
14
- import { ComponentRoute, CreateBoltdocsAppOptions } from "../types";
15
- import {
16
- createContext,
17
- useContext,
18
- Suspense,
19
- lazy,
20
- useLayoutEffect,
21
- } from "react";
22
- import { Link as LucideLink } from "lucide-react";
23
-
24
- export const ConfigContext = createContext<any>(null);
25
-
26
- export function useConfig() {
27
- return useContext(ConfigContext);
28
- }
29
-
30
- const CodeBlock = lazy(() =>
31
- import("../theme/components/CodeBlock").then((m) => ({
32
- default: m.CodeBlock,
33
- })),
34
- );
35
- const Video = lazy(() =>
36
- import("../theme/components/Video").then((m) => ({ default: m.Video })),
37
- );
38
- const PackageManagerTabs = lazy(() =>
39
- import("../theme/components/PackageManagerTabs").then((m) => ({
40
- default: m.PackageManagerTabs,
41
- })),
42
- );
43
- const Playground = lazy(() =>
44
- import("../theme/components/Playground").then((m) => ({
45
- default: m.Playground,
46
- })),
47
- );
48
-
49
- import {
50
- Button,
51
- Badge,
52
- Card,
53
- Cards,
54
- Tabs,
55
- Tab,
56
- Admonition,
57
- Note,
58
- Tip,
59
- Warning,
60
- Danger,
61
- InfoBox,
62
- List,
63
- } from "../theme/components/mdx";
64
- declare global {
65
- interface ImportMeta {
66
- env: Record<string, any>;
67
- }
68
- }
69
-
70
- import { PreloadProvider } from "./preload";
71
-
72
- const Heading = ({
73
- level,
74
- id,
75
- children,
76
- }: {
77
- level: number;
78
- id?: string;
79
- children: React.ReactNode;
80
- }) => {
81
- const Tag = `h${level}` as keyof JSX.IntrinsicElements;
82
- return (
83
- <Tag id={id} className="boltdocs-heading">
84
- {children}
85
- {id && (
86
- <a href={`#${id}`} className="header-anchor" aria-label="Anchor">
87
- <LucideLink size={16} />
88
- </a>
89
- )}
90
- </Tag>
91
- );
92
- };
93
-
94
- const mdxComponents = {
95
- h1: (props: any) => <Heading level={1} {...props} />,
96
- h2: (props: any) => <Heading level={2} {...props} />,
97
- h3: (props: any) => <Heading level={3} {...props} />,
98
- h4: (props: any) => <Heading level={4} {...props} />,
99
- h5: (props: any) => <Heading level={5} {...props} />,
100
- h6: (props: any) => <Heading level={6} {...props} />,
101
- pre: (props: any) => {
102
- return (
103
- <Suspense fallback={<div className="code-block-skeleton" />}>
104
- <CodeBlock {...props}>{props.children}</CodeBlock>
105
- </Suspense>
106
- );
107
- },
108
- video: (props: any) => (
109
- <Suspense fallback={<div className="video-skeleton" />}>
110
- <Video {...props} />
111
- </Suspense>
112
- ),
113
- PackageManagerTabs: (props: any) => (
114
- <Suspense fallback={<div className="pkg-tabs-skeleton" />}>
115
- <PackageManagerTabs {...props} />
116
- </Suspense>
117
- ),
118
- Playground: (props: any) => (
119
- <Suspense fallback={<div className="playground-skeleton" />}>
120
- <Playground {...props} />
121
- </Suspense>
122
- ),
123
- Button,
124
- Badge,
125
- Card,
126
- Cards,
127
- Tabs,
128
- Tab,
129
- Admonition,
130
- Note,
131
- Tip,
132
- Warning,
133
- Danger,
134
- InfoBox,
135
- List,
136
- };
137
-
138
- export function AppShell({
139
- initialRoutes,
140
- initialConfig,
141
- modules,
142
- hot,
143
- homePage: HomePage,
144
- components: customComponents = {},
145
- }: {
146
- initialRoutes: ComponentRoute[];
147
- initialConfig: any;
148
- modules: Record<string, () => Promise<any>>;
149
- hot?: any;
150
- homePage?: React.ComponentType;
151
- components?: Record<string, React.ComponentType<any>>;
152
- }) {
153
- const [routesInfo, setRoutesInfo] = useState<ComponentRoute[]>(initialRoutes);
154
- const [config] = useState(initialConfig);
155
- const [resolvedRoutes, setResolvedRoutes] = useState<any[]>([]);
156
-
157
- // Subscribe to HMR events
158
- useEffect(() => {
159
- if (hot) {
160
- hot.on("boltdocs:routes-update", (newRoutes: ComponentRoute[]) => {
161
- setRoutesInfo(newRoutes);
162
- });
163
- }
164
- }, [hot]);
165
-
166
- // Resolve MDX components
167
- useEffect(() => {
168
- const mapped = routesInfo
169
- .filter(
170
- (route) => !(HomePage && (route.path === "/" || route.path === "")),
171
- )
172
- .map((route) => {
173
- const loaderKey = Object.keys(modules).find((k) =>
174
- k.endsWith("/" + route.filePath),
175
- );
176
- const loader = loaderKey ? modules[loaderKey] : null;
177
-
178
- return {
179
- ...route,
180
- Component: React.lazy(() => {
181
- if (!loader)
182
- return Promise.resolve({ default: () => <NotFound /> });
183
- return loader() as any;
184
- }),
185
- };
186
- });
187
-
188
- setResolvedRoutes(mapped);
189
- }, [routesInfo, modules]);
190
-
191
- return (
192
- <ConfigContext.Provider value={config}>
193
- <PreloadProvider routes={routesInfo} modules={modules}>
194
- <ScrollHandler />
195
- <Routes>
196
- {/* Custom home page WITHOUT docs layout */}
197
- {HomePage && (
198
- <Route
199
- path="/"
200
- element={
201
- <ThemeLayout
202
- config={config}
203
- routes={routesInfo}
204
- sidebar={null}
205
- toc={null}
206
- breadcrumbs={null}
207
- {...config.themeConfig?.layoutProps}
208
- >
209
- <HomePage />
210
- </ThemeLayout>
211
- }
212
- />
213
- )}
214
-
215
- {/* Documentation pages WITH sidebar + TOC layout */}
216
- <Route element={<DocsLayout config={config} routes={routesInfo} />}>
217
- {resolvedRoutes.map((route: any) => (
218
- <Route
219
- key={route.path}
220
- path={route.path === "" ? "/" : route.path}
221
- element={
222
- <React.Suspense fallback={<Loading />}>
223
- <MdxPage
224
- Component={route.Component}
225
- customComponents={customComponents}
226
- />
227
- </React.Suspense>
228
- }
229
- />
230
- ))}
231
- </Route>
232
-
233
- <Route
234
- path="*"
235
- element={
236
- <ThemeLayout
237
- config={config}
238
- routes={routesInfo}
239
- {...config.themeConfig?.layoutProps}
240
- >
241
- <NotFound />
242
- </ThemeLayout>
243
- }
244
- />
245
- </Routes>
246
- </PreloadProvider>
247
- </ConfigContext.Provider>
248
- );
249
- }
250
-
251
- /**
252
- * Handles scroll restoration and hash scrolling on navigation.
253
- */
254
- function ScrollHandler() {
255
- const { pathname, hash } = useLocation();
256
-
257
- useLayoutEffect(() => {
258
- if (hash) {
259
- const id = hash.replace("#", "");
260
- const element = document.getElementById(id);
261
- if (element) {
262
- const offset = 80;
263
- const bodyRect = document.body.getBoundingClientRect().top;
264
- const elementRect = element.getBoundingClientRect().top;
265
- const elementPosition = elementRect - bodyRect;
266
- const offsetPosition = elementPosition - offset;
267
-
268
- window.scrollTo({
269
- top: offsetPosition,
270
- behavior: "smooth",
271
- });
272
- return;
273
- }
274
- }
275
- window.scrollTo(0, 0);
276
- }, [pathname, hash]);
277
-
278
- return null;
279
- }
280
-
281
- /** Wrapper layout for doc pages (sidebar + content + TOC) */
282
- function DocsLayout({
283
- config,
284
- routes,
285
- }: {
286
- config: any;
287
- routes: ComponentRoute[];
288
- }) {
289
- return (
290
- <ThemeLayout
291
- config={config}
292
- routes={routes}
293
- {...config.themeConfig?.layoutProps}
294
- >
295
- <Outlet />
296
- </ThemeLayout>
297
- );
298
- }
299
-
300
- /**
301
- * Renders an MDX page securely, injecting required custom components.
302
- * For example, this overrides the default `<pre>` HTML tags emitted by MDX
303
- * with the Boltdocs `CodeBlock` component for syntax highlighting.
304
- *
305
- * @param props - Contains the dynamically loaded React component representing the MDX page
306
- */
307
- function MdxPage({
308
- Component,
309
- customComponents = {},
310
- }: {
311
- Component: React.LazyExoticComponent<any>;
312
- customComponents?: Record<string, React.ComponentType<any>>;
313
- }) {
314
- const allComponents = { ...mdxComponents, ...customComponents };
315
- return (
316
- <MDXProvider components={allComponents}>
317
- <Component />
318
- </MDXProvider>
319
- );
320
- }
321
-
322
- /**
323
- * Creates and mounts the Boltdocs documentation app.
324
- *
325
- * Usage:
326
- * ```tsx
327
- * import { createBoltdocsApp } from 'boltdocs/client'
328
- * import routes from 'virtual:boltdocs-routes'
329
- * import config from 'virtual:boltdocs-config'
330
- * import 'boltdocs/style.css'
331
- * import HomePage from './HomePage'
332
- *
333
- * createBoltdocsApp({
334
- * target: '#root',
335
- * routes,
336
- * config,
337
- * modules: import.meta.glob('/docs/**\/*.{md,mdx}'),
338
- * hot: import.meta.hot,
339
- * homePage: HomePage,
340
- * })
341
- * ```
342
- */
343
- export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
344
- const { target, routes, config, modules, hot, homePage } = options;
345
- const container = document.querySelector(target);
346
- if (!container) {
347
- throw new Error(
348
- `[boltdocs] Mount target "${target}" not found in document.`,
349
- );
350
- }
351
-
352
- const app = (
353
- <React.StrictMode>
354
- <BrowserRouter>
355
- <AppShell
356
- initialRoutes={routes}
357
- initialConfig={config}
358
- modules={modules}
359
- hot={hot}
360
- homePage={homePage}
361
- components={options.components}
362
- />
363
- </BrowserRouter>
364
- </React.StrictMode>
365
- );
366
-
367
- // SSG pre-renders a shell with mock components for SEO crawlers.
368
- // We always use createRoot because the SSG output doesn't match the
369
- // real client-side component tree (components are lazy/dynamic).
370
- // Clear any SSG placeholder content before mounting.
371
- container.innerHTML = "";
372
- ReactDOM.createRoot(container as HTMLElement).render(app);
373
- }
1
+ import React, { useEffect, useState } from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import {
4
+ BrowserRouter,
5
+ Routes,
6
+ Route,
7
+ Outlet,
8
+ useLocation,
9
+ } from "react-router-dom";
10
+ import { ThemeLayout } from "../theme/ui/Layout";
11
+ import { NotFound } from "../theme/ui/NotFound";
12
+ import { Loading } from "../theme/ui/Loading";
13
+ import { MDXProvider } from "@mdx-js/react";
14
+ import { ComponentRoute, CreateBoltdocsAppOptions } from "../types";
15
+ import {
16
+ createContext,
17
+ useContext,
18
+ Suspense,
19
+ lazy,
20
+ useLayoutEffect,
21
+ } from "react";
22
+ import { Link as LucideLink } from "lucide-react";
23
+
24
+ export const ConfigContext = createContext<any>(null);
25
+
26
+ export function useConfig() {
27
+ return useContext(ConfigContext);
28
+ }
29
+
30
+ const CodeBlock = lazy(() =>
31
+ import("../theme/components/CodeBlock").then((m) => ({
32
+ default: m.CodeBlock,
33
+ })),
34
+ );
35
+ const Video = lazy(() =>
36
+ import("../theme/components/Video").then((m) => ({ default: m.Video })),
37
+ );
38
+ const PackageManagerTabs = lazy(() =>
39
+ import("../theme/components/PackageManagerTabs").then((m) => ({
40
+ default: m.PackageManagerTabs,
41
+ })),
42
+ );
43
+ declare global {
44
+ interface ImportMeta {
45
+ env: Record<string, any>;
46
+ }
47
+ }
48
+
49
+ import { PreloadProvider } from "./preload";
50
+
51
+ const Heading = ({
52
+ level,
53
+ id,
54
+ children,
55
+ }: {
56
+ level: number;
57
+ id?: string;
58
+ children: React.ReactNode;
59
+ }) => {
60
+ const Tag = `h${level}` as keyof JSX.IntrinsicElements;
61
+ return (
62
+ <Tag id={id} className="boltdocs-heading">
63
+ {children}
64
+ {id && (
65
+ <a href={`#${id}`} className="header-anchor" aria-label="Anchor">
66
+ <LucideLink size={16} />
67
+ </a>
68
+ )}
69
+ </Tag>
70
+ );
71
+ };
72
+
73
+ const mdxComponents = {
74
+ h1: (props: any) => <Heading level={1} {...props} />,
75
+ h2: (props: any) => <Heading level={2} {...props} />,
76
+ h3: (props: any) => <Heading level={3} {...props} />,
77
+ h4: (props: any) => <Heading level={4} {...props} />,
78
+ h5: (props: any) => <Heading level={5} {...props} />,
79
+ h6: (props: any) => <Heading level={6} {...props} />,
80
+ pre: (props: any) => {
81
+ return (
82
+ <Suspense fallback={<div className="code-block-skeleton" />}>
83
+ <CodeBlock {...props}>{props.children}</CodeBlock>
84
+ </Suspense>
85
+ );
86
+ },
87
+ video: (props: any) => (
88
+ <Suspense fallback={<div className="video-skeleton" />}>
89
+ <Video {...props} />
90
+ </Suspense>
91
+ ),
92
+ PackageManagerTabs: (props: any) => (
93
+ <Suspense fallback={<div className="pkg-tabs-skeleton" />}>
94
+ <PackageManagerTabs {...props} />
95
+ </Suspense>
96
+ ),
97
+ };
98
+
99
+ export function AppShell({
100
+ initialRoutes,
101
+ initialConfig,
102
+ modules,
103
+ hot,
104
+ homePage: HomePage,
105
+ components: customComponents = {},
106
+ }: {
107
+ initialRoutes: ComponentRoute[];
108
+ initialConfig: any;
109
+ modules: Record<string, () => Promise<any>>;
110
+ hot?: any;
111
+ homePage?: React.ComponentType;
112
+ components?: Record<string, React.ComponentType<any>>;
113
+ }) {
114
+ const [routesInfo, setRoutesInfo] = useState<ComponentRoute[]>(initialRoutes);
115
+ const [config] = useState(initialConfig);
116
+
117
+ const resolveRoutes = (infos: ComponentRoute[]) => {
118
+ return infos
119
+ .filter(
120
+ (route) => !(HomePage && (route.path === "/" || route.path === "")),
121
+ )
122
+ .map((route) => {
123
+ const loaderKey = Object.keys(modules).find((k) =>
124
+ k.endsWith("/" + route.filePath),
125
+ );
126
+ const loader = loaderKey ? modules[loaderKey] : null;
127
+
128
+ return {
129
+ ...route,
130
+ Component: React.lazy(() => {
131
+ if (!loader)
132
+ return Promise.resolve({ default: () => <NotFound /> });
133
+ return loader() as any;
134
+ }),
135
+ };
136
+ });
137
+ };
138
+
139
+ const [resolvedRoutes, setResolvedRoutes] = useState<any[]>(() =>
140
+ resolveRoutes(initialRoutes),
141
+ );
142
+
143
+ // Subscribe to HMR events
144
+ useEffect(() => {
145
+ if (hot) {
146
+ hot.on("boltdocs:routes-update", (newRoutes: ComponentRoute[]) => {
147
+ setRoutesInfo(newRoutes);
148
+ });
149
+ }
150
+ }, [hot]);
151
+
152
+ // Sync resolved routes when info or modules change
153
+ useEffect(() => {
154
+ setResolvedRoutes(resolveRoutes(routesInfo));
155
+ }, [routesInfo, modules]);
156
+
157
+ return (
158
+ <ConfigContext.Provider value={config}>
159
+ <PreloadProvider routes={routesInfo} modules={modules}>
160
+ <ScrollHandler />
161
+ <Routes>
162
+ {/* Custom home page WITHOUT docs layout */}
163
+ {HomePage && (
164
+ <Route
165
+ path="/"
166
+ element={
167
+ <ThemeLayout
168
+ config={config}
169
+ routes={routesInfo}
170
+ sidebar={null}
171
+ toc={null}
172
+ breadcrumbs={null}
173
+ {...config.themeConfig?.layoutProps}
174
+ >
175
+ <HomePage />
176
+ </ThemeLayout>
177
+ }
178
+ />
179
+ )}
180
+
181
+ {/* Documentation pages WITH sidebar + TOC layout */}
182
+ <Route
183
+ key="docs-layout"
184
+ element={<DocsLayout config={config} routes={routesInfo} />}
185
+ >
186
+ {resolvedRoutes.map((route: any) => (
187
+ <Route
188
+ key={route.path}
189
+ path={route.path === "" ? "/" : route.path}
190
+ element={
191
+ <React.Suspense fallback={<Loading />}>
192
+ <MdxPage
193
+ Component={route.Component}
194
+ customComponents={customComponents}
195
+ />
196
+ </React.Suspense>
197
+ }
198
+ />
199
+ ))}
200
+ </Route>
201
+
202
+ <Route
203
+ path="*"
204
+ element={
205
+ <ThemeLayout
206
+ config={config}
207
+ routes={routesInfo}
208
+ {...config.themeConfig?.layoutProps}
209
+ >
210
+ <NotFound />
211
+ </ThemeLayout>
212
+ }
213
+ />
214
+ </Routes>
215
+ </PreloadProvider>
216
+ </ConfigContext.Provider>
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Handles scroll restoration and hash scrolling on navigation.
222
+ */
223
+ function ScrollHandler() {
224
+ const { pathname, hash } = useLocation();
225
+
226
+ useLayoutEffect(() => {
227
+ // Only scroll if we are not in a pending transition state (if we were using useTransition)
228
+ // For now, we ensure the scroll happens.
229
+ if (hash) {
230
+ const id = hash.replace("#", "");
231
+ const element = document.getElementById(id);
232
+ if (element) {
233
+ const offset = 80;
234
+ const bodyRect = document.body.getBoundingClientRect().top;
235
+ const elementRect = element.getBoundingClientRect().top;
236
+ const elementPosition = elementRect - bodyRect;
237
+ const offsetPosition = elementPosition - offset;
238
+
239
+ window.scrollTo({
240
+ top: offsetPosition,
241
+ behavior: "smooth",
242
+ });
243
+ return;
244
+ }
245
+ }
246
+ window.scrollTo(0, 0);
247
+ }, [pathname, hash]);
248
+
249
+ return null;
250
+ }
251
+
252
+ /** Wrapper layout for doc pages (sidebar + content + TOC) */
253
+ function DocsLayout({
254
+ config,
255
+ routes,
256
+ }: {
257
+ config: any;
258
+ routes: ComponentRoute[];
259
+ }) {
260
+ return (
261
+ <ThemeLayout
262
+ config={config}
263
+ routes={routes}
264
+ {...config.themeConfig?.layoutProps}
265
+ >
266
+ <Outlet />
267
+ </ThemeLayout>
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Renders an MDX page securely, injecting required custom components.
273
+ * For example, this overrides the default `<pre>` HTML tags emitted by MDX
274
+ * with the Boltdocs `CodeBlock` component for syntax highlighting.
275
+ *
276
+ * @param props - Contains the dynamically loaded React component representing the MDX page
277
+ */
278
+ function MdxPage({
279
+ Component,
280
+ customComponents = {},
281
+ }: {
282
+ Component: React.LazyExoticComponent<any>;
283
+ customComponents?: Record<string, React.ComponentType<any>>;
284
+ }) {
285
+ const allComponents = { ...mdxComponents, ...customComponents };
286
+ return (
287
+ <MDXProvider components={allComponents}>
288
+ <Component />
289
+ </MDXProvider>
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Creates and mounts the Boltdocs documentation app.
295
+ *
296
+ * Usage:
297
+ * ```tsx
298
+ * import { createBoltdocsApp } from 'boltdocs/client'
299
+ * import routes from 'virtual:boltdocs-routes'
300
+ * import config from 'virtual:boltdocs-config'
301
+ * import 'boltdocs/style.css'
302
+ * import HomePage from './HomePage'
303
+ *
304
+ * createBoltdocsApp({
305
+ * target: '#root',
306
+ * routes,
307
+ * config,
308
+ * modules: import.meta.glob('/docs/**\/*.{md,mdx}'),
309
+ * hot: import.meta.hot,
310
+ * homePage: HomePage,
311
+ * })
312
+ * ```
313
+ */
314
+ export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
315
+ const { target, routes, config, modules, hot, homePage } = options;
316
+ const container = document.querySelector(target);
317
+ if (!container) {
318
+ throw new Error(
319
+ `[boltdocs] Mount target "${target}" not found in document.`,
320
+ );
321
+ }
322
+
323
+ const app = (
324
+ <React.StrictMode>
325
+ <BrowserRouter>
326
+ <AppShell
327
+ initialRoutes={routes}
328
+ initialConfig={config}
329
+ modules={modules}
330
+ hot={hot}
331
+ homePage={homePage}
332
+ components={options.components}
333
+ />
334
+ </BrowserRouter>
335
+ </React.StrictMode>
336
+ );
337
+
338
+ // SSG pre-renders a shell with mock components for SEO crawlers.
339
+ // We always use createRoot because the SSG output doesn't match the
340
+ // real client-side component tree (components are lazy/dynamic).
341
+ // Clear any SSG placeholder content before mounting.
342
+ container.innerHTML = "";
343
+ ReactDOM.createRoot(container as HTMLElement).render(app);
344
+ }