boltdocs 2.5.5 → 2.6.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 (166) hide show
  1. package/bin/boltdocs.js +2 -2
  2. package/dist/client/index.cjs +6 -0
  3. package/dist/client/index.d.cts +1560 -0
  4. package/dist/client/index.d.ts +1219 -922
  5. package/dist/client/index.js +6 -1
  6. package/dist/client/theme/neutral.css +428 -0
  7. package/dist/node/cli-entry.cjs +8 -0
  8. package/dist/node/cli-entry.d.cts +2 -0
  9. package/dist/node/cli-entry.d.mts +2 -1
  10. package/dist/node/cli-entry.mjs +7 -5
  11. package/dist/node/index.cjs +6 -0
  12. package/dist/node/index.d.cts +519 -0
  13. package/dist/node/index.d.mts +374 -422
  14. package/dist/node/index.mjs +6 -1
  15. package/dist/node-BgvNl2Ay.mjs +89 -0
  16. package/dist/node-vkbb0MK7.cjs +89 -0
  17. package/dist/package-CR0HF9x3.mjs +6 -0
  18. package/dist/package-Dgmsc_l5.cjs +6 -0
  19. package/dist/search-dialog-3lvKsbVG.js +6 -0
  20. package/dist/search-dialog-DMK5OpgH.cjs +6 -0
  21. package/dist/use-search-C9bxCqfF.js +6 -0
  22. package/dist/use-search-DcfZSunO.cjs +6 -0
  23. package/package.json +26 -25
  24. package/src/client/app/config-context.tsx +38 -5
  25. package/src/client/app/doc-page.tsx +34 -0
  26. package/src/client/app/mdx-component.tsx +2 -3
  27. package/src/client/app/mdx-components-context.tsx +27 -2
  28. package/src/client/app/routes-context.tsx +34 -0
  29. package/src/client/app/scroll-handler.tsx +7 -4
  30. package/src/client/app/theme-context.tsx +71 -67
  31. package/src/client/components/default-layout.tsx +34 -33
  32. package/src/client/components/docs-layout.tsx +1 -2
  33. package/src/client/components/icons-dev.tsx +36 -5
  34. package/src/client/components/mdx/admonition.tsx +11 -27
  35. package/src/client/components/mdx/badge.tsx +1 -1
  36. package/src/client/components/mdx/button.tsx +3 -3
  37. package/src/client/components/mdx/card.tsx +1 -1
  38. package/src/client/components/mdx/code-block.tsx +90 -80
  39. package/src/client/components/mdx/component-preview.tsx +1 -5
  40. package/src/client/components/mdx/component-props.tsx +1 -1
  41. package/src/client/components/mdx/field.tsx +4 -5
  42. package/src/client/components/mdx/file-tree.tsx +6 -3
  43. package/src/client/components/mdx/hooks/use-code-block.ts +2 -2
  44. package/src/client/components/mdx/image.tsx +1 -1
  45. package/src/client/components/mdx/link.tsx +2 -2
  46. package/src/client/components/mdx/list.tsx +1 -1
  47. package/src/client/components/mdx/table.tsx +1 -1
  48. package/src/client/components/mdx/tabs.tsx +1 -1
  49. package/src/client/components/primitives/breadcrumbs.tsx +1 -7
  50. package/src/client/components/primitives/button-group.tsx +1 -1
  51. package/src/client/components/primitives/button.tsx +1 -1
  52. package/src/client/components/primitives/code-block.tsx +113 -0
  53. package/src/client/components/primitives/link.tsx +23 -41
  54. package/src/client/components/primitives/menu.tsx +5 -6
  55. package/src/client/components/primitives/navbar.tsx +6 -18
  56. package/src/client/components/primitives/navigation-menu.tsx +4 -4
  57. package/src/client/components/primitives/on-this-page.tsx +6 -10
  58. package/src/client/components/primitives/page-nav.tsx +4 -9
  59. package/src/client/components/primitives/popover.tsx +1 -1
  60. package/src/client/components/primitives/search-dialog.tsx +3 -6
  61. package/src/client/components/primitives/sidebar.tsx +80 -22
  62. package/src/client/components/primitives/skeleton.tsx +1 -1
  63. package/src/client/components/primitives/tabs.tsx +4 -11
  64. package/src/client/components/primitives/tooltip.tsx +3 -3
  65. package/src/client/components/ui-base/breadcrumbs.tsx +4 -6
  66. package/src/client/components/ui-base/copy-markdown.tsx +2 -7
  67. package/src/client/components/ui-base/github-stars.tsx +2 -2
  68. package/src/client/components/ui-base/head.tsx +58 -51
  69. package/src/client/components/ui-base/loading.tsx +2 -2
  70. package/src/client/components/ui-base/navbar.tsx +12 -14
  71. package/src/client/components/ui-base/not-found.tsx +1 -1
  72. package/src/client/components/ui-base/on-this-page.tsx +6 -6
  73. package/src/client/components/ui-base/page-nav.tsx +4 -8
  74. package/src/client/components/ui-base/search-dialog.tsx +10 -8
  75. package/src/client/components/ui-base/sidebar.tsx +76 -23
  76. package/src/client/components/ui-base/tabs.tsx +9 -8
  77. package/src/client/components/ui-base/theme-toggle.tsx +2 -2
  78. package/src/client/hooks/use-i18n.ts +3 -3
  79. package/src/client/hooks/use-localized-to.ts +1 -1
  80. package/src/client/hooks/use-navbar.ts +8 -6
  81. package/src/client/hooks/use-routes.ts +19 -11
  82. package/src/client/hooks/use-search.ts +1 -1
  83. package/src/client/hooks/use-sidebar.ts +48 -2
  84. package/src/client/hooks/use-tabs.ts +6 -2
  85. package/src/client/hooks/use-version.ts +3 -3
  86. package/src/client/index.ts +22 -22
  87. package/src/client/ssg/boltdocs-shell.tsx +127 -0
  88. package/src/client/ssg/create-routes.tsx +179 -0
  89. package/src/client/ssg/index.ts +3 -0
  90. package/src/client/ssg/mdx-page.tsx +37 -0
  91. package/src/client/store/boltdocs-context.tsx +66 -0
  92. package/src/client/theme/neutral.css +90 -50
  93. package/src/client/types.ts +5 -33
  94. package/src/client/utils/react-to-text.ts +34 -0
  95. package/CHANGELOG.md +0 -98
  96. package/dist/cache-3FOEPC2P.mjs +0 -1
  97. package/dist/chunk-5B5NKOW6.mjs +0 -77
  98. package/dist/chunk-J2PTDWZM.mjs +0 -1
  99. package/dist/chunk-TP5KMRD3.mjs +0 -1
  100. package/dist/chunk-Y4RE5KI7.mjs +0 -1
  101. package/dist/client/index.d.mts +0 -1263
  102. package/dist/client/index.mjs +0 -1
  103. package/dist/client/ssr.d.mts +0 -78
  104. package/dist/client/ssr.d.ts +0 -78
  105. package/dist/client/ssr.js +0 -1
  106. package/dist/client/ssr.mjs +0 -1
  107. package/dist/node/cli-entry.d.ts +0 -1
  108. package/dist/node/cli-entry.js +0 -82
  109. package/dist/node/index.d.ts +0 -567
  110. package/dist/node/index.js +0 -77
  111. package/dist/package-QFIAETHR.mjs +0 -1
  112. package/dist/search-dialog-O6VLVSOA.mjs +0 -1
  113. package/src/client/app/index.tsx +0 -345
  114. package/src/client/app/mdx-page.tsx +0 -15
  115. package/src/client/app/preload.tsx +0 -66
  116. package/src/client/app/router.tsx +0 -30
  117. package/src/client/integrations/codesandbox.ts +0 -179
  118. package/src/client/integrations/index.ts +0 -1
  119. package/src/client/ssr.tsx +0 -65
  120. package/src/client/store/use-boltdocs-store.ts +0 -44
  121. package/src/node/cache.ts +0 -408
  122. package/src/node/cli/build.ts +0 -53
  123. package/src/node/cli/dev.ts +0 -22
  124. package/src/node/cli/doctor.ts +0 -243
  125. package/src/node/cli/index.ts +0 -9
  126. package/src/node/cli/ui.ts +0 -54
  127. package/src/node/cli-entry.ts +0 -24
  128. package/src/node/config.ts +0 -382
  129. package/src/node/errors.ts +0 -44
  130. package/src/node/index.ts +0 -84
  131. package/src/node/mdx/cache.ts +0 -12
  132. package/src/node/mdx/highlighter.ts +0 -47
  133. package/src/node/mdx/index.ts +0 -122
  134. package/src/node/mdx/rehype-shiki.ts +0 -62
  135. package/src/node/mdx/remark-code-meta.ts +0 -35
  136. package/src/node/mdx/remark-shiki.ts +0 -61
  137. package/src/node/plugin/entry.ts +0 -87
  138. package/src/node/plugin/html.ts +0 -99
  139. package/src/node/plugin/index.ts +0 -478
  140. package/src/node/plugin/types.ts +0 -9
  141. package/src/node/plugins/index.ts +0 -17
  142. package/src/node/plugins/plugin-errors.ts +0 -62
  143. package/src/node/plugins/plugin-lifecycle.ts +0 -117
  144. package/src/node/plugins/plugin-sandbox.ts +0 -59
  145. package/src/node/plugins/plugin-store.ts +0 -54
  146. package/src/node/plugins/plugin-types.ts +0 -107
  147. package/src/node/plugins/plugin-validator.ts +0 -105
  148. package/src/node/routes/cache.ts +0 -28
  149. package/src/node/routes/index.ts +0 -293
  150. package/src/node/routes/parser.ts +0 -262
  151. package/src/node/routes/sorter.ts +0 -42
  152. package/src/node/routes/types.ts +0 -61
  153. package/src/node/schema/config.ts +0 -195
  154. package/src/node/schema/frontmatter.ts +0 -17
  155. package/src/node/search/index.ts +0 -55
  156. package/src/node/security/constants/index.ts +0 -10
  157. package/src/node/security/csp.ts +0 -31
  158. package/src/node/security/headers.ts +0 -27
  159. package/src/node/ssg/index.ts +0 -205
  160. package/src/node/ssg/meta.ts +0 -33
  161. package/src/node/ssg/options.ts +0 -15
  162. package/src/node/ssg/robots.ts +0 -53
  163. package/src/node/ssg/sitemap.ts +0 -55
  164. package/src/node/utils.ts +0 -349
  165. package/tsconfig.json +0 -26
  166. package/tsup.config.ts +0 -56
