boltdocs 1.10.2 → 2.0.0

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 (250) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/dist/cache-7G6D532T.mjs +1 -0
  4. package/dist/chunk-A4HQPEPU.mjs +1 -0
  5. package/dist/chunk-BA5NH5HU.mjs +1 -0
  6. package/dist/chunk-BQCD3DWG.mjs +1 -0
  7. package/dist/chunk-H63UMKYF.mjs +1 -0
  8. package/dist/chunk-IWHRQHS7.mjs +1 -0
  9. package/dist/chunk-JZXLCA2E.mjs +1 -0
  10. package/dist/chunk-MFU7Q6WF.mjs +1 -0
  11. package/dist/chunk-QYPNX5UN.mjs +1 -0
  12. package/dist/chunk-XEAPSFMB.mjs +1 -0
  13. package/dist/client/components/mdx/index.d.mts +209 -0
  14. package/dist/client/components/mdx/index.d.ts +209 -0
  15. package/dist/client/components/mdx/index.js +1 -0
  16. package/dist/client/components/mdx/index.mjs +1 -0
  17. package/dist/client/hooks/index.d.mts +133 -0
  18. package/dist/client/hooks/index.d.ts +133 -0
  19. package/dist/client/hooks/index.js +1 -0
  20. package/dist/client/hooks/index.mjs +1 -0
  21. package/dist/client/index.d.mts +138 -298
  22. package/dist/client/index.d.ts +138 -298
  23. package/dist/client/index.js +1 -3630
  24. package/dist/client/index.mjs +1 -697
  25. package/dist/client/ssr.d.mts +7 -3
  26. package/dist/client/ssr.d.ts +7 -3
  27. package/dist/client/ssr.js +1 -2928
  28. package/dist/client/ssr.mjs +1 -33
  29. package/dist/{config-BsFQ-ErD.d.ts → config-CX4l-ZNp.d.mts} +42 -35
  30. package/dist/{config-BsFQ-ErD.d.mts → config-CX4l-ZNp.d.ts} +42 -35
  31. package/dist/node/index.d.mts +2 -4
  32. package/dist/node/index.d.ts +2 -4
  33. package/dist/node/index.js +31 -1161
  34. package/dist/node/index.mjs +31 -736
  35. package/dist/search-dialog-EB3N4TYM.mjs +1 -0
  36. package/dist/types-BuZWFT7r.d.ts +159 -0
  37. package/dist/types-CvT-SGbK.d.mts +159 -0
  38. package/dist/use-routes-5bAtAAYX.d.mts +30 -0
  39. package/dist/use-routes-BefRXY3v.d.ts +30 -0
  40. package/package.json +34 -12
  41. package/src/client/app/config-context.tsx +18 -0
  42. package/src/client/app/docs-layout.tsx +14 -0
  43. package/src/client/app/index.tsx +137 -262
  44. package/src/client/app/mdx-component.tsx +52 -0
  45. package/src/client/app/mdx-components-context.tsx +23 -0
  46. package/src/client/app/mdx-page.tsx +20 -0
  47. package/src/client/app/preload.tsx +38 -30
  48. package/src/client/app/router.tsx +30 -0
  49. package/src/client/app/scroll-handler.tsx +40 -0
  50. package/src/client/app/theme-context.tsx +75 -0
  51. package/src/client/components/default-layout.tsx +80 -0
  52. package/src/client/components/docs-layout.tsx +105 -0
  53. package/src/client/components/icons-dev.tsx +74 -0
  54. package/src/client/components/mdx/admonition.tsx +107 -0
  55. package/src/client/components/mdx/badge.tsx +41 -0
  56. package/src/client/components/mdx/button.tsx +35 -0
  57. package/src/client/components/mdx/card.tsx +124 -0
  58. package/src/client/components/mdx/code-block.tsx +119 -0
  59. package/src/client/components/mdx/component-preview.tsx +47 -0
  60. package/src/client/components/mdx/component-props.tsx +83 -0
  61. package/src/client/components/mdx/field.tsx +66 -0
  62. package/src/client/components/mdx/file-tree.tsx +287 -0
  63. package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
  64. package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
  65. package/src/client/components/mdx/hooks/useTable.ts +74 -0
  66. package/src/client/components/mdx/hooks/useTabs.ts +68 -0
  67. package/src/client/components/mdx/image.tsx +23 -0
  68. package/src/client/components/mdx/index.ts +53 -0
  69. package/src/client/components/mdx/link.tsx +38 -0
  70. package/src/client/components/mdx/list.tsx +192 -0
  71. package/src/client/components/mdx/table.tsx +156 -0
  72. package/src/client/components/mdx/tabs.tsx +135 -0
  73. package/src/client/components/mdx/video.tsx +68 -0
  74. package/src/client/components/primitives/breadcrumbs.tsx +79 -0
  75. package/src/client/components/primitives/button-group.tsx +54 -0
  76. package/src/client/components/primitives/button.tsx +145 -0
  77. package/src/client/components/primitives/helpers/observer.ts +120 -0
  78. package/src/client/components/primitives/index.ts +17 -0
  79. package/src/client/components/primitives/link.tsx +122 -0
  80. package/src/client/components/primitives/menu.tsx +159 -0
  81. package/src/client/components/primitives/navbar.tsx +359 -0
  82. package/src/client/components/primitives/navigation-menu.tsx +116 -0
  83. package/src/client/components/primitives/on-this-page.tsx +461 -0
  84. package/src/client/components/primitives/page-nav.tsx +87 -0
  85. package/src/client/components/primitives/popover.tsx +47 -0
  86. package/src/client/components/primitives/search-dialog.tsx +183 -0
  87. package/src/client/components/primitives/sidebar.tsx +154 -0
  88. package/src/client/components/primitives/tabs.tsx +90 -0
  89. package/src/client/components/primitives/tooltip.tsx +83 -0
  90. package/src/client/components/primitives/types.ts +11 -0
  91. package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
  92. package/src/client/components/ui-base/copy-markdown.tsx +112 -0
  93. package/src/client/components/ui-base/error-boundary.tsx +52 -0
  94. package/src/client/components/ui-base/github-stars.tsx +27 -0
  95. package/src/client/components/ui-base/head.tsx +69 -0
  96. package/src/client/components/ui-base/loading.tsx +87 -0
  97. package/src/client/components/ui-base/navbar.tsx +138 -0
  98. package/src/client/components/ui-base/not-found.tsx +24 -0
  99. package/src/client/components/ui-base/on-this-page.tsx +152 -0
  100. package/src/client/components/ui-base/page-nav.tsx +39 -0
  101. package/src/client/components/ui-base/powered-by.tsx +19 -0
  102. package/src/client/components/ui-base/progress-bar.tsx +67 -0
  103. package/src/client/components/ui-base/search-dialog.tsx +82 -0
  104. package/src/client/components/ui-base/sidebar.tsx +104 -0
  105. package/src/client/components/ui-base/tabs.tsx +65 -0
  106. package/src/client/components/ui-base/theme-toggle.tsx +32 -0
  107. package/src/client/hooks/index.ts +12 -0
  108. package/src/client/hooks/use-breadcrumbs.ts +22 -0
  109. package/src/client/hooks/use-i18n.ts +84 -0
  110. package/src/client/hooks/use-localized-to.ts +95 -0
  111. package/src/client/hooks/use-location.ts +5 -0
  112. package/src/client/hooks/use-navbar.ts +60 -0
  113. package/src/client/hooks/use-onthispage.ts +23 -0
  114. package/src/client/hooks/use-page-nav.ts +22 -0
  115. package/src/client/hooks/use-routes.ts +72 -0
  116. package/src/client/hooks/use-search.ts +71 -0
  117. package/src/client/hooks/use-sidebar.ts +49 -0
  118. package/src/client/hooks/use-tabs.ts +43 -0
  119. package/src/client/hooks/use-version.ts +78 -0
  120. package/src/client/index.ts +55 -17
  121. package/src/client/integrations/codesandbox.ts +179 -0
  122. package/src/client/ssr.tsx +27 -16
  123. package/src/client/theme/neutral.css +360 -0
  124. package/src/client/types.ts +131 -27
  125. package/src/client/utils/cn.ts +6 -0
  126. package/src/client/utils/copy-clipboard.ts +22 -0
  127. package/src/client/utils/get-base-file-path.ts +21 -0
  128. package/src/client/utils/github.ts +121 -0
  129. package/src/client/utils/use-on-change.ts +15 -0
  130. package/src/client/virtual.d.ts +24 -0
  131. package/src/node/cache.ts +156 -156
  132. package/src/node/config.ts +159 -103
  133. package/src/node/index.ts +13 -13
  134. package/src/node/mdx.ts +213 -61
  135. package/src/node/plugin/entry.ts +29 -18
  136. package/src/node/plugin/html.ts +11 -11
  137. package/src/node/plugin/index.ts +161 -84
  138. package/src/node/plugin/types.ts +2 -4
  139. package/src/node/routes/cache.ts +6 -6
  140. package/src/node/routes/index.ts +206 -113
  141. package/src/node/routes/parser.ts +102 -82
  142. package/src/node/routes/sorter.ts +15 -15
  143. package/src/node/routes/types.ts +24 -24
  144. package/src/node/ssg/index.ts +73 -47
  145. package/src/node/ssg/meta.ts +4 -4
  146. package/src/node/ssg/options.ts +5 -5
  147. package/src/node/ssg/sitemap.ts +14 -14
  148. package/src/node/utils.ts +54 -31
  149. package/tsconfig.json +25 -20
  150. package/tsup.config.ts +23 -14
  151. package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
  152. package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
  153. package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
  154. package/dist/Video-KNTY5BNO.mjs +0 -6
  155. package/dist/cache-KNL5B4EE.mjs +0 -12
  156. package/dist/chunk-7SFUJWTB.mjs +0 -211
  157. package/dist/chunk-FFBNU6IJ.mjs +0 -386
  158. package/dist/chunk-FMTOYQLO.mjs +0 -37
  159. package/dist/chunk-TKLQWU7H.mjs +0 -1920
  160. package/dist/chunk-Z7JHYNAS.mjs +0 -57
  161. package/dist/client/index.css +0 -2847
  162. package/dist/client/ssr.css +0 -2847
  163. package/dist/types-Dj-bfnC3.d.mts +0 -74
  164. package/dist/types-Dj-bfnC3.d.ts +0 -74
  165. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
  166. package/src/client/theme/components/CodeBlock/index.ts +0 -1
  167. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
  168. package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
  169. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
  170. package/src/client/theme/components/Playground/Playground.tsx +0 -180
  171. package/src/client/theme/components/Playground/index.ts +0 -1
  172. package/src/client/theme/components/Playground/playground.css +0 -238
  173. package/src/client/theme/components/Video/Video.tsx +0 -84
  174. package/src/client/theme/components/Video/index.ts +0 -1
  175. package/src/client/theme/components/Video/video.css +0 -41
  176. package/src/client/theme/components/mdx/Admonition.tsx +0 -80
  177. package/src/client/theme/components/mdx/Badge.tsx +0 -31
  178. package/src/client/theme/components/mdx/Button.tsx +0 -50
  179. package/src/client/theme/components/mdx/Card.tsx +0 -80
  180. package/src/client/theme/components/mdx/Field.tsx +0 -60
  181. package/src/client/theme/components/mdx/FileTree.tsx +0 -229
  182. package/src/client/theme/components/mdx/List.tsx +0 -57
  183. package/src/client/theme/components/mdx/Table.tsx +0 -151
  184. package/src/client/theme/components/mdx/Tabs.tsx +0 -123
  185. package/src/client/theme/components/mdx/index.ts +0 -27
  186. package/src/client/theme/components/mdx/mdx-components.css +0 -764
  187. package/src/client/theme/icons/bun.tsx +0 -62
  188. package/src/client/theme/icons/deno.tsx +0 -20
  189. package/src/client/theme/icons/discord.tsx +0 -12
  190. package/src/client/theme/icons/github.tsx +0 -15
  191. package/src/client/theme/icons/npm.tsx +0 -13
  192. package/src/client/theme/icons/pnpm.tsx +0 -72
  193. package/src/client/theme/icons/twitter.tsx +0 -12
  194. package/src/client/theme/styles/markdown.css +0 -394
  195. package/src/client/theme/styles/variables.css +0 -175
  196. package/src/client/theme/styles.css +0 -39
  197. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
  198. package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
  199. package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
  200. package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
  201. package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
  202. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
  203. package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
  204. package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
  205. package/src/client/theme/ui/Footer/footer.css +0 -32
  206. package/src/client/theme/ui/Head/Head.tsx +0 -69
  207. package/src/client/theme/ui/Head/index.ts +0 -1
  208. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
  209. package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
  210. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
  211. package/src/client/theme/ui/Layout/Layout.tsx +0 -203
  212. package/src/client/theme/ui/Layout/base.css +0 -106
  213. package/src/client/theme/ui/Layout/index.ts +0 -2
  214. package/src/client/theme/ui/Layout/pagination.css +0 -72
  215. package/src/client/theme/ui/Layout/responsive.css +0 -47
  216. package/src/client/theme/ui/Link/Link.tsx +0 -392
  217. package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
  218. package/src/client/theme/ui/Link/index.ts +0 -2
  219. package/src/client/theme/ui/Link/link-preview.css +0 -48
  220. package/src/client/theme/ui/Loading/Loading.tsx +0 -10
  221. package/src/client/theme/ui/Loading/index.ts +0 -1
  222. package/src/client/theme/ui/Loading/loading.css +0 -30
  223. package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
  224. package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
  225. package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
  226. package/src/client/theme/ui/Navbar/index.ts +0 -2
  227. package/src/client/theme/ui/Navbar/navbar.css +0 -347
  228. package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
  229. package/src/client/theme/ui/NotFound/index.ts +0 -1
  230. package/src/client/theme/ui/NotFound/not-found.css +0 -64
  231. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
  232. package/src/client/theme/ui/OnThisPage/index.ts +0 -1
  233. package/src/client/theme/ui/OnThisPage/toc.css +0 -152
  234. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
  235. package/src/client/theme/ui/PoweredBy/index.ts +0 -1
  236. package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
  237. package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
  238. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
  239. package/src/client/theme/ui/ProgressBar/index.ts +0 -1
  240. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
  241. package/src/client/theme/ui/SearchDialog/index.ts +0 -1
  242. package/src/client/theme/ui/SearchDialog/search.css +0 -152
  243. package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
  244. package/src/client/theme/ui/Sidebar/index.ts +0 -1
  245. package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
  246. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
  247. package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
  248. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
  249. package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
  250. package/src/client/utils.ts +0 -49
