boltdocs 2.5.6 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/bin/boltdocs.js +2 -2
  2. package/dist/client/index.cjs +6 -0
  3. package/dist/client/{index.d.mts → index.d.cts} +135 -265
  4. package/dist/client/index.d.ts +136 -266
  5. package/dist/client/index.js +1 -1
  6. package/dist/client/theme/neutral.css +90 -50
  7. package/dist/node/cli-entry.cjs +2 -2
  8. package/dist/node/cli-entry.mjs +2 -2
  9. package/dist/node/index.cjs +1 -1
  10. package/dist/node/index.d.cts +168 -205
  11. package/dist/node/index.d.mts +168 -205
  12. package/dist/node/index.mjs +1 -1
  13. package/dist/node-CWN8U_p8.mjs +88 -0
  14. package/dist/node-D5iosYXv.cjs +88 -0
  15. package/dist/{package-OFZf0s2j.mjs → package-DukYeKmD.mjs} +1 -1
  16. package/dist/{package-BY8Jd2j4.cjs → package-c99Cs7mD.cjs} +1 -1
  17. package/dist/search-dialog-3lvKsbVG.js +6 -0
  18. package/dist/search-dialog-DMK5OpgH.cjs +6 -0
  19. package/dist/use-search-C9bxCqfF.js +6 -0
  20. package/dist/use-search-DcfZSunO.cjs +6 -0
  21. package/package.json +21 -23
  22. package/src/client/app/config-context.tsx +38 -5
  23. package/src/client/app/doc-page.tsx +34 -0
  24. package/src/client/app/mdx-component.tsx +2 -3
  25. package/src/client/app/mdx-components-context.tsx +27 -2
  26. package/src/client/app/routes-context.tsx +34 -0
  27. package/src/client/app/scroll-handler.tsx +7 -4
  28. package/src/client/app/theme-context.tsx +71 -67
  29. package/src/client/components/docs-layout.tsx +1 -2
  30. package/src/client/components/icons-dev.tsx +36 -5
  31. package/src/client/components/mdx/admonition.tsx +11 -27
  32. package/src/client/components/mdx/badge.tsx +1 -1
  33. package/src/client/components/mdx/button.tsx +3 -3
  34. package/src/client/components/mdx/card.tsx +1 -1
  35. package/src/client/components/mdx/code-block.tsx +90 -80
  36. package/src/client/components/mdx/component-preview.tsx +1 -5
  37. package/src/client/components/mdx/component-props.tsx +1 -1
  38. package/src/client/components/mdx/field.tsx +4 -5
  39. package/src/client/components/mdx/file-tree.tsx +6 -3
  40. package/src/client/components/mdx/hooks/use-code-block.ts +2 -2
  41. package/src/client/components/mdx/image.tsx +1 -1
  42. package/src/client/components/mdx/link.tsx +2 -2
  43. package/src/client/components/mdx/list.tsx +1 -1
  44. package/src/client/components/mdx/table.tsx +1 -1
  45. package/src/client/components/mdx/tabs.tsx +1 -1
  46. package/src/client/components/primitives/breadcrumbs.tsx +1 -7
  47. package/src/client/components/primitives/button-group.tsx +1 -1
  48. package/src/client/components/primitives/button.tsx +1 -1
  49. package/src/client/components/primitives/code-block.tsx +113 -0
  50. package/src/client/components/primitives/link.tsx +23 -41
  51. package/src/client/components/primitives/menu.tsx +5 -6
  52. package/src/client/components/primitives/navbar.tsx +6 -18
  53. package/src/client/components/primitives/navigation-menu.tsx +4 -4
  54. package/src/client/components/primitives/on-this-page.tsx +6 -10
  55. package/src/client/components/primitives/page-nav.tsx +4 -9
  56. package/src/client/components/primitives/popover.tsx +1 -1
  57. package/src/client/components/primitives/search-dialog.tsx +3 -6
  58. package/src/client/components/primitives/sidebar.tsx +80 -22
  59. package/src/client/components/primitives/skeleton.tsx +1 -1
  60. package/src/client/components/primitives/tabs.tsx +4 -11
  61. package/src/client/components/primitives/tooltip.tsx +3 -3
  62. package/src/client/components/ui-base/breadcrumbs.tsx +4 -6
  63. package/src/client/components/ui-base/copy-markdown.tsx +2 -7
  64. package/src/client/components/ui-base/github-stars.tsx +2 -2
  65. package/src/client/components/ui-base/head.tsx +58 -51
  66. package/src/client/components/ui-base/loading.tsx +2 -2
  67. package/src/client/components/ui-base/navbar.tsx +12 -14
  68. package/src/client/components/ui-base/not-found.tsx +1 -1
  69. package/src/client/components/ui-base/on-this-page.tsx +6 -6
  70. package/src/client/components/ui-base/page-nav.tsx +4 -8
  71. package/src/client/components/ui-base/search-dialog.tsx +10 -8
  72. package/src/client/components/ui-base/sidebar.tsx +76 -23
  73. package/src/client/components/ui-base/tabs.tsx +9 -8
  74. package/src/client/components/ui-base/theme-toggle.tsx +2 -2
  75. package/src/client/hooks/use-i18n.ts +3 -3
  76. package/src/client/hooks/use-localized-to.ts +1 -1
  77. package/src/client/hooks/use-navbar.ts +8 -6
  78. package/src/client/hooks/use-routes.ts +19 -11
  79. package/src/client/hooks/use-search.ts +1 -1
  80. package/src/client/hooks/use-sidebar.ts +48 -2
  81. package/src/client/hooks/use-tabs.ts +6 -2
  82. package/src/client/hooks/use-version.ts +3 -3
  83. package/src/client/index.ts +20 -22
  84. package/src/client/ssg/boltdocs-shell.tsx +127 -0
  85. package/src/client/ssg/create-routes.tsx +179 -0
  86. package/src/client/ssg/index.ts +3 -0
  87. package/src/client/ssg/mdx-page.tsx +37 -0
  88. package/src/client/store/boltdocs-context.tsx +46 -99
  89. package/src/client/theme/neutral.css +90 -50
  90. package/src/client/types.ts +5 -33
  91. package/src/client/utils/react-to-text.ts +34 -0
  92. package/src/shared/config-utils.ts +12 -0
  93. package/src/shared/types.ts +171 -0
  94. package/dist/cache-Cr8W2zgZ.cjs +0 -6
  95. package/dist/cache-DFdakSmR.mjs +0 -6
  96. package/dist/client/index.mjs +0 -6
  97. package/dist/client/ssr.cjs +0 -6
  98. package/dist/client/ssr.d.cts +0 -80
  99. package/dist/client/ssr.d.mts +0 -80
  100. package/dist/client/ssr.mjs +0 -6
  101. package/dist/node-CWXme96p.mjs +0 -73
  102. package/dist/node-VYfhzGrh.cjs +0 -73
  103. package/dist/search-dialog-BeNyI_KQ.mjs +0 -6
  104. package/dist/search-dialog-dYsCAk5S.js +0 -6
  105. package/dist/use-search-D25n0PrV.mjs +0 -6
  106. package/dist/use-search-WuzdH1cJ.js +0 -6
  107. package/src/client/app/index.tsx +0 -348
  108. package/src/client/app/mdx-page.tsx +0 -15
  109. package/src/client/app/preload.tsx +0 -66
  110. package/src/client/app/router.tsx +0 -30
  111. package/src/client/components/default-layout.tsx +0 -90
  112. package/src/client/integrations/codesandbox.ts +0 -179
  113. package/src/client/integrations/index.ts +0 -1
  114. 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,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
