boltdocs 2.5.6 → 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.
- package/bin/boltdocs.js +2 -2
- package/dist/client/index.cjs +6 -0
- package/dist/client/{index.d.mts → index.d.cts} +134 -252
- package/dist/client/index.d.ts +135 -253
- package/dist/client/index.js +1 -1
- package/dist/client/theme/neutral.css +90 -50
- package/dist/node/cli-entry.cjs +2 -2
- package/dist/node/cli-entry.mjs +2 -2
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +150 -205
- package/dist/node/index.d.mts +150 -205
- package/dist/node/index.mjs +1 -1
- package/dist/node-BgvNl2Ay.mjs +89 -0
- package/dist/node-vkbb0MK7.cjs +89 -0
- package/dist/{package-OFZf0s2j.mjs → package-CR0HF9x3.mjs} +1 -1
- package/dist/{package-BY8Jd2j4.cjs → package-Dgmsc_l5.cjs} +1 -1
- package/dist/search-dialog-3lvKsbVG.js +6 -0
- package/dist/search-dialog-DMK5OpgH.cjs +6 -0
- package/dist/use-search-C9bxCqfF.js +6 -0
- package/dist/use-search-DcfZSunO.cjs +6 -0
- package/package.json +19 -22
- package/src/client/app/config-context.tsx +38 -5
- package/src/client/app/doc-page.tsx +34 -0
- package/src/client/app/mdx-component.tsx +2 -3
- package/src/client/app/mdx-components-context.tsx +27 -2
- package/src/client/app/routes-context.tsx +34 -0
- package/src/client/app/scroll-handler.tsx +7 -4
- package/src/client/app/theme-context.tsx +71 -67
- package/src/client/components/default-layout.tsx +13 -14
- package/src/client/components/docs-layout.tsx +1 -2
- package/src/client/components/icons-dev.tsx +36 -5
- package/src/client/components/mdx/admonition.tsx +11 -27
- package/src/client/components/mdx/badge.tsx +1 -1
- package/src/client/components/mdx/button.tsx +3 -3
- package/src/client/components/mdx/card.tsx +1 -1
- package/src/client/components/mdx/code-block.tsx +90 -80
- package/src/client/components/mdx/component-preview.tsx +1 -5
- package/src/client/components/mdx/component-props.tsx +1 -1
- package/src/client/components/mdx/field.tsx +4 -5
- package/src/client/components/mdx/file-tree.tsx +6 -3
- package/src/client/components/mdx/hooks/use-code-block.ts +2 -2
- package/src/client/components/mdx/image.tsx +1 -1
- package/src/client/components/mdx/link.tsx +2 -2
- package/src/client/components/mdx/list.tsx +1 -1
- package/src/client/components/mdx/table.tsx +1 -1
- package/src/client/components/mdx/tabs.tsx +1 -1
- package/src/client/components/primitives/breadcrumbs.tsx +1 -7
- package/src/client/components/primitives/button-group.tsx +1 -1
- package/src/client/components/primitives/button.tsx +1 -1
- package/src/client/components/primitives/code-block.tsx +113 -0
- package/src/client/components/primitives/link.tsx +23 -41
- package/src/client/components/primitives/menu.tsx +5 -6
- package/src/client/components/primitives/navbar.tsx +6 -18
- package/src/client/components/primitives/navigation-menu.tsx +4 -4
- package/src/client/components/primitives/on-this-page.tsx +6 -10
- package/src/client/components/primitives/page-nav.tsx +4 -9
- package/src/client/components/primitives/popover.tsx +1 -1
- package/src/client/components/primitives/search-dialog.tsx +3 -6
- package/src/client/components/primitives/sidebar.tsx +80 -22
- package/src/client/components/primitives/skeleton.tsx +1 -1
- package/src/client/components/primitives/tabs.tsx +4 -11
- package/src/client/components/primitives/tooltip.tsx +3 -3
- package/src/client/components/ui-base/breadcrumbs.tsx +4 -6
- package/src/client/components/ui-base/copy-markdown.tsx +2 -7
- package/src/client/components/ui-base/github-stars.tsx +2 -2
- package/src/client/components/ui-base/head.tsx +58 -51
- package/src/client/components/ui-base/loading.tsx +2 -2
- package/src/client/components/ui-base/navbar.tsx +12 -14
- package/src/client/components/ui-base/not-found.tsx +1 -1
- package/src/client/components/ui-base/on-this-page.tsx +6 -6
- package/src/client/components/ui-base/page-nav.tsx +4 -8
- package/src/client/components/ui-base/search-dialog.tsx +10 -8
- package/src/client/components/ui-base/sidebar.tsx +76 -23
- package/src/client/components/ui-base/tabs.tsx +9 -8
- package/src/client/components/ui-base/theme-toggle.tsx +2 -2
- package/src/client/hooks/use-i18n.ts +3 -3
- package/src/client/hooks/use-localized-to.ts +1 -1
- package/src/client/hooks/use-navbar.ts +8 -6
- package/src/client/hooks/use-routes.ts +19 -11
- package/src/client/hooks/use-search.ts +1 -1
- package/src/client/hooks/use-sidebar.ts +48 -2
- package/src/client/hooks/use-tabs.ts +6 -2
- package/src/client/hooks/use-version.ts +3 -3
- package/src/client/index.ts +22 -22
- package/src/client/ssg/boltdocs-shell.tsx +127 -0
- package/src/client/ssg/create-routes.tsx +179 -0
- package/src/client/ssg/index.ts +3 -0
- package/src/client/ssg/mdx-page.tsx +37 -0
- package/src/client/store/boltdocs-context.tsx +46 -99
- package/src/client/theme/neutral.css +90 -50
- package/src/client/types.ts +5 -33
- package/src/client/utils/react-to-text.ts +34 -0
- package/dist/cache-Cr8W2zgZ.cjs +0 -6
- package/dist/cache-DFdakSmR.mjs +0 -6
- package/dist/client/index.mjs +0 -6
- package/dist/client/ssr.cjs +0 -6
- package/dist/client/ssr.d.cts +0 -80
- package/dist/client/ssr.d.mts +0 -80
- package/dist/client/ssr.mjs +0 -6
- package/dist/node-CWXme96p.mjs +0 -73
- package/dist/node-VYfhzGrh.cjs +0 -73
- package/dist/search-dialog-BeNyI_KQ.mjs +0 -6
- package/dist/search-dialog-dYsCAk5S.js +0 -6
- package/dist/use-search-D25n0PrV.mjs +0 -6
- package/dist/use-search-WuzdH1cJ.js +0 -6
- package/src/client/app/index.tsx +0 -348
- package/src/client/app/mdx-page.tsx +0 -15
- package/src/client/app/preload.tsx +0 -66
- package/src/client/app/router.tsx +0 -30
- package/src/client/integrations/codesandbox.ts +0 -179
- package/src/client/integrations/index.ts +0 -1
- package/src/client/ssr.tsx +0 -65
|
@@ -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,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
|
+
}
|
|
@@ -1,119 +1,66 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createContext, use, useMemo, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
interface BoltdocsState {
|
|
4
|
-
currentLocale: string
|
|
5
|
-
currentVersion: string
|
|
3
|
+
export interface BoltdocsState {
|
|
4
|
+
currentLocale: string
|
|
5
|
+
currentVersion: string
|
|
6
|
+
setLocale: (locale: string) => void
|
|
7
|
+
setVersion: (version: string) => void
|
|
6
8
|
hasHydrated: boolean
|
|
7
|
-
|
|
8
|
-
// Actions
|
|
9
|
-
setLocale: (locale: string | undefined) => void
|
|
10
|
-
setVersion: (version: string | undefined) => void
|
|
11
|
-
setHasHydrated: (val: boolean) => void
|
|
9
|
+
setHasHydrated: (hasHydrated: boolean) => void
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const BoltdocsContext = createContext<BoltdocsContextValue | undefined>(undefined)
|
|
17
|
-
|
|
18
|
-
const STORAGE_KEY = 'boltdocs-storage'
|
|
12
|
+
const BOLTDOCS_CONTEXT_SYMBOL = Symbol.for('__BDOCS_BOLTDOCS_CONTEXT__')
|
|
13
|
+
const BOLTDOCS_INSTANCE_SYMBOL = Symbol.for('__BDOCS_BOLTDOCS_INSTANCE__')
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const stored = localStorage.getItem(STORAGE_KEY)
|
|
26
|
-
if (stored) {
|
|
27
|
-
const parsed = JSON.parse(stored)
|
|
28
|
-
// zustand persist stores the entire state object under a "state" key
|
|
29
|
-
return parsed?.state || parsed
|
|
30
|
-
}
|
|
31
|
-
} catch (e) {
|
|
32
|
-
// ignore parse errors
|
|
33
|
-
}
|
|
34
|
-
return {}
|
|
35
|
-
}
|
|
15
|
+
const BoltdocsContext =
|
|
16
|
+
(globalThis as any)[BOLTDOCS_CONTEXT_SYMBOL] ||
|
|
17
|
+
((globalThis as any)[BOLTDOCS_CONTEXT_SYMBOL] = createContext<
|
|
18
|
+
BoltdocsState | undefined
|
|
19
|
+
>(undefined))
|
|
36
20
|
|
|
37
|
-
/**
|
|
38
|
-
* Provider component that wraps the app and manages state
|
|
39
|
-
*/
|
|
40
21
|
export function BoltdocsProvider({ children }: { children: React.ReactNode }) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const [currentLocale, setCurrentLocale] = useState<string | undefined>(
|
|
44
|
-
persisted.currentLocale
|
|
45
|
-
)
|
|
46
|
-
const [currentVersion, setCurrentVersion] = useState<string | undefined>(
|
|
47
|
-
persisted.currentVersion
|
|
48
|
-
)
|
|
22
|
+
const [locale, setLocale] = useState('')
|
|
23
|
+
const [version, setVersion] = useState('')
|
|
49
24
|
const [hasHydrated, setHasHydrated] = useState(false)
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
localStorage.setItem(
|
|
65
|
-
STORAGE_KEY,
|
|
66
|
-
JSON.stringify({ state: stateToPersist })
|
|
67
|
-
)
|
|
68
|
-
} catch (e) {
|
|
69
|
-
// ignore storage errors
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}, [currentLocale, currentVersion, hasHydrated])
|
|
73
|
-
|
|
74
|
-
const setLocale = useCallback((locale: string | undefined) => {
|
|
75
|
-
setCurrentLocale(locale)
|
|
76
|
-
}, [])
|
|
77
|
-
|
|
78
|
-
const setVersion = useCallback((version: string | undefined) => {
|
|
79
|
-
setCurrentVersion(version)
|
|
80
|
-
}, [])
|
|
81
|
-
|
|
82
|
-
const setHasHydratedAction = useCallback((val: boolean) => {
|
|
83
|
-
setHasHydrated(val)
|
|
84
|
-
}, [])
|
|
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
|
+
)
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
hasHydrated,
|
|
90
|
-
setLocale,
|
|
91
|
-
setVersion,
|
|
92
|
-
setHasHydrated: setHasHydratedAction,
|
|
38
|
+
// Sync with global registry for dual-package fallback
|
|
39
|
+
if (typeof globalThis !== 'undefined') {
|
|
40
|
+
;(globalThis as any)[BOLTDOCS_INSTANCE_SYMBOL] = value
|
|
93
41
|
}
|
|
94
42
|
|
|
95
43
|
return (
|
|
96
|
-
<BoltdocsContext.Provider value={value}>
|
|
44
|
+
<BoltdocsContext.Provider value={value}>
|
|
45
|
+
{children}
|
|
46
|
+
</BoltdocsContext.Provider>
|
|
97
47
|
)
|
|
98
48
|
}
|
|
99
49
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
|
|
106
62
|
if (!context) {
|
|
107
63
|
throw new Error('useBoltdocsContext must be used within a BoltdocsProvider')
|
|
108
64
|
}
|
|
109
|
-
return context
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Backwards-compatible hook that mimics the Zustand store API.
|
|
114
|
-
* Accepts a selector function and returns the selected value.
|
|
115
|
-
*/
|
|
116
|
-
export function useBoltdocsStore<T>(selector: (state: BoltdocsState) => T): T {
|
|
117
|
-
const context = useBoltdocsContext()
|
|
118
|
-
return selector(context)
|
|
65
|
+
return context as BoltdocsState
|
|
119
66
|
}
|
|
@@ -90,6 +90,10 @@
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
@variant dark
|
|
94
|
+
(
|
|
95
|
+
&:where(.dark, .dark *));
|
|
96
|
+
|
|
93
97
|
.animate-pulse {
|
|
94
98
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
95
99
|
}
|
|
@@ -99,9 +103,9 @@
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
:root[data-theme="dark"],
|
|
102
|
-
:root
|
|
103
|
-
--color-bg-main: var(--color-neutral-
|
|
104
|
-
--color-bg-surface: var(--color-neutral-
|
|
106
|
+
:root.dark {
|
|
107
|
+
--color-bg-main: var(--color-neutral-900);
|
|
108
|
+
--color-bg-surface: var(--color-neutral-800);
|
|
105
109
|
--color-bg-muted: var(--color-neutral-800);
|
|
106
110
|
--color-text-main: var(--color-neutral-50);
|
|
107
111
|
--color-text-muted: var(--color-neutral-400);
|
|
@@ -112,6 +116,7 @@
|
|
|
112
116
|
--color-code-text: var(--color-neutral-200);
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
|
|
115
120
|
@layer base {
|
|
116
121
|
*,
|
|
117
122
|
*::before,
|
|
@@ -271,7 +276,6 @@
|
|
|
271
276
|
border-radius: var(--radius-lg);
|
|
272
277
|
margin: 2rem 0;
|
|
273
278
|
display: block;
|
|
274
|
-
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.1);
|
|
275
279
|
}
|
|
276
280
|
.boltdocs-page table {
|
|
277
281
|
width: 100%;
|
|
@@ -294,41 +298,6 @@
|
|
|
294
298
|
.boltdocs-page tr:hover td {
|
|
295
299
|
background-color: var(--color-bg-surface);
|
|
296
300
|
}
|
|
297
|
-
.boltdocs-page :not(pre) > code {
|
|
298
|
-
background-color: var(--color-bg-surface);
|
|
299
|
-
padding: 0.15rem 0.45rem;
|
|
300
|
-
border-radius: 5px;
|
|
301
|
-
font-family: var(--font-mono);
|
|
302
|
-
font-size: 0.85em;
|
|
303
|
-
color: var(--color-primary-400);
|
|
304
|
-
border: 1px solid var(--color-border-subtle);
|
|
305
|
-
}
|
|
306
|
-
.boltdocs-page pre {
|
|
307
|
-
margin: 1.5rem 0;
|
|
308
|
-
border-radius: var(--radius-md);
|
|
309
|
-
overflow-x: auto;
|
|
310
|
-
font-family: var(--font-mono);
|
|
311
|
-
font-size: 0.8125rem;
|
|
312
|
-
line-height: 1.7;
|
|
313
|
-
background-color: var(--color-code-bg);
|
|
314
|
-
color: var(--color-code-text);
|
|
315
|
-
border: 1px solid var(--color-border-subtle);
|
|
316
|
-
}
|
|
317
|
-
.boltdocs-page pre > code {
|
|
318
|
-
display: grid;
|
|
319
|
-
padding: 1rem;
|
|
320
|
-
background-color: transparent;
|
|
321
|
-
border: none;
|
|
322
|
-
color: inherit;
|
|
323
|
-
font-size: inherit;
|
|
324
|
-
}
|
|
325
|
-
.boltdocs-page pre > code .line {
|
|
326
|
-
padding: 0 1.25rem;
|
|
327
|
-
}
|
|
328
|
-
.boltdocs-page pre > code .line.highlighted {
|
|
329
|
-
background-color: oklch(0.6 0.22 280 / 10%);
|
|
330
|
-
border-left: 2px solid var(--color-primary-500);
|
|
331
|
-
}
|
|
332
301
|
|
|
333
302
|
@media (max-width: 768px) {
|
|
334
303
|
.boltdocs-page h1 {
|
|
@@ -369,20 +338,91 @@
|
|
|
369
338
|
opacity: 1;
|
|
370
339
|
}
|
|
371
340
|
|
|
372
|
-
/* ═══ Shiki
|
|
373
|
-
|
|
341
|
+
/* ═══ Shiki Styles ═══ */
|
|
342
|
+
|
|
343
|
+
/* Shiki Light/Dark Mode */
|
|
344
|
+
:root .shiki,
|
|
345
|
+
:root .shiki span {
|
|
346
|
+
font-family: var(--font-mono);
|
|
347
|
+
font-size: 12px !important;
|
|
374
348
|
background-color: transparent !important;
|
|
375
|
-
color: var(--shiki-light) !important;
|
|
376
|
-
}
|
|
377
|
-
.shiki-wrapper .shiki.shiki-themes span {
|
|
378
|
-
color: var(--shiki-light);
|
|
379
349
|
}
|
|
380
|
-
|
|
381
|
-
:root
|
|
350
|
+
|
|
351
|
+
:root.dark .shiki,
|
|
352
|
+
:root.dark .shiki span {
|
|
382
353
|
color: var(--shiki-dark) !important;
|
|
383
354
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
355
|
+
|
|
356
|
+
/* Base Shiki Pre & Span Styles */
|
|
357
|
+
pre.shiki {
|
|
358
|
+
@apply py-2 text-[12px] leading-[1.6];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pre.shiki span.line {
|
|
362
|
+
@apply relative block px-4 py-0;
|
|
363
|
+
min-height: 1.6em;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Shiki Word Wrap */
|
|
367
|
+
pre.shiki-word-wrap {
|
|
368
|
+
@apply whitespace-pre-wrap break-words;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
pre.shiki-word-wrap span.line {
|
|
372
|
+
@apply block w-full;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Shiki Line Numbers */
|
|
376
|
+
pre.shiki-line-numbers code {
|
|
377
|
+
counter-reset: step;
|
|
378
|
+
counter-increment: step 0;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
pre.shiki-line-numbers .line {
|
|
382
|
+
@apply pl-12!;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Hide the last line if it is completely empty to avoid extra numbers */
|
|
386
|
+
pre.shiki-line-numbers .line:last-child:empty,
|
|
387
|
+
pre.shiki-line-numbers .line:last-child:has(> :empty) {
|
|
388
|
+
display: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
pre.shiki-line-numbers .line::before {
|
|
392
|
+
counter-increment: step;
|
|
393
|
+
content: counter(step);
|
|
394
|
+
@apply absolute left-0 top-0 inline-flex w-10 justify-end pr-3;
|
|
395
|
+
@apply text-[11px] text-text-muted opacity-30 select-none;
|
|
396
|
+
line-height: inherit; /* Sync with line text */
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Shiki Highlight */
|
|
400
|
+
pre span.shiki-line-highlight {
|
|
401
|
+
@apply relative z-0 inline-block w-full;
|
|
402
|
+
&::after {
|
|
403
|
+
content: "";
|
|
404
|
+
@apply absolute top-0 left-0 -z-10 h-full w-full border-l-2 border-primary-500 bg-primary-500/10! opacity-100;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Shiki Notation Diff */
|
|
409
|
+
pre.has-diff span.line.diff {
|
|
410
|
+
@apply relative inline-block w-full;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
pre.has-diff span.line.diff.add {
|
|
414
|
+
@apply bg-emerald-500/10! dark:bg-emerald-500/10!;
|
|
415
|
+
&::before {
|
|
416
|
+
content: "+";
|
|
417
|
+
@apply absolute left-2 text-emerald-500 font-bold;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
pre.has-diff span.line.diff.remove {
|
|
422
|
+
@apply bg-danger-500/10! opacity-70;
|
|
423
|
+
&::before {
|
|
424
|
+
content: "-";
|
|
425
|
+
@apply absolute left-2 text-danger-500 font-bold;
|
|
426
|
+
}
|
|
387
427
|
}
|
|
388
428
|
}
|