@@ -1,90 +1,22 @@
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
- import { CodeBlock } from "../theme/components/CodeBlock";
31
- const Video = lazy(() =>
32
- import("../theme/components/Video").then((m) => ({ default: m.Video })),
33
- );
34
- const PackageManagerTabs = lazy(() =>
35
- import("../theme/components/PackageManagerTabs").then((m) => ({
36
- default: m.PackageManagerTabs,
37
- })),
38
- );
39
- declare global {
40
- interface ImportMeta {
41
- env: Record<string, any>;
42
- }
43
- }
44
-
45
- import { PreloadProvider } from "./preload";
46
-
47
- const Heading = ({
48
- level,
49
- id,
50
- children,
51
- }: {
52
- level: number;
53
- id?: string;
54
- children: React.ReactNode;
55
- }) => {
56
- const Tag = `h${level}` as keyof JSX.IntrinsicElements;
57
- return (
58
- <Tag id={id} className="boltdocs-heading">
59
- {children}
60
- {id && (
61
- <a href={`#${id}`} className="header-anchor" aria-label="Anchor">
62
- <LucideLink size={16} />
63
- </a>
64
- )}
65
- </Tag>
66
- );
67
- };
68
-
69
- const mdxComponents = {
70
- h1: (props: any) => <Heading level={1} {...props} />,
71
- h2: (props: any) => <Heading level={2} {...props} />,
72
- h3: (props: any) => <Heading level={3} {...props} />,
73
- h4: (props: any) => <Heading level={4} {...props} />,
74
- h5: (props: any) => <Heading level={5} {...props} />,
75
- h6: (props: any) => <Heading level={6} {...props} />,
76
- pre: (props: any) => <CodeBlock {...props}>{props.children}</CodeBlock>,
77
- video: (props: any) => (
78
- <Suspense fallback={<div className="video-skeleton" />}>
79
- <Video {...props} />
80
- </Suspense>
81
- ),
82
- PackageManagerTabs: (props: any) => (
83
- <Suspense fallback={<div className="pkg-tabs-skeleton" />}>
84
- <PackageManagerTabs {...props} />
85
- </Suspense>
86
- ),
87
- };
1
+ import React, { useEffect, useState, useMemo } from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import { BrowserRouter, Routes, Route } from 'react-router-dom'
4
+ import { NotFound } from '@components/ui-base/not-found'
5
+ import { Loading } from '@components/ui-base/loading'
6
+ import { ThemeProvider } from './theme-context'
7
+ import type { ComponentRoute, CreateBoltdocsAppOptions } from '../types'
8
+ import type { BoltdocsConfig } from '@node/config'
9
+
10
+ import UserLayout from 'virtual:boltdocs-layout'
11
+
12
+ import { PreloadProvider } from './preload'
13
+ import { BoltdocsRouterProvider } from './router'
14
+ import { ConfigContext } from './config-context'
15
+ import { ScrollHandler } from './scroll-handler'
16
+ import { DocsLayout } from './docs-layout'
17
+ import { MdxPage } from './mdx-page'
18
+ import { MdxComponentsProvider } from './mdx-components-context'
19
+ import { mdxComponentsDefault } from './mdx-component'
88
20
 