@@ -1,5 +1,5 @@
1
1
  import { useLocation } from 'react-router-dom'
2
- import { useConfig } from '@client/app/config-context'
2
+ import { useConfig } from '../app/config-context'
3
3
  import type { ComponentRoute } from '../types'
4
4
 
5
5
  export function useSidebar(routes: ComponentRoute[]) {
@@ -41,7 +41,53 @@ export function useSidebar(routes: ComponentRoute[]) {
41
41
  }
42
42
  }
43
43
 
44
- const groups = Array.from(groupsMap.values())
44
+ const groups = Array.from(groupsMap.values()).map((group) => {
45
+ const subRouteParents = new Map<string, ComponentRoute>()
46
+ const subRouteChildren = new Map<string, ComponentRoute[]>()
47
+
48
+ // First pass: Categorize as parent or child
49
+ for (const route of group.routes) {
50
+ if (route.subRouteGroup) {
51
+ const isParent =
52
+ route.path.endsWith(`/${route.subRouteGroup}`) ||
53
+ route.path.endsWith(`/${route.subRouteGroup}/`)
54
+
55
+ if (isParent && !subRouteParents.has(route.subRouteGroup)) {
56
+ subRouteParents.set(route.subRouteGroup, route)
57
+ } else {
58
+ if (!subRouteChildren.has(route.subRouteGroup)) {
59
+ subRouteChildren.set(route.subRouteGroup, [])
60
+ }
61
+ subRouteChildren.get(route.subRouteGroup)!.push(route)
62
+ }
63
+ }
64
+ }
65
+
66
+ const finalRoutes: ComponentRoute[] = []
67
+ const seenSubGroups = new Set<string>()
68
+
69
+ // Second pass: Assemble maintaining mostly original order
70
+ for (const route of group.routes) {
71
+ if (route.subRouteGroup) {
72
+ if (!seenSubGroups.has(route.subRouteGroup)) {
73
+ seenSubGroups.add(route.subRouteGroup)
74
+ const parent = subRouteParents.get(route.subRouteGroup)
75
+ const children = subRouteChildren.get(route.subRouteGroup) || []
76
+
77
+ if (parent) {
78
+ finalRoutes.push({ ...parent, subRoutes: children })
79
+ } else {
80
+ // Fallback
81
+ finalRoutes.push(...children)
82
+ }
83
+ }
84
+ } else {
85
+ finalRoutes.push(route)
86
+ }
87
+ }
88
+
89
+ return { ...group, routes: finalRoutes }
90
+ })
45
91
 
