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
@@ -3,10 +3,10 @@ import {
3
3
  AnchorProvider,
4
4
  ScrollProvider,
5
5
  useActiveAnchor,
6
- } from '@components/primitives/on-this-page'
7
- import React, { useRef, useEffect, useState, useCallback } from 'react'
8
- import { useOnThisPage } from '@hooks/use-onthispage'
9
- import type { OnThisPageProps } from '@client/types'
6
+ } from '../primitives/on-this-page'
7
+ import { useRef, useEffect, useState, useCallback, useMemo } from 'react'
8
+ import { useOnThisPage } from '../../hooks/use-onthispage'
9
+ import type { OnThisPageProps } from '../../types'
10
10
  import { Pencil, CircleHelp, TextAlignStart } from 'lucide-react'
11
11
 
12
12
  export function OnThisPage({
@@ -17,7 +17,7 @@ export function OnThisPage({
17
17
  }: OnThisPageProps) {
18
18
  const { headings } = useOnThisPage(rawHeadings)
19
19
 
20
- const toc = React.useMemo(
20
+ const toc = useMemo(
21
21
  () =>
22
22
  headings.map((h) => ({ title: h.text, url: `#${h.id}`, depth: h.level })),
23
23
  [headings],
@@ -115,7 +115,7 @@ function OnThisPageInner({
115
115
 
116
116
  {(editLink || communityHelp) && (
117
117
  <div className="mt-8 pt-8 border-t border-border-subtle space-y-4">
118
- <p className="text-xs font-bold uppercase tracking-wider text-text-main">
118
+ <p className="text-xs font-bold uppercase text-text-main">
119
119
  Need help?
120
120
  </p>
121
121
  <ul className="space-y-3">
@@ -1,5 +1,5 @@
1
- import { usePageNav } from '@hooks/use-page-nav'
2
- import { PageNav as PageNavPrimitive } from '@components/primitives/page-nav'
1
+ import { usePageNav } from '../../hooks/use-page-nav'
2
+ import { PageNav as PageNavPrimitive } from '../primitives/page-nav'
3
3
 
4
4
  /**
5
5
  * Component to display the previous and next page navigation buttons.
@@ -14,9 +14,7 @@ export function PageNav() {
14
14
  <PageNavPrimitive.Root className="animate-in fade-in slide-in-from-bottom-4 duration-700">
15
15
  {prevPage ? (
16
16
  <PageNavPrimitive.Link to={prevPage.path} direction="prev">
17
- <PageNavPrimitive.Title>
18
- Previous
19
- </PageNavPrimitive.Title>
17
+ <PageNavPrimitive.Title>Previous</PageNavPrimitive.Title>
20
18
  <PageNavPrimitive.Description>
21
19
  {prevPage.title}
22
20
  </PageNavPrimitive.Description>
@@ -27,9 +25,7 @@ export function PageNav() {
27
25
 
28
26
  {nextPage && (
29
27
  <PageNavPrimitive.Link to={nextPage.path} direction="next">
30
- <PageNavPrimitive.Title>
31
- Next
32
- </PageNavPrimitive.Title>
28
+ <PageNavPrimitive.Title>Next</PageNavPrimitive.Title>
33
29
  <PageNavPrimitive.Description>
34
30
  {nextPage.title}
35
31
  </PageNavPrimitive.Description>
@@ -1,11 +1,9 @@
1
1
  import { useEffect, useCallback } from 'react'
2
- import { useSearch } from '@hooks/use-search'
3
- import {
4
- SearchDialog as SearchDialogPrimitive,
5
- } from '@components/primitives/search-dialog'
6
- import Navbar from '@components/primitives/navbar'
2
+ import { useSearch } from '../../hooks/use-search'
3
+ import { SearchDialog as SearchDialogPrimitive } from '../primitives/search-dialog'
4
+ import Navbar from '../primitives/navbar'
7
5
  import { useNavigate } from 'react-router-dom'
8
- import type { ComponentRoute } from '@client/types'
6
+ import type { ComponentRoute } from '../../types'
9
7
  interface SearchResult {
10
8
  id: string
11
9
  title: string
@@ -73,8 +71,12 @@ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
73
71
  >
74
72
  <SearchDialogPrimitive.Item.Icon isHeading={item.isHeading} />
75
73
  <div className="flex flex-col justify-center gap-0.5">
76
- <SearchDialogPrimitive.Item.Title>{item.title}</SearchDialogPrimitive.Item.Title>
77
- <SearchDialogPrimitive.Item.Bio>{item.bio}</SearchDialogPrimitive.Item.Bio>
74
+ <SearchDialogPrimitive.Item.Title>
75
+ {item.title}
76
+ </SearchDialogPrimitive.Item.Title>
77
+ <SearchDialogPrimitive.Item.Bio>
78
+ {item.bio}
79
+ </SearchDialogPrimitive.Item.Bio>
78
80
  </div>
79
81
  </SearchDialogPrimitive.Item>
80
82
  )}
@@ -1,52 +1,105 @@
1
1
  import { useState, useEffect, useMemo } from 'react'
2
- import { useSidebar } from '@hooks/use-sidebar'
3
- import { Sidebar as SidebarPrimitive } from '@components/primitives/sidebar'
2
+ import { useSidebar } from '../../hooks/use-sidebar'
3
+ import { Sidebar as SidebarPrimitive } from '../primitives/sidebar'
4
4
  import { PoweredBy } from './powered-by'
5
5
  import * as LucideIcons from 'lucide-react'
6
- import type { ComponentRoute } from '@client/types'
7
- import type { BoltdocsConfig } from '@node/config'
6
+ import virtualIcons from 'virtual:boltdocs-icons'
7
+ import type { ComponentRoute } from '../../types'
8
+ import type { BoltdocsConfig } from '../../../shared/types'
8
9
 
9
10
  function getIcon(iconName?: string): React.ElementType | undefined {
10
11
  if (!iconName) return undefined
11
- const icons = LucideIcons as unknown as Record<string, React.ElementType>
12
- const IconComponent = icons[iconName]
12
+ const icons = { ...LucideIcons, ...virtualIcons } as unknown as Record<
13
+ string,
14
+ React.ElementType
15
+ >
16
+ const IconComponent = icons[iconName] || icons[iconName + 'Icon']
13
17
  return IconComponent || undefined
14
18
  }
15
19
 
16
- function CollapsibleSidebarGroup({
17
- group,
20
+ function SidebarSubRouteGroup({
21
+ route,
18
22
  activePath,
19
23
  getIcon,
20
24
  }: {
21
- group: {
22
- slug: string
23
- title: string
24
- routes: ComponentRoute[]
25
- icon?: string
26
- }
25
+ route: ComponentRoute
27
26
  activePath: string
28
27
  getIcon: (iconName?: string) => React.ElementType | undefined
29
28
  }) {
30
- const hasActiveRoute = useMemo(
31
- () => group.routes.some((r) => r.path === activePath),
32
- [group.routes, activePath],
29
+ const isCurrent =
30
+ activePath ===
31
+ (route.path.endsWith('/') ? route.path.slice(0, -1) : route.path)
32
+
33
+ const hasActiveSubRoute = useMemo(
34
+ () => route.subRoutes?.some((r) => r.path === activePath),
35
+ [route.subRoutes, activePath],
33
36
  )
34
37
 
35
- const [isOpen, setIsOpen] = useState(true)
38
+ const [isOpen, setIsOpen] = useState(hasActiveSubRoute || isCurrent)
36
39
 
37
40
  useEffect(() => {
38
- if (hasActiveRoute) {
41
+ if (hasActiveSubRoute || isCurrent) {
39
42
  setIsOpen(true)
40
43
  }
41
- }, [hasActiveRoute])
44
+ }, [hasActiveSubRoute, isCurrent])
42
45
 
43
46
  return (
44
- <SidebarPrimitive.Group
45
- title={group.title}
47
+ <SidebarPrimitive.SubGroup
48
+ label={route.title}
49
+ href={route.path}
50
+ active={isCurrent}
51
+ icon={getIcon(route.icon)}
52
+ badge={route.badge}
46
53
  isOpen={isOpen}
47
54
  onToggle={() => setIsOpen(!isOpen)}
48
55
  >
56
+ {route.subRoutes?.map((subRoute: ComponentRoute) => {
57
+ const isSubCurrent =
58
+ activePath ===
59
+ (subRoute.path.endsWith('/') ? subRoute.path.slice(0, -1) : subRoute.path)
60
+ return (
61
+ <SidebarPrimitive.Link
62
+ key={subRoute.path}
63
+ label={subRoute.title}
64
+ href={subRoute.path}
65
+ active={isSubCurrent}
66
+ icon={getIcon(subRoute.icon)}
67
+ badge={subRoute.badge}
68
+ />
69
+ )
70
+ })}
71
+ </SidebarPrimitive.SubGroup>
72
+ )
73
+ }
74
+
75
+ function SidebarGroupSection({
76
+ group,
77
+ activePath,
78
+ getIcon,
79
+ }: {
80
+ group: {
81
+ slug: string
82
+ title: string
83
+ routes: ComponentRoute[]
84
+ icon?: string
85
+ }
86
+ activePath: string
87
+ getIcon: (iconName?: string) => React.ElementType | undefined
88
+ }) {
89
+ return (
90
+ <SidebarPrimitive.Group title={group.title} icon={getIcon(group.icon)}>
49
91
  {group.routes.map((route: ComponentRoute) => {
92
+ if (route.subRoutes && route.subRoutes.length > 0) {
93
+ return (
94
+ <SidebarSubRouteGroup
95
+ key={route.path}
96
+ route={route}
97
+ activePath={activePath}
98
+ getIcon={getIcon}
99
+ />
100
+ )
101
+ }
102
+
50
103
  const isCurrent =
51
104
  activePath ===
52
105
  (route.path.endsWith('/') ? route.path.slice(0, -1) : route.path)
@@ -98,7 +151,7 @@ export function Sidebar({
98
151
  )}
99
152
 
100
153
  {groups.map((group) => (
101
- <CollapsibleSidebarGroup
154
+ <SidebarGroupSection
102
155
  key={group.slug}
103
156
  group={group}
104
157
  activePath={activePath}
@@ -1,10 +1,10 @@
1
- import { useTabs as useTabsHook } from '@hooks/use-tabs'
2
- import { Tabs as T } from '@components/primitives/tabs'
3
- import { Link } from '@components/primitives/link'
4
- import type { BoltdocsTab, ComponentRoute } from '@client/types'
1
+ import { useTabs as useTabsHook } from '../../hooks/use-tabs'
2
+ import { Tabs as T } from '../primitives/tabs'
3
+ import { Link } from '../primitives/link'
4
+ import type { BoltdocsTab, ComponentRoute } from '../../types'
5
5
  import * as Icons from 'lucide-react'
6
- import { getTranslated } from '@client/utils/i18n'
7
- import { useRoutes } from '@hooks/use-routes'
6
+ import { getTranslated } from '../../utils/i18n'
7
+ import { useRoutes } from '../../hooks/use-routes'
8
8
 
9
9
  export function Tabs({
10
10
  tabs,
@@ -50,10 +50,11 @@ export function Tabs({
50
50
  ref={(el: HTMLAnchorElement | null) => {
51
51
  tabRefs.current[index] = el
52
52
  }}
53
- className={`relative flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors outline-none ${isActive
53
+ className={`relative flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors outline-none ${
54
+ isActive
54
55
  ? 'text-primary-500'
55
56
  : 'text-text-muted hover:text-text-main'
56
- }`}
57
+ }`}
57
58
  >
58
59
  {renderTabIcon(tab.icon)}
59
60
  <span>{getTranslated(tab.text, currentLocale)}</span>
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import { Sun, Moon, Monitor } from 'lucide-react'
3
- import { useTheme } from '@client/app/theme-context'
3
+ import { useTheme } from '../../app/theme-context'
4
4
  import { Button } from 'react-aria-components'
5
- import { Menu } from '@components/primitives/menu'
5
+ import { Menu } from '../primitives/menu'
6
6
 
7
7
  export function ThemeToggle() {
8
8
  const { theme, setTheme } = useTheme()
@@ -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/boltdocs-context'
4
+ import { useBoltdocsContext } from '../store/boltdocs-context'
5
5
 
6
6
  export interface LocaleOption {
7
7
  key: string
@@ -25,7 +25,7 @@ export function useI18n(): UseI18nReturn {
25
25
  const routeContext = useRoutes()
26
26
  const { allRoutes, currentRoute, currentLocale, config } = routeContext
27
27
  const i18n = config.i18n
28
- const setLocale = useBoltdocsStore((s) => s.setLocale)
28
+ const { setLocale } = useBoltdocsContext()
29
29
 
30
30
  const handleLocaleChange = (locale: string) => {
31
31
  if (!i18n || locale === currentLocale) return
@@ -1,4 +1,4 @@
1
- import { useConfig } from '@client/app/config-context'
1
+ import { useConfig } from '../app/config-context'
2
2
  import type { LinkProps as RouterLinkProps } from 'react-router-dom'
3
3
  import { useRoutes } from './use-routes'
4
4
 
@@ -1,13 +1,13 @@
1
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
- import { getTranslated } from '@client/utils/i18n'
2
+ import { useConfig } from '../app/config-context'
3
+ import { useTheme } from '../app/theme-context'
4
+ import type { NavbarLink } from '../types'
5
+ import { getTranslated } from '../utils/i18n'
6
6
  import { useRoutes } from './use-routes'
7
7
 
8
8
  export function useNavbar() {
9
9
  const config = useConfig()
10
- const { theme } = useTheme()
10
+ const { theme, resolvedTheme } = useTheme()
11
11
  const location = useLocation()
12
12
  const { currentLocale } = useRoutes()
13
13
 
@@ -67,11 +67,13 @@ export function useNavbar() {
67
67
  })
68
68
 
69
69
  const logo = themeConfig.logo
70
+ // Use resolvedTheme so 'system' correctly maps to 'dark' or 'light'
71
+ // based on the OS preference, instead of always falling back to 'light'.
70
72
  const logoSrc = !logo
71
73
  ? null
72
74
  : typeof logo === 'string'
73
75
  ? logo
74
- : theme === 'dark'
76
+ : resolvedTheme === 'dark'
75
77
  ? (logo as any).dark
76
78
  : (logo as any).light
77
79
 
@@ -1,7 +1,7 @@
1
1
  import { useLocation } from 'react-router-dom'
2
- import { useConfig } from '@client/app/config-context'
3
- import { usePreload } from '@client/app/preload'
4
- import { useBoltdocsStore } from '../store/boltdocs-context'
2
+ import { useConfig } from '../app/config-context'
3
+ import { useRoutesContext } from '../app/routes-context'
4
+ import { useBoltdocsContext } from '../store/boltdocs-context'
5
5
 
6
6
  /**
7
7
  * Hook to access the framework's routing state.
@@ -9,17 +9,25 @@ import { useBoltdocsStore } from '../store/boltdocs-context'
9
9
  * version and locale.
10
10
  */
11
11
  export function useRoutes() {
12
- const { routes: allRoutes } = usePreload()
12
+ const { routes: allRoutes } = useRoutesContext()
13
13
  const config = useConfig()
14
14
  const location = useLocation()
15
15
 
16
16
  // Use Zustand store for active state
17
- const currentLocaleStore = useBoltdocsStore((s) => s.currentLocale)
18
- const currentVersionStore = useBoltdocsStore((s) => s.currentVersion)
19
- const hasHydrated = useBoltdocsStore((s) => s.hasHydrated)
17
+ const {
18
+ hasHydrated,
19
+ currentLocale: currentLocaleStore,
20
+ currentVersion: currentVersionStore,
21
+ } = useBoltdocsContext()
22
+
23
+ const normalize = (p: string) =>
24
+ p.endsWith('/') && p.length > 1 ? p.slice(0, -1) : p
25
+ const currentPath = normalize(location.pathname)
20
26
 
21
27
  // Find the current route matching the pathname
22
- const currentRoute = allRoutes.find((r) => r.path === location.pathname)
28
+ const currentRoute = allRoutes?.find?.(
29
+ (r) => normalize(r.path) === currentPath,
30
+ )
23
31
 
24
32
  // Derive current locale and version
25
33
  // Priority: URL (currentRoute) > Zustand Store (Persistence) > Config Default
@@ -36,7 +44,7 @@ export function useRoutes() {
36
44
  : undefined
37
45
 
38
46
  // Filter routes to those matching the current version and locale
39
- const routes = allRoutes.filter((r) => {
47
+ const routes = allRoutes?.filter?.((r) => {
40
48
  const localeMatch = config.i18n
41
49
  ? (r.locale || config.i18n.defaultLocale) === currentLocale
42
50
  : true
@@ -53,7 +61,7 @@ export function useRoutes() {
53
61
  const isCurrentRoutePrefixed = !!currentRoute?.locale
54
62
  const isRoutePrefixed = !!r.locale
55
63
 
56
- const hasAlternate = allRoutes.some(
64
+ const hasAlternate = allRoutes?.some?.(
57
65
  (alt) =>
58
66
  alt !== r &&
59
67
  alt.filePath === r.filePath &&
@@ -78,7 +86,7 @@ export function useRoutes() {
78
86
  config.i18n?.locales[currentLocale as string] ||
79
87
  currentLocale
80
88
 
81
- const currentVersionConfig = config.versions?.versions.find(
89
+ const currentVersionConfig = config.versions?.versions?.find?.(
82
90
  (v) => v.path === currentVersion,
83
91
  )
84
92
  const currentVersionLabel = currentVersionConfig?.label || currentVersion
@@ -1,7 +1,7 @@
1
1
  import { useState, useMemo, useEffect } from 'react'
2
2
  import { Index } from 'flexsearch'
3
3
  import { useRoutes } from './use-routes'
4
- import type { ComponentRoute } from '@client/types'
4
+ import type { ComponentRoute } from '../types'
5
5
  // @ts-ignore
6
6
  import searchData from 'virtual:boltdocs-search'
7
7
 
@@ -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/boltdocs-context'
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,32 @@
1
- export type { BoltdocsConfig, BoltdocsThemeConfig } from '../node/config'
2
1
  export type {
3
2
  ComponentRoute,
4
- CreateBoltdocsAppOptions,
5
3
  LayoutProps,
6
4
  } 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'
5
+ export * from './ssg'
6
+ export { useConfig } from './app/config-context'
7
+ export { useTheme } from './app/theme-context'
8
+ export { useRoutes } from './hooks/use-routes'
9
+ export { useMdxComponents } from './app/mdx-components-context'
12
10
 
13
11
  // Hooks
14
- export * from '@hooks/index'
12
+ export * from './hooks/index'
15
13
  // Composable layout building blocks
16
- export { DocsLayout } from '@components/docs-layout'
17
- export { DefaultLayout } from '@components/default-layout'
14
+ export { DocsLayout } from './components/docs-layout'
18
15
 
19
16
  // 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'
17
+ export { Navbar } from './components/ui-base/navbar'
18
+ export { Sidebar } from './components/ui-base/sidebar'
19
+ export { OnThisPage } from './components/ui-base/on-this-page'
20
+ export { Head } from './components/ui-base/head'
21
+ export { Breadcrumbs } from './components/ui-base/breadcrumbs'
22
+ export { PageNav } from './components/ui-base/page-nav'
23
+ export { ErrorBoundary } from './components/ui-base/error-boundary'
24
+ export { CopyMarkdown } from './components/ui-base/copy-markdown'
28
25
 
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'
26
+ export { NotFound } from './components/ui-base/not-found'
27
+ export { Loading } from './components/ui-base/loading'
28
+ export { CodeBlock } from './components/mdx/code-block'
29
+ export { Video } from './components/mdx/video'
33
30
 
34
31
  // MDX Components
35
32
  export {
@@ -104,3 +101,4 @@ export { Skeleton as PrimitiveSkeleton } from './components/primitives/skeleton'
104
101
 
105
102
  // Utilities
106
103
  export { cn } from './utils/cn'
104
+ export { getTranslated } from './utils/i18n'