89
21
  export function AppShell({
90
22
  initialRoutes,
@@ -93,194 +25,127 @@ export function AppShell({
93
25
  modules,
94
26
  hot,
95
27
  homePage: HomePage,
28
+ externalPages,
96
29
  components: customComponents = {},
97
30
  }: {
98
- initialRoutes: ComponentRoute[];
99
- initialConfig: any;
100
- docsDirName: string;
101
- modules: Record<string, () => Promise<any>>;
102
- hot?: any;
103
- homePage?: React.ComponentType;
104
- components?: Record<string, React.ComponentType<any>>;
31
+ initialRoutes: ComponentRoute[]
32
+ initialConfig: BoltdocsConfig
33
+ docsDirName: string
34
+ modules: Record<string, () => Promise<{ default: React.ComponentType<any> }>>
35
+ hot?: CreateBoltdocsAppOptions['hot']
36
+ homePage?: React.ComponentType
37
+ externalPages?: Record<string, React.ComponentType>
38
+ components?: Record<string, React.ComponentType>
105
39
  }) {
106
- const [routesInfo, setRoutesInfo] = useState<ComponentRoute[]>(initialRoutes);
107
- const [config] = useState(initialConfig);
40
+ const [routesInfo, setRoutesInfo] = useState<ComponentRoute[]>(initialRoutes)
41
+ const [config] = useState(initialConfig)
42
+ const computedExternalPages = externalPages || {}
108
43
 
109
- const resolveRoutes = (infos: ComponentRoute[]) => {
110
- return infos
44
+ const resolvedRoutes = useMemo(() => {
45
+ return routesInfo
111
46
  .filter(
112
- (route) => !(HomePage && (route.path === "/" || route.path === "")),
47
+ (route) =>
48
+ !(HomePage && (route.path === '/' || route.path === '')) &&
49
+ !computedExternalPages[route.path === '' ? '/' : route.path],
113
50
  )
114
51
  .map((route) => {
115
52
  const loaderKey = Object.keys(modules).find(
116
- (k) => k === `/${docsDirName}/${route.filePath}`,
117
- );
118
- const loader = loaderKey ? modules[loaderKey] : null;
53
+ (k) =>
54
+ k === `/${docsDirName}/${route.filePath}` || // Vite dev/build relative path
55
+ k.endsWith(`/${docsDirName}/${route.filePath}`) || // SSG absolute path fallback
56
+ k.endsWith(`/${docsDirName}\\${route.filePath.replace(/\\/g, '/')}`), // Windows fallback
57
+ )
58
+ const loader = loaderKey ? modules[loaderKey] : null
119
59
 
120
60
  return {
121
61
  ...route,
122
- Component: React.lazy(() => {
123
- if (!loader)
124
- return Promise.resolve({ default: () => <NotFound /> });
125
- return loader() as any;
62
+ Component: React.lazy<React.ComponentType<any>>(async () => {
63
+ if (!loader) return { default: NotFound as React.ComponentType<any> }
64
+ const mod = await loader()
65
+ return mod
126
66
  }),
127
- };
128
- });
129
- };
130
-
131
- const [resolvedRoutes, setResolvedRoutes] = useState<any[]>(() =>
132
- resolveRoutes(initialRoutes),
133
- );
67
+ }
68
+ })
69
+ }, [routesInfo, modules, docsDirName, HomePage, computedExternalPages])
134
70
 
135
71
  // Subscribe to HMR events
136
72
  useEffect(() => {
137
73
  if (hot) {
138
- hot.on("boltdocs:routes-update", (newRoutes: ComponentRoute[]) => {
139
- setRoutesInfo(newRoutes);
140
- });
74
+ hot.on('boltdocs:routes-update', (newRoutes: ComponentRoute[]) => {
75
+ setRoutesInfo(newRoutes)
76
+ })
141
77
  }
142
- }, [hot]);
78
+ }, [hot])
143
79
 
