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,116 @@
1
+ import * as RAC from 'react-aria-components'
2
+ import { ChevronDown } from 'lucide-react'
3
+ import { cn } from '@client/utils/cn'
4
+ import type { ComponentBase, CompoundComponent } from './types'
5
+
6
+ export interface NavigationMenuItemProps extends ComponentBase {
7
+ label: string
8
+ }
9
+
10
+ export interface NavigationMenuLinkProps
11
+ extends Omit<ComponentBase, 'children'> {
12
+ href: string
13
+ label: string
14
+ description?: string
15
+ children?:
16
+ | React.ReactNode
17
+ | ((opts: RAC.MenuItemRenderProps) => React.ReactNode)
18
+ }
19
+
20
+ export type NavigationMenuComponent = CompoundComponent<
21
+ ComponentBase,
22
+ {
23
+ List: React.FC<ComponentBase>
24
+ Item: React.FC<NavigationMenuItemProps>
25
+ Link: React.FC<NavigationMenuLinkProps>
26
+ }
27
+ >
28
+
29
+ const NavigationMenuRoot = ({
30
+ children,
31
+ className,
32
+ ...props
33
+ }: ComponentBase) => {
34
+ return (
35
+ <nav className={cn('relative flex items-center', className)} {...props}>
36
+ {children}
37
+ </nav>
38
+ )
39
+ }
40
+
41
+ const NavigationMenuList = ({ children, className }: ComponentBase) => {
42
+ return (
43
+ <div className={cn('flex list-none items-center gap-1', className)}>
44
+ {children}
45
+ </div>
46
+ )
47
+ }
48
+
49
+ const NavigationMenuItem = ({
50
+ children,
51
+ label,
52
+ className,
53
+ }: NavigationMenuItemProps) => {
54
+ return (
55
+ <RAC.MenuTrigger>
56
+ <RAC.Button
57
+ className={cn(
58
+ 'flex items-center gap-1 rounded-md px-3 py-1.5 text-sm font-medium outline-none transition-colors cursor-pointer',
59
+ 'text-text-muted hover:bg-bg-surface hover:text-text-main',
60
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
61
+ className,
62
+ )}
63
+ >
64
+ {label}
65
+ <ChevronDown size={14} className="transition-transform" />
66
+ </RAC.Button>
67
+ <RAC.Popover
68
+ placement="bottom start"
69
+ className="entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95 fill-mode-forwards"
70
+ >
71
+ <RAC.Menu className="w-56 outline-none rounded-xl border border-border-subtle bg-bg-surface p-2 shadow-xl ring-1 ring-border-strong/5">
72
+ {children as any}
73
+ </RAC.Menu>
74
+ </RAC.Popover>
75
+ </RAC.MenuTrigger>
76
+ )
77
+ }
78
+
79
+ const NavigationMenuLink = ({
80
+ label,
81
+ href,
82
+ description,
83
+ className,
84
+ children,
85
+ ...props
86
+ }: NavigationMenuLinkProps) => {
87
+ return (
88
+ <RAC.MenuItem
89
+ href={href}
90
+ className={cn(
91
+ 'block rounded-lg px-3 py-2 text-sm outline-none cursor-pointer transition-colors',
92
+ 'hover:bg-bg-muted focus:bg-bg-muted',
93
+ className,
94
+ )}
95
+ {...props}
96
+ >
97
+ {children || (
98
+ <>
99
+ <div className="font-semibold text-text-main">{label}</div>
100
+ {description && (
101
+ <div className="text-xs text-text-muted line-clamp-1 mt-0.5">
102
+ {description}
103
+ </div>
104
+ )}
105
+ </>
106
+ )}
107
+ </RAC.MenuItem>
108
+ )
109
+ }
110
+
111
+ export default {
112
+ NavigationMenuRoot,
113
+ NavigationMenuList,
114
+ NavigationMenuItem,
115
+ NavigationMenuLink,
116
+ }
@@ -0,0 +1,461 @@
1
+ import React, {
2
+ createContext,
3
+ use,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type ReactNode,
10
+ type RefObject,
11
+ } from 'react'
12
+ import scrollIntoView from 'scroll-into-view-if-needed'
13
+ import { cn } from '../../utils/cn'
14
+ import { useOnChange } from '../../utils/use-on-change'
15
+ import type { ComponentBase, CompoundComponent } from './types'
16
+ import { getItemId } from './helpers/observer'
17
+
18
+ export interface TOCItemType {
19
+ title: ReactNode
20
+ url: string
21
+ depth: number
22
+ _step?: number
23
+ }
24
+
25
+ export type TableOfContents = TOCItemType[]
26
+
27
+ export interface TOCItemInfo {
28
+ id: string
29
+ active: boolean
30
+ /** last time the item is updated */
31
+ t: number
32
+ /** currently active but not intersecting in viewport */
33
+ fallback: boolean
34
+ original?: TOCItemType
35
+ }
36
+
37
+ export interface AnchorProviderProps {
38
+ toc: TOCItemType[]
39
+ /**
40
+ * Only accept one active item at most
41
+ * @defaultValue false
42
+ */
43
+ single?: boolean
44
+ children?: ReactNode
45
+ }
46
+
47
+ export interface ScrollProviderProps {
48
+ /**
49
+ * Scroll into the view of container when active
50
+ */
51
+ containerRef: RefObject<HTMLElement | null>
52
+ children?: ReactNode
53
+ }
54
+
55
+ export interface OnThisPageContentProps extends ComponentBase {
56
+ ref?: React.Ref<HTMLDivElement>
57
+ scrollRef?: RefObject<HTMLElement | null>
58
+ }
59
+
60
+ export interface OnThisPageItemProps extends ComponentBase {
61
+ level?: number
62
+ }
63
+
64
+ export interface OnThisPageLinkProps extends ComponentBase {
65
+ href?: string
66
+ active?: boolean
67
+ onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void
68
+ }
69
+
70
+ export interface OnThisPageIndicatorProps extends ComponentBase {
71
+ style?: React.CSSProperties
72
+ }
73
+
74
+ const ItemsContext = createContext<TOCItemInfo[] | null>(null)
75
+ const ScrollContext = createContext<RefObject<HTMLElement | null> | null>(null)
76
+
77
+ class Observer {
78
+ items: TOCItemInfo[] = []
79
+ single = false
80
+ private observer: IntersectionObserver | null = null
81
+ onChange?: () => void
82
+
83
+ private callback(entries: IntersectionObserverEntry[]) {
84
+ if (entries.length === 0) return
85
+
86
+ let hasActive = false
87
+ this.items = this.items.map((item) => {
88
+ const entry = entries.find((entry) => entry.target.id === item.id)
89
+ let active = entry ? entry.isIntersecting : item.active && !item.fallback
90
+ if (this.single && hasActive) active = false
91
+
92
+ if (item.active !== active) {
93
+ item = {
94
+ ...item,
95
+ t: Date.now(),
96
+ active,
97
+ fallback: false,
98
+ }
99
+ }
100
+
101
+ if (active) hasActive = true
102
+ return item
103
+ })
104
+
105
+ if (!hasActive && entries[0].rootBounds) {
106
+ const viewTop = entries[0].rootBounds.top
107
+ let min = Number.MAX_VALUE
108
+ let fallbackIdx = -1
109
+
110
+ for (let i = 0; i < this.items.length; i++) {
111
+ const element = document.getElementById(this.items[i].id)
112
+ if (!element) continue
113
+
114
+ const d = Math.abs(viewTop - element.getBoundingClientRect().top)
115
+ if (d < min) {
116
+ fallbackIdx = i
117
+ min = d
118
+ }
119
+ }
120
+
121
+ if (fallbackIdx !== -1) {
122
+ this.items[fallbackIdx] = {
123
+ ...this.items[fallbackIdx],
124
+ active: true,
125
+ fallback: true,
126
+ t: Date.now(),
127
+ }
128
+ }
129
+ }
130
+
131
+ this.onChange?.()
132
+ }
133
+
134
+ setItems(newItems: TOCItemType[]) {
135
+ const observer = this.observer
136
+ if (observer) {
137
+ for (const item of this.items) {
138
+ const element = document.getElementById(item.id)
139
+ if (!element) continue
140
+ observer.unobserve(element)
141
+ }
142
+ }
143
+
144
+ this.items = []
145
+ for (const item of newItems) {
146
+ const id = getItemId(item.url)
147
+ if (!id) continue
148
+
149
+ this.items.push({
150
+ id,
151
+ active: false,
152
+ fallback: false,
153
+ t: 0,
154
+ original: item,
155
+ })
156
+ }
157
+ this.watchItems()
158
+
159
+ // In an SPA, the TOC might update before the MDX content is in the DOM.
160
+ // We perform a few delayed scans to ensure we catch those elements.
161
+ if (typeof window !== 'undefined') {
162
+ setTimeout(() => this.watchItems(), 100)
163
+ setTimeout(() => this.watchItems(), 500)
164
+ setTimeout(() => this.watchItems(), 1000)
165
+ }
166
+
167
+ this.onChange?.()
168
+ }
169
+
170
+ watch(options?: IntersectionObserverInit) {
171
+ if (this.observer) return
172
+ this.observer = new IntersectionObserver(this.callback.bind(this), options)
173
+ this.watchItems()
174
+ }
175
+
176
+ private watchItems() {
177
+ if (!this.observer) return
178
+ for (const item of this.items) {
179
+ const element = document.getElementById(item.id)
180
+ if (!element) continue
181
+ this.observer.observe(element)
182
+ }
183
+ }
184
+
185
+ unwatch() {
186
+ this.observer?.disconnect()
187
+ this.observer = null
188
+ }
189
+ }
190
+
191
+ export function useItems() {
192
+ const ctx = use(ItemsContext)
193
+ if (!ctx)
194
+ throw new Error(
195
+ `Component must be used under the <AnchorProvider /> component.`,
196
+ )
197
+ return ctx
198
+ }
199
+
200
+ export function useScrollStatus(ref: RefObject<HTMLElement | null>) {
201
+ const [status, setStatus] = useState({
202
+ isOverflowing: false,
203
+ isAtBottom: false,
204
+ })
205
+
206
+ useEffect(() => {
207
+ const el = ref.current
208
+ if (!el) return
209
+
210
+ const checkStatus = () => {
211
+ const isOverflowing = el.scrollHeight > el.clientHeight
212
+ // We use a 2px threshold for floating point math issues
213
+ const isAtBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 2
214
+ setStatus({ isOverflowing, isAtBottom })
215
+ }
216
+
217
+ checkStatus()
218
+ el.addEventListener('scroll', checkStatus, { passive: true })
219
+ window.addEventListener('resize', checkStatus)
220
+
221
+ const mutationObserver = new MutationObserver(checkStatus)
222
+ mutationObserver.observe(el, { childList: true, subtree: true })
223
+
224
+ return () => {
225
+ el.removeEventListener('scroll', checkStatus)
226
+ window.removeEventListener('resize', checkStatus)
227
+ mutationObserver.disconnect()
228
+ }
229
+ }, [ref])
230
+
231
+ return status
232
+ }
233
+
234
+ export function useActiveAnchor(): string | undefined {
235
+ const items = useItems()
236
+ return useMemo(() => {
237
+ let out: TOCItemInfo | undefined
238
+ for (const item of items) {
239
+ if (!item.active) continue
240
+ if (!out || item.t > out.t) {
241
+ out = item
242
+ }
243
+ }
244
+ return out?.id
245
+ }, [items])
246
+ }
247
+
248
+ export function useActiveAnchors(): string[] {
249
+ const items = useItems()
250
+ return useMemo(() => {
251
+ const out: string[] = []
252
+ for (const item of items) {
253
+ if (item.active) out.push(item.id)
254
+ }
255
+ return out
256
+ }, [items])
257
+ }
258
+
259
+ /** Optional: add auto-scroll to TOC items. */
260
+ export function ScrollProvider({
261
+ containerRef,
262
+ children,
263
+ }: ScrollProviderProps) {
264
+ return (
265
+ <ScrollContext.Provider value={containerRef}>
266
+ {children}
267
+ </ScrollContext.Provider>
268
+ )
269
+ }
270
+
271
+ export function AnchorProvider({
272
+ toc,
273
+ single = false,
274
+ children,
275
+ }: AnchorProviderProps) {
276
+ const observer = useMemo(() => new Observer(), [])
277
+ const [items, setItems] = useState<TOCItemInfo[]>(observer.items)
278
+
279
+ observer.single = single
280
+ useEffect(() => {
281
+ observer.setItems(toc)
282
+ }, [observer, toc])
283
+
284
+ useEffect(() => {
285
+ observer.watch({
286
+ rootMargin: '0px',
287
+ threshold: 0.98,
288
+ })
289
+ observer.onChange = () => setItems([...observer.items])
290
+
291
+ return () => {
292
+ observer.unwatch()
293
+ }
294
+ }, [observer])
295
+
296
+ return <ItemsContext.Provider value={items}>{children}</ItemsContext.Provider>
297
+ }
298
+
299
+ export const OnThisPageRoot = ({ children, className }: ComponentBase) => {
300
+ return (
301
+ <nav
302
+ className={cn(
303
+ 'sticky top-navbar hidden xl:flex flex-col shrink-0',
304
+ 'w-toc',
305
+ 'py-8 pl-6 pr-4',
306
+ className,
307
+ )}
308
+ >
309
+ {children}
310
+ </nav>
311
+ )
312
+ }
313
+
314
+ export const OnThisPageHeader = ({
315
+ children,
316
+ className,
317
+ ...props
318
+ }: ComponentBase) => {
319
+ return (
320
+ <div
321
+ className={cn(
322
+ 'mb-4 text-xs font-bold uppercase tracking-wider text-text-main',
323
+ className,
324
+ )}
325
+ {...props}
326
+ >
327
+ {children}
328
+ </div>
329
+ )
330
+ }
331
+
332
+ export const OnThisPageContent = ({
333
+ children,
334
+ className,
335
+ ref,
336
+ ...props
337
+ }: OnThisPageContentProps) => {
338
+ const internalRef = useRef<HTMLDivElement>(null)
339
+
340
+ useImperativeHandle(ref, () => internalRef.current!)
341
+
342
+ const { isOverflowing, isAtBottom } = useScrollStatus(internalRef)
343
+
344
+ return (
345
+ <div
346
+ ref={internalRef}
347
+ className={cn(
348
+ 'relative overflow-y-auto boltdocs-otp-content',
349
+ isOverflowing &&
350
+ !isAtBottom &&
351
+ 'mask-[linear-gradient(to_bottom,black_85%,transparent_100%)]',
352
+ className,
353
+ )}
354
+ {...props}
355
+ >
356
+ {children}
357
+ </div>
358
+ )
359
+ }
360
+
361
+ OnThisPageContent.displayName = 'OnThisPageContent'
362
+
363
+ export const OnThisPageList = ({ children, className }: ComponentBase) => {
364
+ return (
365
+ <ul
366
+ className={cn(
367
+ 'relative space-y-1 text-sm border-l border-border-subtle',
368
+ className,
369
+ )}
370
+ >
371
+ {children}
372
+ </ul>
373
+ )
374
+ }
375
+
376
+ export const OnThisPageItem = ({
377
+ level,
378
+ children,
379
+ className,
380
+ }: OnThisPageItemProps) => {
381
+ return <li className={cn(level === 3 && 'pl-3', className)}>{children}</li>
382
+ }
383
+
384
+ export const OnThisPageLink = ({
385
+ children,
386
+ href,
387
+ active,
388
+ onClick,
389
+ className,
390
+ }: OnThisPageLinkProps) => {
391
+ const items = use(ItemsContext)
392
+ const containerRef = use(ScrollContext)
393
+ const id = href ? getItemId(href) : null
394
+ const anchorRef = useRef<HTMLAnchorElement>(null)
395
+ const [internalActive, setInternalActive] = useState(active)
396
+
397
+ useOnChange(
398
+ id && items ? items.find((i) => i.id === id)?.active : null,
399
+ (newActive) => {
400
+ if (newActive !== null && newActive !== internalActive) {
401
+ setInternalActive(!!newActive)
402
+
403
+ if (newActive && anchorRef.current && containerRef?.current) {
404
+ scrollIntoView(anchorRef.current, {
405
+ behavior: 'smooth',
406
+ block: 'center',
407
+ inline: 'center',
408
+ scrollMode: 'if-needed',
409
+ boundary: containerRef.current,
410
+ })
411
+ }
412
+ }
413
+ },
414
+ )
415
+
416
+ // Also sync with external active prop if provided
417
+ useEffect(() => {
418
+ if (active !== undefined) setInternalActive(active)
419
+ }, [active])
420
+
421
+ return (
422
+ <a
423
+ ref={anchorRef}
424
+ href={href}
425
+ onClick={onClick}
426
+ data-active={internalActive}
427
+ className={cn(
428
+ 'block py-1 pl-4 text-[13px] outline-none transition-colors hover:text-text-main',
429
+ internalActive ? 'text-primary-500 font-medium' : 'text-text-muted',
430
+ className,
431
+ )}
432
+ >
433
+ {children}
434
+ </a>
435
+ )
436
+ }
437
+
438
+ export const OnThisPageIndicator = ({
439
+ style,
440
+ className,
441
+ }: OnThisPageIndicatorProps) => {
442
+ return (
443
+ <div
444
+ className={cn(
445
+ 'absolute -left-px w-0.5 rounded-full bg-primary-500 transition-all duration-300',
446
+ className,
447
+ )}
448
+ style={style}
449
+ />
450
+ )
451
+ }
452
+
453
+ export default {
454
+ OnThisPageRoot,
455
+ OnThisPageHeader,
456
+ OnThisPageContent,
457
+ OnThisPageList,
458
+ OnThisPageItem,
459
+ OnThisPageLink,
460
+ OnThisPageIndicator,
461
+ }
@@ -0,0 +1,87 @@
1
+ import * as RAC from 'react-aria-components'
2
+ import { ChevronLeft, ChevronRight } from 'lucide-react'
3
+ import { cn } from '@client/utils/cn'
4
+ import type { ComponentBase } from './types'
5
+
6
+ export interface PageNavProps extends ComponentBase {
7
+ to: string
8
+ direction: 'prev' | 'next'
9
+ }
10
+
11
+ export const PageNavRoot = ({ children, className }: ComponentBase) => {
12
+ return (
13
+ <nav
14
+ className={cn(
15
+ 'grid grid-cols-1 sm:grid-cols-2 gap-4 mt-12 pt-8 border-t border-border-subtle',
16
+ className,
17
+ )}
18
+ >
19
+ {children}
20
+ </nav>
21
+ )
22
+ }
23
+
24
+ export const PageNavLink = ({
25
+ children,
26
+ to,
27
+ direction,
28
+ className,
29
+ }: PageNavProps) => {
30
+ const isNext = direction === 'next'
31
+ return (
32
+ <RAC.Link
33
+ href={to}
34
+ className={cn(
35
+ 'flex group items-center p-4 rounded-xl border border-border-subtle bg-bg-surface outline-none',
36
+ 'transition-all hover:bg-bg-main hover:border-primary-500 hover:shadow-lg',
37
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
38
+ isNext ? 'text-right justify-end' : 'text-left justify-start',
39
+ className,
40
+ )}
41
+ >
42
+ {!isNext && (
43
+ <ChevronLeft className="mr-3 h-5 w-5 text-text-muted group-hover:text-primary-500 transition-transform group-hover:-translate-x-1" />
44
+ )}
45
+ <div className="flex flex-col gap-1 flex-1">{children}</div>
46
+ {isNext && (
47
+ <ChevronRight className="ml-3 h-5 w-5 text-text-muted group-hover:text-primary-500 transition-transform group-hover:translate-x-1" />
48
+ )}
49
+ </RAC.Link>
50
+ )
51
+ }
52
+
53
+ export const PageNavTitle = ({ children, className }: ComponentBase) => {
54
+ return (
55
+ <span
56
+ className={cn(
57
+ 'text-xs font-medium uppercase tracking-wider text-text-muted',
58
+ className,
59
+ )}
60
+ >
61
+ {children}
62
+ </span>
63
+ )
64
+ }
65
+
66
+ export const PageNavDescription = ({ children, className }: ComponentBase) => {
67
+ return (
68
+ <span
69
+ className={cn('text-base font-bold text-text-main truncate', className)}
70
+ >
71
+ {children}
72
+ </span>
73
+ )
74
+ }
75
+
76
+ export const PageNavIcon = ({ children }: ComponentBase) => {
77
+ return <>{children}</>
78
+ }
79
+
80
+ export default {
81
+ PageNavRoot,
82
+ PageNavLink: Object.assign(PageNavLink, {
83
+ Title: PageNavTitle,
84
+ Description: PageNavDescription,
85
+ Icon: PageNavIcon,
86
+ }),
87
+ }
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import * as RAC from 'react-aria-components'
4
+ import { cn } from '@client/utils/cn'
5
+
6
+ export interface PopoverProps extends Omit<RAC.PopoverProps, 'children'> {
7
+ children: React.ReactNode
8
+ className?: string
9
+ showArrow?: boolean
10
+ }
11
+
12
+ /**
13
+ * A reusable Popover primitive with premium glassmorphism styling and smooth animations.
14
+ */
15
+ export const Popover = ({
16
+ children,
17
+ className,
18
+ showArrow,
19
+ ...props
20
+ }: PopoverProps) => {
21
+ return (
22
+ <RAC.Popover
23
+ offset={8}
24
+ {...props}
25
+ className={RAC.composeRenderProps(className, (className) =>
26
+ cn(
27
+ 'z-50 overflow-auto rounded-xl border border-border-subtle bg-bg-surface/80 shadow-xl backdrop-blur-md outline-none',
28
+ 'entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95 fill-mode-forwards',
29
+ className,
30
+ ),
31
+ )}
32
+ >
33
+ {showArrow && (
34
+ <RAC.OverlayArrow className="group">
35
+ <svg
36
+ viewBox="0 0 12 12"
37
+ className="block h-3 w-3 fill-bg-surface/80 stroke-border-subtle group-placement-bottom:rotate-180 group-placement-left:-rotate-90 group-placement-right:rotate-90"
38
+ aria-hidden="true"
39
+ >
40
+ <path d="M0 0 L6 6 L12 0" />
41
+ </svg>
42
+ </RAC.OverlayArrow>
43
+ )}
44
+ {children as any}
45
+ </RAC.Popover>
46
+ )
47
+ }