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
@@ -0,0 +1,82 @@
1
+ import { useEffect, useCallback } from 'react'
2
+ import { useSearch } from '@hooks/use-search'
3
+ import {
4
+ SearchDialogAutocomplete,
5
+ SearchDialogInput,
6
+ SearchDialogItemBio,
7
+ SearchDialogItemIcon,
8
+ SearchDialogItemRoot,
9
+ SearchDialogItemTitle,
10
+ SearchDialogList,
11
+ SearchDialogRoot,
12
+ } from '@components/primitives/search-dialog'
13
+ import Navbar from '@components/primitives/navbar'
14
+ import { useNavigate } from 'react-router-dom'
15
+
16
+ export function SearchDialog({ routes }: { routes: any[] }) {
17
+ const { isOpen, setIsOpen, query, setQuery, list } = useSearch(routes)
18
+ const navigate = useNavigate()
19
+
20
+ useEffect(() => {
21
+ const handleKeyDown = (e: KeyboardEvent) => {
22
+ const isMac = /Mac/.test(navigator.userAgent)
23
+ const isMeta = isMac ? e.metaKey : e.ctrlKey
24
+
25
+ if (isMeta && (e.key === 'k' || e.key === 'j')) {
26
+ e.preventDefault()
27
+ setIsOpen((prev) => !prev)
28
+ }
29
+ }
30
+ window.addEventListener('keydown', handleKeyDown)
31
+ return () => window.removeEventListener('keydown', handleKeyDown)
32
+ }, [setIsOpen])
33
+
34
+ const handleSelect = useCallback(
35
+ (key: React.Key) => {
36
+ const path = String(key)
37
+ setIsOpen(false)
38
+
39
+ if (path.includes('#')) {
40
+ const [p, id] = path.split('#')
41
+ navigate(p)
42
+ setTimeout(() => {
43
+ const el = document.getElementById(id)
44
+ if (el) el.scrollIntoView({ behavior: 'smooth' })
45
+ }, 100)
46
+ } else {
47
+ navigate(path)
48
+ }
49
+ },
50
+ [navigate, setIsOpen],
51
+ )
52
+
53
+ return (
54
+ <>
55
+ <Navbar.SearchTrigger onPress={() => setIsOpen(true)} />
56
+
57
+ <SearchDialogRoot isOpen={isOpen} onOpenChange={setIsOpen}>
58
+ <SearchDialogAutocomplete onSelectionChange={handleSelect}>
59
+ <SearchDialogInput
60
+ value={query}
61
+ onChange={(e: any) => setQuery(e.target.value)}
62
+ />
63
+ <SearchDialogList items={list}>
64
+ {(item: any) => (
65
+ <SearchDialogItemRoot
66
+ key={item.id}
67
+ onPress={() => handleSelect(item.id)}
68
+ textValue={item.title}
69
+ >
70
+ <SearchDialogItemIcon isHeading={item.isHeading} />
71
+ <div className="flex flex-col justify-center gap-0.5">
72
+ <SearchDialogItemTitle>{item.title}</SearchDialogItemTitle>
73
+ <SearchDialogItemBio>{item.groupTitle}</SearchDialogItemBio>
74
+ </div>
75
+ </SearchDialogItemRoot>
76
+ )}
77
+ </SearchDialogList>
78
+ </SearchDialogAutocomplete>
79
+ </SearchDialogRoot>
80
+ </>
81
+ )
82
+ }
@@ -0,0 +1,104 @@
1
+ import { useState, useEffect, useMemo } from 'react'
2
+ import { useSidebar } from '@hooks/use-sidebar'
3
+ import SidebarPrimitive from '@components/primitives/sidebar'
4
+ import { PoweredBy } from './powered-by'
5
+ import * as LucideIcons from 'lucide-react'
6
+ import type { ComponentRoute } from '@client/types'
7
+ import type { BoltdocsConfig } from '@node/config'
8
+
9
+ function getIcon(iconName?: string): React.ElementType | undefined {
10
+ if (!iconName) return undefined
11
+ const IconComponent = (LucideIcons as Record<string, any>)[iconName]
12
+ return IconComponent || undefined
13
+ }
14
+
15
+ function CollapsibleSidebarGroup({
16
+ group,
17
+ activePath,
18
+ getIcon,
19
+ }: {
20
+ group: {
21
+ slug: string
22
+ title: string
23
+ routes: ComponentRoute[]
24
+ icon?: string
25
+ }
26
+ activePath: string
27
+ getIcon: (iconName?: string) => React.ElementType | undefined
28
+ }) {
29
+ const hasActiveRoute = useMemo(
30
+ () => group.routes.some((r) => r.path === activePath),
31
+ [group.routes, activePath],
32
+ )
33
+
34
+ const [isOpen, setIsOpen] = useState(true)
35
+
36
+ useEffect(() => {
37
+ if (hasActiveRoute) {
38
+ setIsOpen(true)
39
+ }
40
+ }, [hasActiveRoute])
41
+
42
+ return (
43
+ <SidebarPrimitive.SidebarGroup
44
+ title={group.title}
45
+ isOpen={isOpen}
46
+ onToggle={() => setIsOpen(!isOpen)}
47
+ >
48
+ {group.routes.map((route: ComponentRoute) => (
49
+ <SidebarPrimitive.SidebarLink
50
+ key={route.path}
51
+ label={route.title}
52
+ href={route.path}
53
+ active={activePath === route.path}
54
+ icon={getIcon(route.icon)}
55
+ badge={route.badge}
56
+ />
57
+ ))}
58
+ </SidebarPrimitive.SidebarGroup>
59
+ )
60
+ }
61
+
62
+ export function Sidebar({
63
+ routes,
64
+ config,
65
+ }: {
66
+ routes: ComponentRoute[]
67
+ config: BoltdocsConfig
68
+ }) {
69
+ const { groups, ungrouped, activePath } = useSidebar(routes)
70
+
71
+ return (
72
+ <SidebarPrimitive.SidebarRoot>
73
+ {ungrouped.length > 0 && (
74
+ <SidebarPrimitive.SidebarGroup className="mb-6">
75
+ {ungrouped.map((route) => (
76
+ <SidebarPrimitive.SidebarLink
77
+ key={route.path}
78
+ label={route.title}
79
+ href={route.path}
80
+ active={activePath === route.path}
81
+ icon={getIcon(route.icon)}
82
+ badge={route.badge}
83
+ />
84
+ ))}
85
+ </SidebarPrimitive.SidebarGroup>
86
+ )}
87
+
88
+ {groups.map((group) => (
89
+ <CollapsibleSidebarGroup
90
+ key={group.slug}
91
+ group={group}
92
+ activePath={activePath}
93
+ getIcon={getIcon}
94
+ />
95
+ ))}
96
+
97
+ {config.themeConfig?.poweredBy && (
98
+ <div className="mt-auto pt-8">
99
+ <PoweredBy />
100
+ </div>
101
+ )}
102
+ </SidebarPrimitive.SidebarRoot>
103
+ )
104
+ }
@@ -0,0 +1,65 @@
1
+ import { useTabs as useTabsHook } from '@hooks/use-tabs'
2
+ import T from '@components/primitives/tabs'
3
+ import { Link } from '@components/primitives/link'
4
+ import type { BoltdocsTab, ComponentRoute } from '@client/types'
5
+ import * as Icons from 'lucide-react'
6
+
7
+ export function Tabs({
8
+ tabs,
9
+ routes,
10
+ }: {
11
+ tabs: BoltdocsTab[]
12
+ routes: ComponentRoute[]
13
+ }) {
14
+ const { indicatorStyle, tabRefs, activeIndex } = useTabsHook(tabs, routes)
15
+
16
+ const renderTabIcon = (iconName?: string) => {
17
+ if (!iconName) return null
18
+ if (iconName.trim().startsWith('<svg')) {
19
+ return (
20
+ <span
21
+ className="h-4 w-4"
22
+ dangerouslySetInnerHTML={{ __html: iconName }}
23
+ />
24
+ )
25
+ }
26
+ const LucideIcon = (Icons as Record<string, any>)[iconName]
27
+ if (LucideIcon) {
28
+ return <LucideIcon size={16} />
29
+ }
30
+ return <img src={iconName} alt="" className="h-4 w-4 object-contain" />
31
+ }
32
+
33
+ return (
34
+ <div className="mx-auto max-w-(--breakpoint-3xl) px-4 md:px-6">
35
+ <T.TabsList className="border-none py-0">
36
+ {tabs.map((tab, index) => {
37
+ const isActive = index === activeIndex
38
+ const firstRoute = routes.find(
39
+ (r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase(),
40
+ )
41
+ const linkTo = firstRoute ? firstRoute.path : '#'
42
+
43
+ return (
44
+ <Link
45
+ key={tab.id}
46
+ href={linkTo}
47
+ ref={(el: HTMLAnchorElement | null) => {
48
+ tabRefs.current[index] = el
49
+ }}
50
+ className={`relative flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors outline-none ${
51
+ isActive
52
+ ? 'text-primary-500'
53
+ : 'text-text-muted hover:text-text-main'
54
+ }`}
55
+ >
56
+ {renderTabIcon(tab.icon)}
57
+ <span>{tab.text}</span>
58
+ </Link>
59
+ )
60
+ })}
61
+ <T.TabsIndicator style={indicatorStyle} />
62
+ </T.TabsList>
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,32 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Sun, Moon } from 'lucide-react'
3
+ import { useTheme } from '@client/app/theme-context'
4
+ import { ToggleButton } from 'react-aria-components'
5
+
6
+ export function ThemeToggle() {
7
+ const { theme, toggleTheme } = useTheme()
8
+ const [mounted, setMounted] = useState(false)
9
+
10
+ useEffect(() => {
11
+ setMounted(true)
12
+ }, [])
13
+
14
+ if (!mounted) {
15
+ return <div className="h-9 w-9" />
16
+ }
17
+
18
+ return (
19
+ <ToggleButton
20
+ onChange={toggleTheme}
21
+ className="flex h-9 w-9 items-center justify-center rounded-md text-text-muted transition-colors hover:bg-bg-surface hover:text-text-main"
22
+ aria-label="Toggle theme"
23
+ isSelected={theme === 'dark'}
24
+ >
25
+ {theme === 'dark' ? (
26
+ <Sun size={20} className="animate-in fade-in zoom-in duration-300" />
27
+ ) : (
28
+ <Moon size={20} className="animate-in fade-in zoom-in duration-300" />
29
+ )}
30
+ </ToggleButton>
31
+ )
32
+ }
@@ -0,0 +1,12 @@
1
+ export * from './use-navbar'
2
+ export * from './use-sidebar'
3
+ export * from './use-search'
4
+ export * from './use-onthispage'
5
+ export * from './use-tabs'
6
+ export * from './use-version'
7
+ export * from './use-i18n'
8
+ export * from './use-page-nav'
9
+ export * from './use-breadcrumbs'
10
+ export * from './use-routes'
11
+ export * from './use-localized-to'
12
+ export * from './use-location'
@@ -0,0 +1,22 @@
1
+ import { useRoutes } from './use-routes'
2
+
3
+ /**
4
+ * Hook to generate breadcrumbs based on the current active route.
5
+ */
6
+ export function useBreadcrumbs() {
7
+ const { currentRoute: activeRoute } = useRoutes()
8
+
9
+ const crumbs: Array<{ label: string; href?: string }> = []
10
+
11
+ if (activeRoute) {
12
+ if (activeRoute.groupTitle) {
13
+ crumbs.push({ label: activeRoute.groupTitle })
14
+ }
15
+ crumbs.push({ label: activeRoute.title, href: activeRoute.path })
16
+ }
17
+
18
+ return {
19
+ crumbs,
20
+ activeRoute,
21
+ }
22
+ }
@@ -0,0 +1,84 @@
1
+ import { useNavigate } from 'react-router-dom'
2
+ import { getBaseFilePath } from '@client/utils/get-base-file-path'
3
+ import { useRoutes } from './use-routes'
4
+
5
+ export interface LocaleOption {
6
+ key: string
7
+ label: string
8
+ value: string
9
+ isCurrent: boolean
10
+ }
11
+
12
+ export interface UseI18nReturn {
13
+ currentLocale: string | undefined
14
+ currentLocaleLabel: string | undefined
15
+ availableLocales: LocaleOption[]
16
+ handleLocaleChange: (locale: string) => void
17
+ }
18
+
19
+ /**
20
+ * Hook to manage and switch between different locales (languages) of the documentation.
21
+ */
22
+ export function useI18n(): UseI18nReturn {
23
+ const navigate = useNavigate()
24
+ const routeContext = useRoutes()
25
+ const { allRoutes, currentRoute, currentLocale, config } = routeContext
26
+ const i18n = config.i18n
27
+
28
+ const handleLocaleChange = (locale: string) => {
29
+ if (!i18n || locale === currentLocale) return
30
+
31
+ let targetPath = '/'
32
+
33
+ if (currentRoute) {
34
+ const baseFile = getBaseFilePath(
35
+ currentRoute.filePath,
36
+ currentRoute.version,
37
+ currentRoute.locale,
38
+ )
39
+ const targetRoute = allRoutes.find(
40
+ (r) =>
41
+ getBaseFilePath(r.filePath, r.version, r.locale) === baseFile &&
42
+ (r.locale || i18n.defaultLocale) === locale &&
43
+ r.version === currentRoute.version,
44
+ )
45
+
46
+ if (targetRoute) {
47
+ targetPath = targetRoute.path
48
+ } else {
49
+ const defaultIndexRoute = allRoutes.find(
50
+ (r) =>
51
+ getBaseFilePath(r.filePath, r.version, r.locale) === 'index.md' &&
52
+ (r.locale || i18n.defaultLocale) === locale &&
53
+ r.version === currentRoute.version,
54
+ )
55
+ targetPath = defaultIndexRoute
56
+ ? defaultIndexRoute.path
57
+ : locale === i18n.defaultLocale
58
+ ? currentRoute.version
59
+ ? `/${currentRoute.version}`
60
+ : '/'
61
+ : currentRoute.version
62
+ ? `/${currentRoute.version}/${locale}`
63
+ : `/${locale}`
64
+ }
65
+ } else {
66
+ targetPath = locale === i18n.defaultLocale ? '/' : `/${locale}`
67
+ }
68
+
69
+ navigate(targetPath)
70
+ }
71
+
72
+ const availableLocales = routeContext.availableLocales.map((l) => ({
73
+ ...l,
74
+ label: l.label as string,
75
+ value: l.key,
76
+ }))
77
+
78
+ return {
79
+ currentLocale,
80
+ currentLocaleLabel: routeContext.currentLocaleLabel,
81
+ availableLocales,
82
+ handleLocaleChange,
83
+ }
84
+ }
@@ -0,0 +1,95 @@
1
+ import { useLocation } from 'react-router-dom'
2
+ import { useConfig } from '@client/app/config-context'
3
+ import type { LinkProps as RouterLinkProps } from 'react-router-dom'
4
+
5
+ /**
6
+ * Hook to automatically localize a path based on the current version and locale context.
7
+ * It ensures that navigation within the /docs path preserves the active version and language.
8
+ */
9
+ export function useLocalizedTo(to: RouterLinkProps['to']) {
10
+ const location = useLocation()
11
+ const config = useConfig()
12
+
13
+ if (!config || typeof to !== 'string') return to
14
+ if (!config.i18n && !config.versions) return to
15
+
16
+ const basePath = '/docs'
17
+ if (!to.startsWith(basePath)) return to
18
+
19
+ // 1. Detect current context from location
20
+ const curSub = location.pathname.substring(basePath.length)
21
+ const curParts = curSub.split('/').filter(Boolean)
22
+
23
+ let currentVersion = config.versions?.defaultVersion
24
+ let currentLocale = config.i18n?.defaultLocale
25
+
26
+ let cIdx = 0
27
+ if (
28
+ config.versions &&
29
+ curParts.length > cIdx &&
30
+ config.versions.versions[curParts[cIdx]]
31
+ ) {
32
+ currentVersion = curParts[cIdx]
33
+ cIdx++
34
+ }
35
+ if (
36
+ config.i18n &&
37
+ curParts.length > cIdx &&
38
+ config.i18n.locales[curParts[cIdx]]
39
+ ) {
40
+ currentLocale = curParts[cIdx]
41
+ }
42
+
43
+ // 2. Parse the target `to` path
44
+ const toSub = to.substring(basePath.length)
45
+ const toParts = toSub.split('/').filter(Boolean)
46
+
47
+ let tIdx = 0
48
+ let hasVersion = false
49
+ let hasLocale = false
50
+
51
+ if (
52
+ config.versions &&
53
+ toParts.length > tIdx &&
54
+ config.versions.versions[toParts[tIdx]]
55
+ ) {
56
+ hasVersion = true
57
+ tIdx++
58
+ }
59
+ if (
60
+ config.i18n &&
61
+ toParts.length > tIdx &&
62
+ config.i18n.locales[toParts[tIdx]]
63
+ ) {
64
+ hasLocale = true
65
+ tIdx++
66
+ }
67
+
68
+ // Extract just the actual route parts
69
+ const routeParts = toParts.slice(tIdx)
70
+
71
+ // Reconstruct path
72
+ const finalParts = []
73
+ if (config.versions) {
74
+ if (hasVersion) {
75
+ finalParts.push(toParts[0])
76
+ } else if (currentVersion) {
77
+ finalParts.push(currentVersion)
78
+ }
79
+ }
80
+ if (config.i18n) {
81
+ if (hasLocale) {
82
+ finalParts.push(toParts[hasVersion ? 1 : 0])
83
+ } else if (currentLocale) {
84
+ finalParts.push(currentLocale)
85
+ }
86
+ }
87
+
88
+ finalParts.push(...routeParts)
89
+
90
+ let finalPath = `${basePath}/${finalParts.join('/')}`
91
+ if (finalPath.endsWith('/')) {
92
+ finalPath = finalPath.slice(0, -1)
93
+ }
94
+ return finalPath === basePath ? basePath : finalPath
95
+ }
@@ -0,0 +1,5 @@
1
+ import { useLocation as useReactLocation } from 'react-router-dom'
2
+
3
+ export const useLocation = () => {
4
+ return useReactLocation()
5
+ }
@@ -0,0 +1,60 @@
1
+ import { useLocation } from 'react-router-dom'
2
+ import { useConfig } from '@client/app/config-context'
3
+ import { useTheme } from '@client/app/theme-context'
4
+ import type { NavbarLink } from '@client/types'
5
+
6
+ export function useNavbar() {
7
+ const config = useConfig()
8
+ const { theme } = useTheme()
9
+ const location = useLocation()
10
+
11
+ const themeConfig = config.themeConfig || {}
12
+ const title = themeConfig.title || 'Boltdocs'
13
+ const rawLinks = themeConfig.navbar || []
14
+ const socialLinks = themeConfig.socialLinks || []
15
+ const githubRepo = themeConfig.githubRepo
16
+
17
+ // Transform links to the new NavbarLink structure
18
+ const links: NavbarLink[] = rawLinks.map((item: any) => {
19
+ const href = item.href || item.to || item.link || ''
20
+ return {
21
+ label: item.label || item.text || '',
22
+ href,
23
+ active: location.pathname === href,
24
+ to:
25
+ href.startsWith('http') || href.startsWith('//')
26
+ ? 'external'
27
+ : undefined,
28
+ }
29
+ })
30
+
31
+ const logo = themeConfig.logo
32
+ const logoSrc = !logo
33
+ ? null
34
+ : typeof logo === 'string'
35
+ ? logo
36
+ : theme === 'dark'
37
+ ? (logo as any).dark
38
+ : (logo as any).light
39
+
40
+ const logoProps = {
41
+ alt:
42
+ (logo && typeof logo === 'object' ? (logo as any).alt : undefined) ||
43
+ title,
44
+ width: logo && typeof logo === 'object' ? (logo as any).width : undefined,
45
+ height: logo && typeof logo === 'object' ? (logo as any).height : undefined,
46
+ }
47
+
48
+ const github = githubRepo ? `https://github.com/${githubRepo}` : null
49
+
50
+ return {
51
+ links,
52
+ title,
53
+ logo: logoSrc,
54
+ logoProps,
55
+ github,
56
+ social: socialLinks,
57
+ config,
58
+ theme,
59
+ }
60
+ }
@@ -0,0 +1,23 @@
1
+ import { useState } from 'react'
2
+
3
+ interface Heading {
4
+ id: string
5
+ text: string
6
+ level: number
7
+ }
8
+
9
+ /**
10
+ * Hook to manage and provide current page headings for the OnThisPage component.
11
+ */
12
+ export function useOnThisPage(headings: Heading[] = []) {
13
+ const [activeId, setActiveId] = useState<string | null>(null)
14
+
15
+ // We keep the signature the same for backward compatibility,
16
+ // but the activeId tracking is now handled by AnchorProvider in the primitives.
17
+
18
+ return {
19
+ headings,
20
+ activeId,
21
+ setActiveId,
22
+ }
23
+ }
@@ -0,0 +1,22 @@
1
+ import { useRoutes } from './use-routes'
2
+
3
+ /**
4
+ * Hook to manage the previous and next button functionality for documentation pages.
5
+ */
6
+ export function usePageNav() {
7
+ const { routes } = useRoutes()
8
+ const currentPath = window.location.pathname
9
+
10
+ const currentIndex = routes.findIndex((r) => r.path === currentPath)
11
+ const currentRoute = routes[currentIndex]
12
+
13
+ const prevPage = currentIndex > 0 ? routes[currentIndex - 1] : null
14
+ const nextPage =
15
+ currentIndex < routes.length - 1 ? routes[currentIndex + 1] : null
16
+
17
+ return {
18
+ prevPage,
19
+ nextPage,
20
+ currentRoute,
21
+ }
22
+ }
@@ -0,0 +1,72 @@
1
+ import { useLocation } from 'react-router-dom'
2
+ import { useConfig } from '@client/app/config-context'
3
+ import { usePreload } from '@client/app/preload'
4
+
5
+ /**
6
+ * Hook to access the framework's routing state.
7
+ * Returns both the complete set of routes and a filtered list based on the current
8
+ * version and locale.
9
+ */
10
+ export function useRoutes() {
11
+ const { routes: allRoutes } = usePreload()
12
+ const config = useConfig()
13
+ const location = useLocation()
14
+
15
+ // Find the current route exactly matching the pathname
16
+ const currentRoute = allRoutes.find((r) => r.path === location.pathname)
17
+
18
+ // Derive current locale and version from the route or defaults
19
+ const currentLocale = config.i18n
20
+ ? currentRoute?.locale || config.i18n.defaultLocale
21
+ : undefined
22
+
23
+ const currentVersion = config.versions
24
+ ? currentRoute?.version || config.versions.defaultVersion
25
+ : undefined
26
+
27
+ // Filter routes to those matching the current version and locale
28
+ const routes = allRoutes.filter((r) => {
29
+ const localeMatch = config.i18n
30
+ ? (r.locale || config.i18n.defaultLocale) === currentLocale
31
+ : true
32
+ const versionMatch = config.versions
33
+ ? (r.version || config.versions.defaultVersion) === currentVersion
34
+ : true
35
+ return localeMatch && versionMatch
36
+ })
37
+
38
+ // Labels and lists for UI convenience
39
+ const currentLocaleLabel =
40
+ config.i18n?.locales[currentLocale as string] || currentLocale
41
+ const currentVersionLabel =
42
+ config.versions?.versions[currentVersion as string] || currentVersion
43
+
44
+ const availableLocales = config.i18n
45
+ ? Object.entries(config.i18n.locales).map(([key, label]) => ({
46
+ key,
47
+ label,
48
+ isCurrent: key === currentLocale,
49
+ }))
50
+ : []
51
+
52
+ const availableVersions = config.versions
53
+ ? Object.entries(config.versions.versions).map(([key, label]) => ({
54
+ key,
55
+ label,
56
+ isCurrent: key === currentVersion,
57
+ }))
58
+ : []
59
+
60
+ return {
61
+ routes,
62
+ allRoutes,
63
+ currentRoute,
64
+ currentLocale,
65
+ currentLocaleLabel,
66
+ availableLocales,
67
+ currentVersion,
68
+ currentVersionLabel,
69
+ availableVersions,
70
+ config,
71
+ }
72
+ }