144
- // Sync resolved routes when info or modules change
145
- useEffect(() => {
146
- setResolvedRoutes(resolveRoutes(routesInfo));
147
- }, [routesInfo, modules, docsDirName]);
80
+ const allComponents = useMemo(
81
+ () => ({ ...mdxComponentsDefault, ...customComponents }),
82
+ [customComponents],
83
+ )
148
84
 
149
85
  return (
150
- <ConfigContext.Provider value={config}>
151
- <PreloadProvider routes={routesInfo} modules={modules}>
152
- <ScrollHandler />
153
- <Routes>
154
- {/* Custom home page WITHOUT docs layout */}
155
- {HomePage && (
156
- <Route
157
- path="/"
158
- element={
159
- <ThemeLayout
160
- config={config}
161
- routes={routesInfo}
162
- sidebar={null}
163
- toc={null}
164
- breadcrumbs={null}
165
- {...config.themeConfig?.layoutProps}
166
- >
167
- <HomePage />
168
- </ThemeLayout>
169
- }
170
- />
171
- )}
172
-
173
- {/* Documentation pages WITH sidebar + TOC layout */}
174
- <Route
175
- key="docs-layout"
176
- element={<DocsLayout config={config} routes={routesInfo} />}
177
- >
178
- {resolvedRoutes.map((route: any) => (
179
- <Route
180
- key={route.path}
181
- path={route.path === "" ? "/" : route.path}
182
- element={
183
- <React.Suspense fallback={<Loading />}>
184
- <MdxPage
185
- Component={route.Component}
186
- customComponents={customComponents}
86
+ <ThemeProvider>
87
+ <MdxComponentsProvider components={allComponents}>
88
+ <ConfigContext.Provider value={config}>
89
+ <BoltdocsRouterProvider>
90
+ <PreloadProvider routes={routesInfo} modules={modules}>
91
+ <ScrollHandler />
92
+ <Routes>
93
+ {/* Custom home page with user layout */}
94
+ {HomePage && (
95
+ <Route
96
+ path="/"
97
+ element={
98
+ <UserLayout>
99
+ <HomePage />
100
+ </UserLayout>
101
+ }
102
+ />
103
+ )}
104
+
105
+ {/* Custom External Pages with user layout */}
106
+ {Object.entries(computedExternalPages).map(
107
+ ([extPath, ExtComponent]) => (
108
+ <Route
109
+ key={extPath}
110
+ path={extPath}
111
+ element={
112
+ <UserLayout>
113
+ <ExtComponent />
114
+ </UserLayout>
115
+ }
187
116
  />
188
- </React.Suspense>
189
- }
190
- />
191
- ))}
192
- </Route>
193
-
194
- <Route
195
- path="*"
196
- element={
197
- <ThemeLayout
198
- config={config}
199
- routes={routesInfo}
200
- {...config.themeConfig?.layoutProps}
201
- >
202
- <NotFound />
203
- </ThemeLayout>
204
- }
205
- />
206
- </Routes>
207
- </PreloadProvider>
208
- </ConfigContext.Provider>
209
- );
210
- }
211
-
212
- /**
213
- * Handles scroll restoration and hash scrolling on navigation.
214
- */
215
- function ScrollHandler() {
216
- const { pathname, hash } = useLocation();
217
-
218
- useLayoutEffect(() => {
219
- const container = document.querySelector(".boltdocs-content");
220
- if (!container) return;
221
-
222
- if (hash) {
223
- const id = hash.replace("#", "");
224
- const element = document.getElementById(id);
225
- if (element) {
226
- const offset = 80;
227
- const containerRect = container.getBoundingClientRect().top;
228
- const elementRect = element.getBoundingClientRect().top;
229
- const elementPosition = elementRect - containerRect;
230
- const offsetPosition = elementPosition - offset + container.scrollTop;
231
-
232
- container.scrollTo({
233
- top: offsetPosition,
234
- behavior: "smooth",
235
- });
236
- return;
237
- }
238
- }
239
- container.scrollTo(0, 0);
240
- }, [pathname, hash]);
241
-
242
- return null;
243
- }
244
-
245
- /** Wrapper layout for doc pages (sidebar + content + TOC) */
246
- function DocsLayout({
247
- config,
248
- routes,
249
- }: {
250
- config: any;
251
- routes: ComponentRoute[];
252
- }) {
253
- return (
254
- <ThemeLayout
255
- config={config}
256
- routes={routes}
257
- {...config.themeConfig?.layoutProps}
258
- >
259
- <Outlet />
260
- </ThemeLayout>
261
- );
262
- }
263
-
264
- /**
265
- * Renders an MDX page securely, injecting required custom components.
266
- * For example, this overrides the default `<pre>` HTML tags emitted by MDX
267
- * with the Boltdocs `CodeBlock` component for syntax highlighting.
268
- *
269
- * @param props - Contains the dynamically loaded React component representing the MDX page
270
- */
271
- function MdxPage({
272
- Component,
273
- customComponents = {},
274
- }: {
275
- Component: React.LazyExoticComponent<any>;
276
- customComponents?: Record<string, React.ComponentType<any>>;
277
- }) {
278
- const allComponents = { ...mdxComponents, ...customComponents };
279
- return (
280
- <MDXProvider components={allComponents}>
281
- <Component />
282
- </MDXProvider>
283
- );
117
+ ),
118
+ )}
119
+
120
+ <Route key="docs-layout" element={<DocsLayout />}>
121
+ {resolvedRoutes.map((route) => (
122
+ <Route
123
+ key={route.path}
124
+ path={route.path === '' ? '/' : route.path}
125
+ element={
126
+ <React.Suspense fallback={<Loading />}>
127
+ <MdxPage Component={route.Component} />
128
+ </React.Suspense>
129
+ }
130
+ />
131
+ ))}
132
+ </Route>
133
+
134
+ <Route
135
+ path="*"
136
+ element={
137
+ <UserLayout>
138
+ <NotFound />
139
+ </UserLayout>
140
+ }
141
+ />
142
+ </Routes>
143
+ </PreloadProvider>
144
+ </BoltdocsRouterProvider>
145
+ </ConfigContext.Provider>
146
+ </MdxComponentsProvider>
147
+ </ThemeProvider>
148
+ )
284
149
  }