46
92
  return {
47
93
  groups,
@@ -1,6 +1,6 @@
1
1
  import { useLocation } from 'react-router-dom'
2
2
  import { useEffect, useState, useRef } from 'react'
3
- import type { ComponentRoute, BoltdocsTab } from '@client/types'
3
+ import type { ComponentRoute, BoltdocsTab } from '../types'
4
4
 
5
5
  export function useTabs(
6
6
  tabs: BoltdocsTab[] = [],
@@ -14,7 +14,11 @@ export function useTabs(
14
14
  width: 0,
15
15
  })
16
16
 
17
- const activeRoute = routes.find((r) => r.path === location.pathname)
17
+ const normalize = (p: string) =>
18
+ p.endsWith('/') && p.length > 1 ? p.slice(0, -1) : p
19
+ const currentPath = normalize(location.pathname)
20
+
21
+ const activeRoute = routes.find((r) => normalize(r.path) === currentPath)
18
22
  const activeTabId = activeRoute?.tab?.toLowerCase()
19
23
  const activeIndex = tabs.findIndex(
20
24
  (tab) => tab.id.toLowerCase() === activeTabId,
@@ -1,7 +1,7 @@
1
1
  import { useNavigate } from 'react-router-dom'
2
- import { getBaseFilePath } from '@client/utils/get-base-file-path'
2
+ import { getBaseFilePath } from '../utils/get-base-file-path'
3
3
  import { useRoutes } from './use-routes'
4
- import { useBoltdocsStore } from '../store/use-boltdocs-store'
4
+ import { useBoltdocsContext } from '../store/boltdocs-context'
5
5
 
6
6
  export interface VersionOption {
7
7
  key: string
@@ -26,7 +26,7 @@ export function useVersion(): UseVersionReturn {
26
26
  const { allRoutes, currentRoute, currentVersion, currentLocale, config } =
27
27
  routeContext
28
28
  const versions = config.versions
29
- const setVersion = useBoltdocsStore((s) => s.setVersion)
29
+ const { setVersion } = useBoltdocsContext()
30
30
 
31
31
  const handleVersionChange = (version: string) => {
32
32
  if (!versions || version === currentVersion) return
@@ -1,35 +1,35 @@
1
- export type { BoltdocsConfig, BoltdocsThemeConfig } from '../node/config'
1
+ export type { BoltdocsConfig, BoltdocsThemeConfig } from '../shared/types'
2
+ export { defineConfig } from '../shared/config-utils'
2
3
  export type {
3
4
  ComponentRoute,
4
- CreateBoltdocsAppOptions,
5
5
  LayoutProps,
6
6
  } from './types'
7
- export { createBoltdocsApp } from './app'
8
- export { useConfig } from '@client/app/config-context'
9
- export { useTheme } from '@client/app/theme-context'
10
- export { useRoutes } from '@client/hooks/use-routes'
11
- export { useMdxComponents } from '@client/app/mdx-components-context'
7
+ export * from './ssg'
8
+ export { useConfig } from './app/config-context'
9
+ export { useTheme } from './app/theme-context'
10
+ export { useRoutes } from './hooks/use-routes'
11
+ export { useMdxComponents } from './app/mdx-components-context'
12
12
 
13
13
  // Hooks
14
- export * from '@hooks/index'
14
+ export * from './hooks/index'
15
15
  // Composable layout building blocks
16
- export { DocsLayout } from '@components/docs-layout'
17
- export { DefaultLayout } from '@components/default-layout'
16
+ export { DocsLayout } from './components/docs-layout'
17
+ export { DefaultLayout } from './components/default-layout'
18
18
 
19
19
  // Default UI components (for use in custom layout.tsx)
20
- export { Navbar } from '@components/ui-base/navbar'
21
- export { Sidebar } from '@components/ui-base/sidebar'
22
- export { OnThisPage } from '@components/ui-base/on-this-page'
23
- export { Head } from '@components/ui-base/head'
24
- export { Breadcrumbs } from '@components/ui-base/breadcrumbs'
25
- export { PageNav } from '@components/ui-base/page-nav'
26
- export { ErrorBoundary } from '@components/ui-base/error-boundary'
27
- export { CopyMarkdown } from '@components/ui-base/copy-markdown'
20
+ export { Navbar } from './components/ui-base/navbar'
21
+ export { Sidebar } from './components/ui-base/sidebar'
22
+ export { OnThisPage } from './components/ui-base/on-this-page'
23
+ export { Head } from './components/ui-base/head'
24
+ export { Breadcrumbs } from './components/ui-base/breadcrumbs'
25
+ export { PageNav } from './components/ui-base/page-nav'
26
+ export { ErrorBoundary } from './components/ui-base/error-boundary'
27
+ export { CopyMarkdown } from './components/ui-base/copy-markdown'
28
28
 
29
- export { NotFound } from '@components/ui-base/not-found'
30
- export { Loading } from '@components/ui-base/loading'
31
- export { CodeBlock } from '@components/mdx/code-block'
32
- export { Video } from '@components/mdx/video'
29
+ export { NotFound } from './components/ui-base/not-found'
30
+ export { Loading } from './components/ui-base/loading'
31
+ export { CodeBlock } from './components/mdx/code-block'
32
+ export { Video } from './components/mdx/video'
33
33
 
34
34
  // MDX Components
35
35
  export {
@@ -0,0 +1,127 @@
1
+ import { useEffect, useMemo } from 'react'
2
+ import { Outlet, useLocation, useNavigate } from 'react-router-dom'
3
+ import { RouterProvider } from 'react-aria-components'
4
+ import { BoltdocsProvider, useBoltdocsContext } from '../store/boltdocs-context'
5
+ import { ThemeProvider } from '../app/theme-context'
6
+ import { MdxComponentsProvider } from '../app/mdx-components-context'
7
+ import { HelmetProvider } from 'react-helmet-async'
8
+ import { ConfigContext } from '../app/config-context'
9
+ import { ScrollHandler } from '../app/scroll-handler'
10
+ import { mdxComponentsDefault } from '../app/mdx-component'
11
+ import { RoutesProvider } from '../app/routes-context'
12
+ import type { BoltdocsConfig } from '../../shared/types'
13
+ import type { ComponentRoute } from '../types'
14
+
15
+ import virtualCustomComponents from 'virtual:boltdocs-mdx-components'
16
+
17
+ /**
18
+ * Updates the HTML lang and dir attributes based on the current locale configuration.
19
+ */
20
+ function I18nUpdater({ config }: { config: BoltdocsConfig }) {
21
+ const { currentLocale } = useBoltdocsContext()
22
+
23
+ useEffect(() => {
24
+ if (!config.i18n || typeof document === 'undefined') return
25
+ const locale = currentLocale || config.i18n.defaultLocale
26
+ const localeConfig = config.i18n.localeConfigs?.[locale]
27
+ document.documentElement.lang = localeConfig?.htmlLang || locale || 'en'
28
+ document.documentElement.dir = localeConfig?.direction || 'ltr'
29
+ }, [currentLocale, config.i18n])
30
+
31
+ return null
32
+ }
33
+
34
+ /**
35
+ * Synchronizes the Zustand store with the current URL pathname.
36
+ */
37
+ function StoreSync({ config }: { config: BoltdocsConfig }) {
38
+ const location = useLocation()
39
+ const { setLocale, setVersion, currentLocale, currentVersion } =
40
+ useBoltdocsContext()
41
+
42
+ useEffect(() => {
43
+ const parts = location.pathname.split('/').filter(Boolean)
44
+ let cIdx = 0
45
+ let detectedVersion = config.versions?.defaultVersion
46
+ let detectedLocale = config.i18n?.defaultLocale
47
+
48
+ // 0. Skip docs prefix if present
49
+ if (parts[cIdx] === 'docs') cIdx++
50
+
51
+ // 1. Version detection
52
+ if (config.versions && parts.length > cIdx) {
53
+ const versionMatch = config.versions.versions.find(
54
+ (v) => v.path === parts[cIdx],
55
+ )
56
+ if (versionMatch) {
57
+ detectedVersion = versionMatch.path
58
+ cIdx++
59
+ }
60
+ }
61
+
62
+ // 2. Locale detection
63
+ if (
64
+ config.i18n &&
65
+ parts.length > cIdx &&
66
+ config.i18n.locales[parts[cIdx]]
67
+ ) {
68
+ detectedLocale = parts[cIdx]
69
+ } else if (config.i18n && parts.length === 0) {
70
+ detectedLocale = currentLocale || config.i18n.defaultLocale
71
+ }
72
+
73
+ if (detectedLocale !== currentLocale) setLocale(detectedLocale || '')
74
+ if (detectedVersion !== currentVersion) setVersion(detectedVersion ?? '')
75
+ }, [
76
+ location.pathname,
77
+ config,
78
+ setLocale,
79
+ setVersion,
80
+ currentLocale,
81
+ currentVersion,
82
+ ])
83
+
84
+ return null
85
+ }
86
+
87
+ export function BoltdocsShell({
88
+ config,
89
+ routes,
90
+ components = {},
91
+ }: {
92
+ config: BoltdocsConfig
93
+ routes: ComponentRoute[]
94
+ components?: Record<string, React.ComponentType>
95
+ }) {
96
+ const allComponents = useMemo(
97
+ () => ({
98
+ ...mdxComponentsDefault,
99
+ ...virtualCustomComponents,
100
+ ...components,
101
+ }),
102
+ [components],
103
+ )
104
+
105
+ const navigate = useNavigate()
106
+
107
+ return (
108
+ <HelmetProvider>
109
+ <BoltdocsProvider>
110
+ <ThemeProvider>
111
+ <MdxComponentsProvider components={allComponents}>
112
+ <ConfigContext.Provider value={config}>
113
+ <RoutesProvider routes={routes}>
114
+ <RouterProvider navigate={navigate}>
115
+ <ScrollHandler />
116
+ <StoreSync config={config} />
117
+ <I18nUpdater config={config} />
118
+ <Outlet />
119
+ </RouterProvider>
120
+ </RoutesProvider>
121
+ </ConfigContext.Provider>
122
+ </MdxComponentsProvider>
123
+ </ThemeProvider>
124
+ </BoltdocsProvider>
125
+ </HelmetProvider>
126
+ )
127
+ }
@@ -0,0 +1,179 @@
1
+ import type { RouteRecord } from '@bdocs/ssg'
2
+ import type { ComponentRoute, BoltdocsConfig } from '../types'
3
+ import { MdxPage } from './mdx-page'
4
+ import { BoltdocsShell } from './boltdocs-shell'
5
+ import { NotFound } from '../components/ui-base/not-found'
6
+
7
+ interface CreateRoutesOptions {
8
+ routesData: ComponentRoute[]
9
+ config: BoltdocsConfig
10
+ mdxModules: Record<string, { default?: React.ComponentType }>
11
+ Layout: React.ComponentType<{ children: React.ReactNode }>
12
+ homePage?: React.ComponentType
13
+ externalPages?: Record<string, React.ComponentType>
14
+ externalLayout?: React.ComponentType<{ children: React.ReactNode }>
15
+ components?: Record<string, React.ComponentType>
16
+ }
17
+
18
+ /**
19
+ * Finds the matching module key from import.meta.glob for a given filePath.
20
+ */
21
+ function findModuleKey(
22
+ modules: Record<string, any>,
23
+ filePath: string,
24
+ ): string | undefined {
25
+ const normalizedFilePath = filePath.replace(/\\/g, '/')
26
+ return Object.keys(modules).find(
27
+ (key) =>
28
+ key.endsWith(`/${normalizedFilePath}`) ||
29
+ key.endsWith(normalizedFilePath),
30
+ )
31
+ }
32
+
33
+ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
34
+ const {
35
+ routesData,
36
+ config,
37
+ mdxModules,
38
+ Layout,
39
+ homePage: HomePage,
40
+ externalPages,
41
+ externalLayout,
42
+ components,
43
+ } = options
44
+
45
+ const EffectiveExternalLayout = externalLayout || Layout
46
+
47
+ const withBase = (path: string) => {
48
+ // Future support for base path in config
49
+ const base = config.base || '/'
50
+ if (path.startsWith(base)) return path
51
+ const b = base === '/' ? '' : base.replace(/\/$/, '')
52
+ const p = path.startsWith('/') ? path : `/${path}`
53
+ return `${b}${p}` || '/'
54
+ }
55
+
56
+ // 1. Documentation routes
57
+ const docRoutes: RouteRecord[] = routesData.map((route) => {
58
+ const moduleKey = findModuleKey(mdxModules, route.filePath)
59
+ const MDXComponent = moduleKey ? mdxModules[moduleKey]?.default : null
60
+
61
+ const path = withBase(route.path === '' ? '/' : route.path)
62
+
63
+ return {
64
+ path,
65
+ element: (
66
+ <MdxPage MDXComponent={MDXComponent} mdxComponents={components} />
67
+ ),
68
+ loader: async () => ({
69
+ path,
70
+ frontmatter: {
71
+ title: route.title,
72
+ description: route.description || '',
73
+ },
74
+ headings: route.headings || [],
75
+ filePath: route.filePath,
76
+ locale: route.locale,
77
+ version: route.version,
78
+ group: route.group,
79
+ groupTitle: route.groupTitle,
80
+ }),
81
+ getStaticPaths: () => [path],
82
+ }
83
+ })
84
+
85
+ const children: RouteRecord[] = [...docRoutes]
86
+
87
+ // 2. Home page route
88
+ if (HomePage) {
89
+ const homePaths = [withBase('/')]
90
+ if (config.i18n) {
91
+ Object.keys(config.i18n.locales).forEach((locale) => {
92
+ homePaths.push(withBase(`/${locale}`))
93
+ })
94
+ }
95
+
96
+ homePaths.forEach((path) => {
97
+ // Avoid duplicate routes if documentation also maps to '/'
98
+ if (!children.find((r) => r.path === path)) {
99
+ children.push({
100
+ path,
101
+ element: (
102
+ <EffectiveExternalLayout>
103
+ <HomePage />
104
+ </EffectiveExternalLayout>
105
+ ),
106
+ getStaticPaths: () => [path],
107
+ })
108
+ }
109
+ })
110
+ }
111
+
112
+ // 3. External pages
113
+ if (externalPages) {
114
+ Object.entries(externalPages).forEach(([rawPath, ExtComponent]) => {
115
+ const path = withBase(rawPath)
116
+ if (!children.find((r) => r.path === path)) {
117
+ children.push({
118
+ path,
119
+ element: (
120
+ <EffectiveExternalLayout>
121
+ <ExtComponent />
122
+ </EffectiveExternalLayout>
123
+ ),
124
+ getStaticPaths: () => [path],
125
+ })
126
+
127
+ // Also add i18n variants for external pages if needed
128
+ if (config.i18n) {
129
+ Object.keys(config.i18n.locales).forEach((locale) => {
130
+ const localePath = withBase(
131
+ `/${locale}${rawPath === '/' ? '' : rawPath}`,
132
+ )
133
+ if (!children.find((r) => r.path === localePath)) {
134
+ children.push({
135
+ path: localePath,
136
+ element: (
137
+ <EffectiveExternalLayout>
138
+ <ExtComponent />
139
+ </EffectiveExternalLayout>
140
+ ),
141
+ getStaticPaths: () => [localePath],
142
+ })
143
+ }
144
+ })
145
+ }
146
+ }
147
+ })
148
+ }
149
+
150
+ // --- 4. 404 catch-all ---
151
+ children.push({
152
+ path: '*',
153
+ element: (
154
+ <EffectiveExternalLayout>
155
+ <NotFound />
156
+ </EffectiveExternalLayout>
157
+ ),
158
+ })
159
+
160
+ // --- 5. Construct Metadata for UI Providers ---
161
+ // We need to pass the full metadata to BoltdocsShell so that Sidebar/Tabs can work.
162
+ const allMetadata: ComponentRoute[] = [...routesData]
163
+
164
+ // Wrap everything in the Boltdocs shell (providers)
165
+ return [
166
+ {
167
+ // No path = Layout Route
168
+ // This allows children to retain their absolute paths while being wrapped in the shell.
169
+ element: (
170
+ <BoltdocsShell
171
+ config={config}
172
+ routes={allMetadata}
173
+ components={components}
174
+ />
175
+ ),
176
+ children,
177
+ },
178
+ ]
179
+ }
@@ -0,0 +1,3 @@
1
+ export { createRoutes } from './create-routes'
2
+ export { MdxPage } from './mdx-page'
3
+ export { BoltdocsShell } from './boltdocs-shell'
@@ -0,0 +1,37 @@
1
+ import { useLoaderData } from 'react-router-dom'
2
+ import { DocPage } from '../app/doc-page'
3
+
4
+ /**
5
+ * Renders an MDX page by consuming pre-loaded module data.
6
+ * Uses DocPage to ensure consistent layout and metadata application.
7
+ */
8
+ export function MdxPage({
9
+ MDXComponent: propMDX,
10
+ mdxComponents: propComponents,
11
+ }: any) {
12
+ const data = useLoaderData() as any
13
+ const MDXComponent = propMDX || data?.MDXComponent
14
+ const components = propComponents || data?.mdxComponents
15
+
16
+ if (!MDXComponent) return null
17
+
18
+ return (
19
+ <DocPage
20
+ route={
21
+ {
22
+ path: data.path,
23
+ filePath: data.filePath,
24
+ title: data.frontmatter.title,
25
+ description: data.frontmatter.description,
26
+ headings: data.headings,
27
+ locale: data.locale,
28
+ version: data.version,
29
+ group: data.group,
30
+ groupTitle: data.groupTitle,
31
+ } as any
32
+ }
33
+ content={MDXComponent}
34
+ mdxComponents={components}
35
+ />
36
+ )
37
+ }
@@ -0,0 +1,66 @@
1
+ import { createContext, use, useMemo, useState } from 'react'
2
+
3
+ export interface BoltdocsState {
4
+ currentLocale: string
5
+ currentVersion: string
6
+ setLocale: (locale: string) => void
7
+ setVersion: (version: string) => void
8
+ hasHydrated: boolean
9
+ setHasHydrated: (hasHydrated: boolean) => void
10
+ }
11
+
12
+ const BOLTDOCS_CONTEXT_SYMBOL = Symbol.for('__BDOCS_BOLTDOCS_CONTEXT__')
13
+ const BOLTDOCS_INSTANCE_SYMBOL = Symbol.for('__BDOCS_BOLTDOCS_INSTANCE__')
14
+
15
+ const BoltdocsContext =
16
+ (globalThis as any)[BOLTDOCS_CONTEXT_SYMBOL] ||
17
+ ((globalThis as any)[BOLTDOCS_CONTEXT_SYMBOL] = createContext<
18
+ BoltdocsState | undefined
19
+ >(undefined))
20
+
21
+ export function BoltdocsProvider({ children }: { children: React.ReactNode }) {
22
+ const [locale, setLocale] = useState('')
23
+ const [version, setVersion] = useState('')
24
+ const [hasHydrated, setHasHydrated] = useState(false)
25
+
26
+ const value = useMemo(
27
+ () => ({
28
+ currentLocale: locale,
29
+ currentVersion: version,
30
+ setLocale,
31
+ setVersion,
32
+ hasHydrated,
33
+ setHasHydrated,
34
+ }),
35
+ [locale, version, hasHydrated],
36
+ )
37
+
38
+ // Sync with global registry for dual-package fallback
39
+ if (typeof globalThis !== 'undefined') {
40
+ ;(globalThis as any)[BOLTDOCS_INSTANCE_SYMBOL] = value
41
+ }
42
+
43
+ return (
44
+ <BoltdocsContext.Provider value={value}>
45
+ {children}
46
+ </BoltdocsContext.Provider>
47
+ )
48
+ }
49
+
50
+ export function useBoltdocsContext() {
51
+ const context = use(BoltdocsContext)
52
+
53
+ // Fallback to global registry if context is missing (dual-package hazard safety net)
54
+ if (
55
+ !context &&
56
+ typeof globalThis !== 'undefined' &&
57
+ (globalThis as any)[BOLTDOCS_INSTANCE_SYMBOL]
58
+ ) {
59
+ return (globalThis as any)[BOLTDOCS_INSTANCE_SYMBOL] as BoltdocsState
60
+ }
61
+
62
+ if (!context) {
63
+ throw new Error('useBoltdocsContext must be used within a BoltdocsProvider')
64
+ }
65
+ return context as BoltdocsState
66
+ }