+ }
@@ -1,119 +1,66 @@
1
- import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
1
+ import { createContext, use, useMemo, useState } from 'react'
2
2
 
3
- interface BoltdocsState {
4
- currentLocale: string | undefined
5
- currentVersion: string | undefined
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
- interface BoltdocsContextValue extends BoltdocsState {}
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
- * Load persisted state from localStorage
22
- */
23
- function loadPersistedState(): Partial<BoltdocsState> {
24
- try {
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 persisted = loadPersistedState()
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
- // Mark as hydrated after initial load
52
- useEffect(() => {
53
- setHasHydrated(true)
54
- }, [])
55
-
56
- // Persist state changes to localStorage
57
- useEffect(() => {
58
- if (hasHydrated) {
59
- const stateToPersist = {
60
- currentLocale,
61
- currentVersion,
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
- const value: BoltdocsContextValue = {
87
- currentLocale,
88
- currentVersion,
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}>{children}</BoltdocsContext.Provider>
44
+ <BoltdocsContext.Provider value={value}>
45
+ {children}
46
+ </BoltdocsContext.Provider>
97
47
  )
98
48
  }
99
49
 
100
- /**
101
- * Hook to access the Boltdocs context.
102
- * Must be used within a BoltdocsProvider.
103
- */
104
- export function useBoltdocsContext(): BoltdocsContextValue {
105
- const context = useContext(BoltdocsContext)
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:not(.theme-light) {
103
- --color-bg-main: var(--color-neutral-950);
104
- --color-bg-surface: var(--color-neutral-900);
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 Dual Theme ═══ */
373
- .shiki-wrapper .shiki.shiki-themes {
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
- :root[data-theme="dark"] .shiki-wrapper .shiki.shiki-themes,
381
- :root:not(.theme-light) .shiki-wrapper .shiki.shiki-themes {
350
+
351
+ :root.dark .shiki,
352
+ :root.dark .shiki span {
382
353
  color: var(--shiki-dark) !important;
383
354
  }
384
- :root[data-theme="dark"] .shiki-wrapper .shiki.shiki-themes span,
385
- :root:not(.theme-light) .shiki-wrapper .shiki.shiki-themes span {
386
- color: var(--shiki-dark) !important;
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
  }