285
150
 
286
151
  /**
@@ -305,13 +170,22 @@ function MdxPage({
305
170
  * ```
306
171
  */
307
172
  export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
308
- const { target, routes, docsDirName, config, modules, hot, homePage } =
309
- options;
310
- const container = document.querySelector(target);
173
+ const {
174
+ target,
175
+ routes,
176
+ docsDirName,
177
+ config,
178
+ modules,
179
+ hot,
180
+ homePage,
181
+ externalPages,
182
+ components,
183
+ } = options
184
+ const container = document.querySelector(target)
311
185
  if (!container) {
312
186
  throw new Error(
313
187
  `[boltdocs] Mount target "${target}" not found in document.`,
314
- );
188
+ )
315
189
  }
316
190
 
317
191
  const app = (
@@ -324,16 +198,17 @@ export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
324
198
  modules={modules}
325
199
  hot={hot}
326
200
  homePage={homePage}
327
- components={options.components}
201
+ externalPages={externalPages}
202
+ components={components}
328
203
  />
329
204
  </BrowserRouter>
330
205
  </React.StrictMode>
331
- );
206
+ )
332
207
 
333
208
  // SSG pre-renders a shell with mock components for SEO crawlers.
334
209
  // We always use createRoot because the SSG output doesn't match the
335
210
  // real client-side component tree (components are lazy/dynamic).
336
211
  // Clear any SSG placeholder content before mounting.
337
- container.innerHTML = "";
338
- ReactDOM.createRoot(container as HTMLElement).render(app);
212
+ container.innerHTML = ''
213
+ ReactDOM.createRoot(container as HTMLElement).render(app)
339
214
  }
@@ -0,0 +1,52 @@
1
+ import type React from 'react'
2
+ import { Link as LucideLink } from 'lucide-react'
3
+ import * as MdxComponents from '@components/mdx'
4
+
5
+ const Heading = ({
6
+ level,
7
+ id,
8
+ children,
9
+ }: {
10
+ level: number
11
+ id?: string
12
+ children?: React.ReactNode
13
+ }) => {
14
+ const Tag = `h${level}` as keyof JSX.IntrinsicElements
15
+ return (
16
+ <Tag id={id} className="boltdocs-heading">
17
+ {children}
18
+ {id && (
19
+ <a href={`#${id}`} className="header-anchor" aria-label="Anchor">
20
+ <LucideLink size={16} />
21
+ </a>
22
+ )}
23
+ </Tag>
24
+ )
25
+ }
26
+
27
+ export const mdxComponentsDefault = {
28
+ ...MdxComponents,
29
+ h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
30
+ <Heading level={1} {...props} />
31
+ ),
32
+ h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
33
+ <Heading level={2} {...props} />
34
+ ),
35
+ h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
36
+ <Heading level={3} {...props} />
37
+ ),
38
+ h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
39
+ <Heading level={4} {...props} />
40
+ ),
41
+ h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
42
+ <Heading level={5} {...props} />
43
+ ),
44
+ h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
45
+ <Heading level={6} {...props} />
46
+ ),
47
+ pre: (props: React.HTMLAttributes<HTMLPreElement>) => (
48
+ <MdxComponents.CodeBlock {...props}>
49
+ {props.children}
50
+ </MdxComponents.CodeBlock>
51
+ ),
52
+ }
@@ -0,0 +1,23 @@
1
+ import React, { createContext, useContext } from 'react'
2
+
3
+ export type MdxComponentsType = Record<string, React.ComponentType<any>>
4
+
5
+ const MdxComponentsContext = createContext<MdxComponentsType>({})
6
+
7
+ export function useMdxComponents() {
8
+ return useContext(MdxComponentsContext)
9
+ }
10
+
11
+ export function MdxComponentsProvider({
12
+ components,
13
+ children,
14
+ }: {
15
+ components: MdxComponentsType
16
+ children: React.ReactNode
17
+ }) {
18
+ return (
19
+ <MdxComponentsContext.Provider value={components}>
20
+ {children}
21
+ </MdxComponentsContext.Provider>
22
+ )
23
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { MDXProvider } from '@mdx-js/react'
3
+ import { useMdxComponents } from './mdx-components-context'
4
+
5
+ /**
6
+ * Renders an MDX page securely, injecting required custom components.
7
+ * This overrides the default HTML tags emitted by MDX with stylized components.
8
+ */
9
+ export function MdxPage({
10
+ Component,
11
+ }: {
12
+ Component: React.ComponentType<any>
13
+ }) {
14
+ const allComponents = useMdxComponents()
15
+ return (
16
+ <MDXProvider components={allComponents as any}>
17
+ <Component />
18
+ </MDXProvider>
19
+ )
20
+ }
@@ -1,18 +1,18 @@
1
- import React, { createContext, useContext, useCallback } from "react";
2
- import { ComponentRoute } from "../types";
1
+ import { createContext, use, useCallback, useRef } from 'react'
2
+ import type { ComponentRoute } from '../types'
3
3
 
4
4
  interface PreloadContextType {
5
- preload: (path: string) => void;
6
- routes: ComponentRoute[];
5
+ preload: (path: string) => void
6
+ routes: ComponentRoute[]
7
7
  }
8
8
 
9
9
  const PreloadContext = createContext<PreloadContextType>({
10
10
  preload: () => {},
11
11
  routes: [],
12
- });
12
+ })
13
13
 
14
14
  export function usePreload() {
15
- return useContext(PreloadContext);
15
+ return use(PreloadContext)
16
16
  }
17
17
 
18
18
  export function PreloadProvider({
@@ -20,39 +20,47 @@ export function PreloadProvider({
20
20
  modules,
21
21
  children,
22
22
  }: {
23
- routes: ComponentRoute[];
24
- modules: Record<string, () => Promise<any>>;
25
- children: React.ReactNode;
23
+ routes: ComponentRoute[]
24
+ modules: Record<string, () => Promise<unknown>>
25
+ children: React.ReactNode
26
26
  }) {
27
+ const preloadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
28
+
27
29
  const preload = useCallback(
28
30
  (path: string) => {
29
- // Normalize path (remove hash and search)
30
- const cleanPath = path.split("#")[0].split("?")[0];
31
-
32
- // Support index routes matching "/"
33
- const route = routes.find(
34
- (r) => r.path === cleanPath || (cleanPath === "/" && r.path === ""),
35
- );
36
-
37
- if (route && route.filePath) {
38
- const loaderKey = Object.keys(modules).find((k) =>
39
- k.endsWith("/" + route.filePath),
40
- );
41
-
42
- if (loaderKey) {
43
- // Trigger the dynamic import
44
- modules[loaderKey]().catch((err: any) => {
45
- console.error(`[boltdocs] Failed to preload route ${path}:`, err);
46
- });
47
- }
31
+ if (preloadTimerRef.current) {
32
+ clearTimeout(preloadTimerRef.current)
48
33
  }
34
+
35
+ preloadTimerRef.current = setTimeout(() => {
36
+ // Normalize path (remove hash and search)
37
+ const cleanPath = path.split('#')[0].split('?')[0]
38
+
39
+ // Support index routes matching "/"
40
+ const route = routes.find(
41
+ (r) => r.path === cleanPath || (cleanPath === '/' && r.path === ''),
42
+ )
43
+
44
+ if (route?.filePath) {
45
+ const loaderKey = Object.keys(modules).find((k) =>
46
+ k.endsWith('/' + route.filePath),
47
+ )
48
+
49
+ if (loaderKey) {
50
+ // Trigger the dynamic import
51
+ modules[loaderKey]().catch((err: unknown) => {
52
+ console.error(`[boltdocs] Failed to preload route ${path}:`, err)
53
+ })
54
+ }
55
+ }
56
+ }, 100) // 100ms debounce
49
57
  },
50
58
  [routes, modules],
51
- );
59
+ )
52
60
 
53
61
  return (
54
62
  <PreloadContext.Provider value={{ preload, routes }}>
55
63
  {children}
56
64
  </PreloadContext.Provider>
57
- );
65
+ )
58
66
  }
@@ -0,0 +1,30 @@
1
+ import type React from 'react'
2
+ import { startTransition } from 'react'
3
+ import { RouterProvider } from 'react-aria-components'
4
+ import { useNavigate, useHref } from 'react-router-dom'
5
+
6
+ /**
7
+ * Connects React Aria Components (RAC) to React Router.
8
+ * This ensures all RAC links and buttons use client-side navigation
9
+ * instead of full page reloads.
10
+ */
11
+ export function BoltdocsRouterProvider({
12
+ children,
13
+ }: {
14
+ children: React.ReactNode
15
+ }) {
16
+ const navigate = useNavigate()
17
+
18
+ return (
19
+ <RouterProvider
20
+ navigate={(to, options) => {
21
+ startTransition(() => {
22
+ navigate(to, options)
23
+ })
24
+ }}
25
+ useHref={useHref}
26
+ >
27
+ {children as any}
28
+ </RouterProvider>
29
+ )
30
+ }