boltdocs 2.2.0 → 2.4.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 (124) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/boltdocs.js +2 -2
  3. package/dist/base-ui/index.d.mts +4 -4
  4. package/dist/base-ui/index.d.ts +4 -4
  5. package/dist/base-ui/index.js +1 -1
  6. package/dist/base-ui/index.mjs +1 -1
  7. package/dist/{cache-CRAZ55X7.mjs → cache-P6WK424C.mjs} +1 -1
  8. package/dist/chunk-2DI3OGHV.mjs +1 -0
  9. package/dist/chunk-2Z5T6EAU.mjs +1 -0
  10. package/dist/chunk-64AJ5QLT.mjs +1 -0
  11. package/dist/chunk-DDX52BX4.mjs +1 -0
  12. package/dist/chunk-HRZDSFR5.mjs +1 -0
  13. package/dist/chunk-PPVDMDEL.mjs +1 -0
  14. package/dist/chunk-UBE4CKOA.mjs +1 -0
  15. package/dist/chunk-UWT4AJTH.mjs +73 -0
  16. package/dist/chunk-WWJ7WKDI.mjs +1 -0
  17. package/dist/chunk-Y4RRHPXC.mjs +1 -0
  18. package/dist/client/index.d.mts +15 -21
  19. package/dist/client/index.d.ts +15 -21
  20. package/dist/client/index.js +1 -1
  21. package/dist/client/index.mjs +1 -1
  22. package/dist/client/ssr.js +1 -1
  23. package/dist/client/ssr.mjs +1 -1
  24. package/dist/client/types.d.mts +1 -1
  25. package/dist/client/types.d.ts +1 -1
  26. package/dist/client/types.js +1 -1
  27. package/dist/{copy-markdown-CbS8X-qe.d.mts → copy-markdown--9yjpbyy.d.mts} +1 -1
  28. package/dist/{copy-markdown-C-90ixSe.d.ts → copy-markdown-l2MYkcG7.d.ts} +1 -1
  29. package/dist/hooks/index.d.mts +8 -16
  30. package/dist/hooks/index.d.ts +8 -16
  31. package/dist/hooks/index.js +1 -1
  32. package/dist/hooks/index.mjs +1 -1
  33. package/dist/integrations/index.d.mts +1 -1
  34. package/dist/integrations/index.d.ts +1 -1
  35. package/dist/{loading-chS3pm9W.d.ts → loading-BwUos0wZ.d.mts} +5 -16
  36. package/dist/{loading-BqGrFWO5.d.mts → loading-nlnUD01v.d.ts} +5 -16
  37. package/dist/mdx/index.d.mts +4 -2
  38. package/dist/mdx/index.d.ts +4 -2
  39. package/dist/mdx/index.js +1 -1
  40. package/dist/mdx/index.mjs +1 -1
  41. package/dist/node/cli-entry.js +25 -22
  42. package/dist/node/cli-entry.mjs +5 -1
  43. package/dist/node/index.d.mts +0 -9
  44. package/dist/node/index.d.ts +0 -9
  45. package/dist/node/index.js +14 -15
  46. package/dist/node/index.mjs +1 -1
  47. package/dist/primitives/index.d.mts +13 -22
  48. package/dist/primitives/index.d.ts +13 -22
  49. package/dist/primitives/index.js +1 -1
  50. package/dist/primitives/index.mjs +1 -1
  51. package/dist/search-dialog-OONKKC5H.mjs +1 -0
  52. package/dist/{types-j7jvWsJj.d.ts → types-opDA2E9-.d.mts} +4 -11
  53. package/dist/{types-j7jvWsJj.d.mts → types-opDA2E9-.d.ts} +4 -11
  54. package/dist/{use-routes-Cd806kGw.d.ts → use-routes-DNwgTRpU.d.ts} +1 -1
  55. package/dist/{use-routes-DDL0_jkQ.d.mts → use-routes-DrT80Eom.d.mts} +1 -1
  56. package/package.json +2 -1
  57. package/src/client/app/index.tsx +20 -9
  58. package/src/client/app/mdx-components-context.tsx +2 -2
  59. package/src/client/app/mdx-page.tsx +0 -1
  60. package/src/client/app/scroll-handler.tsx +21 -10
  61. package/src/client/app/theme-context.tsx +14 -7
  62. package/src/client/components/default-layout.tsx +6 -4
  63. package/src/client/components/docs-layout.tsx +34 -4
  64. package/src/client/components/icons-dev.tsx +154 -0
  65. package/src/client/components/mdx/code-block.tsx +57 -5
  66. package/src/client/components/mdx/component-preview.tsx +1 -0
  67. package/src/client/components/mdx/file-tree.tsx +35 -0
  68. package/src/client/components/primitives/helpers/observer.ts +30 -39
  69. package/src/client/components/primitives/index.ts +1 -0
  70. package/src/client/components/primitives/menu.tsx +18 -12
  71. package/src/client/components/primitives/navbar.tsx +34 -93
  72. package/src/client/components/primitives/on-this-page.tsx +7 -161
  73. package/src/client/components/primitives/popover.tsx +1 -2
  74. package/src/client/components/primitives/search-dialog.tsx +4 -4
  75. package/src/client/components/primitives/sidebar.tsx +3 -2
  76. package/src/client/components/primitives/skeleton.tsx +26 -0
  77. package/src/client/components/ui-base/copy-markdown.tsx +4 -10
  78. package/src/client/components/ui-base/index.ts +0 -1
  79. package/src/client/components/ui-base/loading.tsx +43 -73
  80. package/src/client/components/ui-base/navbar.tsx +18 -15
  81. package/src/client/components/ui-base/page-nav.tsx +2 -1
  82. package/src/client/components/ui-base/powered-by.tsx +4 -1
  83. package/src/client/components/ui-base/search-dialog.tsx +16 -5
  84. package/src/client/components/ui-base/sidebar.tsx +4 -2
  85. package/src/client/hooks/use-i18n.ts +3 -2
  86. package/src/client/hooks/use-localized-to.ts +6 -5
  87. package/src/client/hooks/use-navbar.ts +37 -6
  88. package/src/client/hooks/use-page-nav.ts +27 -6
  89. package/src/client/hooks/use-routes.ts +2 -1
  90. package/src/client/hooks/use-search.ts +81 -59
  91. package/src/client/hooks/use-sidebar.ts +2 -1
  92. package/src/client/index.ts +0 -1
  93. package/src/client/store/use-boltdocs-store.ts +6 -5
  94. package/src/client/theme/neutral.css +31 -3
  95. package/src/client/types.ts +2 -2
  96. package/src/node/{cli.ts → cli/build.ts} +17 -23
  97. package/src/node/cli/dev.ts +22 -0
  98. package/src/node/cli/doctor.ts +243 -0
  99. package/src/node/cli/index.ts +9 -0
  100. package/src/node/cli/ui.ts +54 -0
  101. package/src/node/cli-entry.ts +16 -16
  102. package/src/node/config.ts +1 -15
  103. package/src/node/mdx/cache.ts +1 -1
  104. package/src/node/mdx/index.ts +2 -0
  105. package/src/node/mdx/rehype-shiki.ts +9 -0
  106. package/src/node/mdx/remark-code-meta.ts +35 -0
  107. package/src/node/mdx/remark-shiki.ts +1 -1
  108. package/src/node/plugin/entry.ts +22 -15
  109. package/src/node/plugin/index.ts +46 -14
  110. package/src/node/routes/parser.ts +12 -9
  111. package/src/node/search/index.ts +55 -0
  112. package/src/node/ssg/index.ts +83 -15
  113. package/src/node/ssg/robots.ts +7 -4
  114. package/dist/chunk-5D6XPYQ3.mjs +0 -74
  115. package/dist/chunk-6QXCKZAT.mjs +0 -1
  116. package/dist/chunk-H4M6P3DM.mjs +0 -1
  117. package/dist/chunk-JXHNX2WN.mjs +0 -1
  118. package/dist/chunk-MZBG4N4W.mjs +0 -1
  119. package/dist/chunk-Q3MLYTIQ.mjs +0 -1
  120. package/dist/chunk-RSII2UPE.mjs +0 -1
  121. package/dist/chunk-ZK2266IZ.mjs +0 -1
  122. package/dist/chunk-ZRJ55GGF.mjs +0 -1
  123. package/dist/search-dialog-MA5AISC7.mjs +0 -1
  124. package/src/client/components/ui-base/progress-bar.tsx +0 -67
@@ -20,7 +20,7 @@ export function MenuTrigger(props: MenuTriggerProps) {
20
20
  return (
21
21
  <RAC.MenuTrigger {...props}>
22
22
  {trigger as any}
23
- <Popover placement={props.placement} className="min-w-[200px]">
23
+ <Popover placement={props.placement} className="min-w-35">
24
24
  {menu as any}
25
25
  </Popover>
26
26
  </RAC.MenuTrigger>
@@ -52,7 +52,10 @@ export function Menu<T extends object>(props: RAC.MenuProps<T>) {
52
52
  <RAC.Menu
53
53
  {...props}
54
54
  className={RAC.composeRenderProps(props.className, (className) =>
55
- cn('p-1.5 outline-none max-h-[inherit] overflow-auto', className),
55
+ cn(
56
+ 'p-1.5 outline-none max-h-[inherit] overflow-auto max-w-75',
57
+ className,
58
+ ),
56
59
  )}
57
60
  />
58
61
  )
@@ -74,11 +77,14 @@ export function MenuItem(props: RAC.MenuItemProps) {
74
77
  props.className,
75
78
  (className, { isFocused, isPressed, isDisabled }) =>
76
79
  cn(
77
- 'group relative flex flex-row items-center gap-2.5 px-3 py-1.5 rounded-lg outline-none cursor-default transition-all duration-200',
78
- 'text-text-main text-[0.8125rem]',
79
- isFocused && 'bg-primary-500/10 text-primary-600 shadow-sm',
80
- isPressed && 'scale-[0.98] bg-primary-500/15',
81
- isDisabled && 'opacity-40 grayscale pointer-events-none',
80
+ 'group relative flex flex-row items-center gap-2 px-2 py-1 rounded-lg outline-none cursor-default hover:cursor-pointer transition-none',
81
+ 'text-text-main text-[12px]',
82
+ {
83
+ 'bg-bg-surface-elevated text-primary-600 ring-1 ring-border-strong/5':
84
+ isFocused,
85
+ 'bg-bg-surface-elevanted': isPressed,
86
+ 'opacity-40 grayscale pointer-events-none': isDisabled,
87
+ },
82
88
  className,
83
89
  ),
84
90
  )}
@@ -88,20 +94,20 @@ export function MenuItem(props: RAC.MenuItemProps) {
88
94
  (children, { selectionMode, isSelected, hasSubmenu }) => (
89
95
  <>
90
96
  {selectionMode !== 'none' && (
91
- <span className="flex items-center w-4 h-4 shrink-0 justify-center">
97
+ <span className="flex items-center size-4 shrink-0 justify-center">
92
98
  {isSelected && selectionMode === 'multiple' && (
93
- <Check className="w-3.5 h-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
99
+ <Check className="size-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
94
100
  )}
95
101
  {isSelected && selectionMode === 'single' && (
96
- <Dot className="w-6 h-6 text-primary-500 animate-in zoom-in-50 duration-200" />
102
+ <Dot className="size-5 text-primary-500 animate-in zoom-in-50 duration-200" />
97
103
  )}
98
104
  </span>
99
105
  )}
100
- <div className="flex-1 flex flex-row items-center gap-2.5 truncate font-medium">
106
+ <div className="flex flex-row w-full transition-colors items-center gap-2 py-1 px-1">
101
107
  {children}
102
108
  </div>
103
109
  {hasSubmenu && (
104
- <ChevronRight className="w-4 h-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
110
+ <ChevronRight className="size-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
105
111
  )}
106
112
  </>
107
113
  ),
@@ -1,16 +1,7 @@
1
1
  import { type ReactNode, useState, useEffect } from 'react'
2
- import {
3
- Button,
4
- Separator,
5
- ToggleButton,
6
- Link,
7
- Menu,
8
- MenuItem,
9
- MenuTrigger,
10
- cn,
11
- } from './index'
2
+ import { Separator, ToggleButton, Link, cn } from './index'
12
3
  import { Button as ButtonRAC } from 'react-aria-components'
13
- import { Search, Sun, Moon, ExternalLink, ChevronDown } from 'lucide-react'
4
+ import { Search, Sun, Moon, ExternalLink } from 'lucide-react'
14
5
  import * as IconsSocials from '@components/icons-dev'
15
6
  import type { ComponentBase } from './types'
16
7
  import type { BoltdocsSocialLink } from '@node/config'
@@ -39,21 +30,6 @@ export interface NavbarThemeProps {
39
30
  onThemeChange: (isSelected: boolean) => void
40
31
  }
41
32
 
42
- export interface NavbarMenuProps extends ComponentBase {
43
- label: ReactNode
44
- icon?: ReactNode
45
- }
46
-
47
- export interface NavbarVersionProps extends ComponentBase {
48
- current: string
49
- }
50
-
51
- export interface NavbarItemProps extends Omit<ComponentBase, 'children'> {
52
- label: string
53
- onPress?: () => void
54
- isCurrent?: boolean
55
- }
56
-
57
33
  export interface NavbarSocialsProps extends ComponentBase {
58
34
  icon: string
59
35
  link: string
@@ -92,13 +68,25 @@ export const NavbarContent = ({ children, className }: ComponentBase) => {
92
68
 
93
69
  export const NavbarLeft = ({ children, className }: ComponentBase) => {
94
70
  return (
95
- <div className={cn('flex items-center gap-4', className)}>{children}</div>
71
+ <div
72
+ className={cn(
73
+ 'flex flex-1 items-center justify-start gap-4 min-w-0',
74
+ className,
75
+ )}
76
+ >
77
+ {children}
78
+ </div>
96
79
  )
97
80
  }
98
81
 
99
82
  export const NavbarRight = ({ children, className }: ComponentBase) => {
100
83
  return (
101
- <div className={cn('flex items-center gap-2 md:gap-4', className)}>
84
+ <div
85
+ className={cn(
86
+ 'flex flex-1 items-center justify-end gap-2 md:gap-4 min-w-0',
87
+ className,
88
+ )}
89
+ >
102
90
  {children}
103
91
  </div>
104
92
  )
@@ -108,7 +96,7 @@ export const NavbarCenter = ({ children, className }: ComponentBase) => {
108
96
  return (
109
97
  <div
110
98
  className={cn(
111
- 'hidden lg:flex flex-1 justify-center items-center gap-4 px-4',
99
+ 'hidden lg:flex flex-1 justify-center items-center gap-4 px-4 min-w-0 w-full',
112
100
  className,
113
101
  )}
114
102
  >
@@ -144,14 +132,16 @@ export const NavbarLogo = ({
144
132
 
145
133
  export const NavbarTitle = ({ children, className }: ComponentBase) => {
146
134
  return (
147
- <span
148
- className={cn(
149
- 'text-lg font-bold tracking-tight hidden sm:inline-block',
150
- className,
151
- )}
152
- >
153
- {children}
154
- </span>
135
+ <Link href="/">
136
+ <span
137
+ className={cn(
138
+ 'text-lg font-bold tracking-tight hidden sm:inline-block',
139
+ className,
140
+ )}
141
+ >
142
+ {children}
143
+ </span>
144
+ </Link>
155
145
  )
156
146
  }
157
147
 
@@ -180,10 +170,10 @@ export const NavbarLink = ({
180
170
  href={href}
181
171
  target={to === 'external' ? '_blank' : undefined}
182
172
  className={cn(
183
- 'transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
173
+ 'transition-colors outline-none font-medium focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
184
174
  {
185
- 'text-primary-500 font-bold': active,
186
- 'text-text-muted hover:text-text-main font-medium': !active,
175
+ 'text-primary-500': active,
176
+ 'text-text-muted hover:text-text-main': !active,
187
177
  },
188
178
  className,
189
179
  )}
@@ -214,9 +204,9 @@ export const NavbarSearchTrigger = ({
214
204
  onPress={onPress}
215
205
  className={cn(
216
206
  'flex items-center gap-2 rounded-full border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-muted outline-none cursor-pointer',
217
- 'transition-colors hover:border-border-strong hover:text-text-main',
207
+ 'transition-all duration-200 hover:border-border-strong hover:text-text-main hover:bg-bg-muted hover:shadow-sm active:scale-[0.98]',
218
208
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
219
- 'w-full max-w-[320px] justify-between',
209
+ 'w-full max-w-[720px] justify-between',
220
210
  className,
221
211
  )}
222
212
  >
@@ -246,8 +236,8 @@ export const NavbarTheme = ({
246
236
  isSelected={theme === 'dark'}
247
237
  onChange={onThemeChange}
248
238
  className={cn(
249
- 'rounded-md p-2 text-text-muted outline-none cursor-pointer transition-colors',
250
- 'hover:bg-bg-surface hover:text-text-main',
239
+ 'rounded-md p-2 text-text-muted outline-none cursor-pointer',
240
+ 'transition-all duration-300 hover:bg-bg-surface hover:text-text-main hover:rotate-12 active:scale-90',
251
241
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
252
242
  className,
253
243
  )}
@@ -258,54 +248,6 @@ export const NavbarTheme = ({
258
248
  )
259
249
  }
260
250
 
261
- export const NavbarMenu = ({
262
- label,
263
- children,
264
- className,
265
- icon,
266
- }: NavbarMenuProps) => {
267
- return (
268
- <MenuTrigger placement="bottom end">
269
- <Button
270
- variant="ghost"
271
- className={cn(
272
- 'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-text-muted outline-none cursor-pointer transition-colors',
273
- 'hover:bg-bg-surface hover:text-text-main',
274
- 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
275
- className,
276
- )}
277
- >
278
- {icon && <span className="flex items-center shrink-0">{icon}</span>}
279
- <span className="text-[13px] font-bold uppercase tracking-wide">
280
- {label}
281
- </span>
282
- <ChevronDown size={14} className="ml-0.5 opacity-50" />
283
- </Button>
284
- <Menu className="min-w-[180px]">{children as any}</Menu>
285
- </MenuTrigger>
286
- )
287
- }
288
-
289
- export const NavbarItem = ({
290
- label,
291
- className,
292
- onPress,
293
- isCurrent,
294
- }: NavbarItemProps) => {
295
- return (
296
- <MenuItem
297
- onAction={onPress}
298
- className={cn(
299
- isCurrent &&
300
- 'bg-primary-500 text-white font-medium hover:bg-primary-600 focus:bg-primary-600 focus:text-white',
301
- className,
302
- )}
303
- >
304
- {label}
305
- </MenuItem>
306
- )
307
- }
308
-
309
251
  export const Icon = ({ name }: { name: BoltdocsSocialLink['icon'] }) => {
310
252
  if (name === 'github') return <IconsSocials.Github />
311
253
  if (name === 'discord') return <IconsSocials.Discord />
@@ -355,7 +297,6 @@ export default {
355
297
  Link: NavbarLink,
356
298
  SearchTrigger: NavbarSearchTrigger,
357
299
  Theme: NavbarTheme,
358
- Item: NavbarItem,
359
300
  Socials: NavbarSocials,
360
301
  Split: NavbarSplit,
361
302
  Content: NavbarContent,
@@ -12,8 +12,8 @@ import React, {
12
12
  import scrollIntoView from 'scroll-into-view-if-needed'
13
13
  import { cn } from '../../utils/cn'
14
14
  import { useOnChange } from '../../utils/use-on-change'
15
- import type { ComponentBase, CompoundComponent } from './types'
16
- import { getItemId } from './helpers/observer'
15
+ import type { ComponentBase } from './types'
16
+ import { getItemId, Observer } from './helpers/observer'
17
17
 
18
18
  export interface TOCItemType {
19
19
  title: ReactNode
@@ -74,120 +74,6 @@ export interface OnThisPageIndicatorProps extends ComponentBase {
74
74
  const ItemsContext = createContext<TOCItemInfo[] | null>(null)
75
75
  const ScrollContext = createContext<RefObject<HTMLElement | null> | null>(null)
76
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
77
  export function useItems() {
192
78
  const ctx = use(ItemsContext)
193
79
  if (!ctx)
@@ -197,40 +83,6 @@ export function useItems() {
197
83
  return ctx
198
84
  }
199
85
 
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
86
  export function useActiveAnchor(): string | undefined {
235
87
  const items = useItems()
236
88
  return useMemo(() => {
@@ -282,9 +134,11 @@ export function AnchorProvider({
282
134
  }, [observer, toc])
283
135
 
284
136
  useEffect(() => {
137
+ // We use a rootMargin that acts as an activation "line" near the top.
138
+ // headings are "intersecting" (active=true) when they are BELOW this line.
285
139
  observer.watch({
286
- rootMargin: '0px',
287
- threshold: 0.98,
140
+ rootMargin: '-100px 0% 0% 0%',
141
+ threshold: 0,
288
142
  })
289
143
  observer.onChange = () => setItems([...observer.items])
290
144
 
@@ -339,18 +193,10 @@ export const OnThisPageContent = ({
339
193
 
340
194
  useImperativeHandle(ref, () => internalRef.current!)
341
195
 
342
- const { isOverflowing, isAtBottom } = useScrollStatus(internalRef)
343
-
344
196
  return (
345
197
  <div
346
198
  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
- )}
199
+ className={cn('relative overflow-y-auto boltdocs-otp-content', className)}
354
200
  {...props}
355
201
  >
356
202
  {children}
@@ -24,8 +24,7 @@ export const Popover = ({
24
24
  {...props}
25
25
  className={RAC.composeRenderProps(className, (className) =>
26
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',
27
+ 'z-50 overflow-auto rounded-xl border border-border-subtle bg-bg-surface/80 shadow-xl backdrop-blur-md outline-none transition-none',
29
28
  className,
30
29
  ),
31
30
  )}
@@ -50,12 +50,12 @@ export const SearchDialogRoot = ({
50
50
  )
51
51
  }
52
52
 
53
- export const SearchDialogAutocomplete = ({
53
+ export const SearchDialogAutocomplete = <T extends object>({
54
54
  children,
55
55
  className,
56
56
  onSelectionChange,
57
57
  ...props
58
- }: RAC.AutocompleteProps<object> & {
58
+ }: RAC.AutocompleteProps<T> & {
59
59
  className?: string
60
60
  onSelectionChange?: (key: RAC.Key) => void
61
61
  }) => {
@@ -98,11 +98,11 @@ export const SearchDialogInput = ({
98
98
  )
99
99
  }
100
100
 
101
- export const SearchDialogList = ({
101
+ export const SearchDialogList = <T extends object>({
102
102
  children,
103
103
  className,
104
104
  ...props
105
- }: RAC.ListBoxProps<object> & { className?: string }) => {
105
+ }: RAC.ListBoxProps<T> & { className?: string }) => {
106
106
  return (
107
107
  <RAC.ListBox
108
108
  {...props}
@@ -122,11 +122,12 @@ export const SidebarLink = ({
122
122
  <Link
123
123
  href={href}
124
124
  className={cn(
125
- 'group flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm outline-none transition-colors',
125
+ 'group flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm outline-none',
126
+ 'transition-all duration-200 ease-in-out',
126
127
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
127
128
  active
128
129
  ? 'bg-primary-500/10 text-primary-500 font-medium'
129
- : 'text-text-muted hover:bg-bg-surface hover:text-text-main',
130
+ : 'text-text-muted hover:bg-bg-surface hover:text-text-main hover:translate-x-1',
130
131
  className,
131
132
  )}
132
133
  >
@@ -0,0 +1,26 @@
1
+ import { cn } from '@client/utils/cn'
2
+
3
+ interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ variant?: 'rect' | 'circle'
5
+ }
6
+
7
+ /**
8
+ * A flexible skeleton component that mimics the shape of content
9
+ * while it is loading. Features a smooth pulse animation.
10
+ */
11
+ export function Skeleton({
12
+ className,
13
+ variant = 'rect',
14
+ ...props
15
+ }: SkeletonProps) {
16
+ return (
17
+ <div
18
+ className={cn(
19
+ 'animate-pulse bg-bg-muted',
20
+ variant === 'circle' ? 'rounded-full' : 'rounded-md',
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
@@ -80,25 +80,19 @@ export function CopyMarkdown({ content, mdxRaw, config }: CopyMarkdownProps) {
80
80
  )}
81
81
  />
82
82
  <Menu className="w-52">
83
- <MenuItem
84
- onAction={handleCopy}
85
- className="flex flex-row items-start gap-2.5 group"
86
- >
83
+ <MenuItem onAction={handleCopy}>
87
84
  <Copy
88
85
  size={16}
89
- className="w-4 h-4 shrink-0 mt-0.5 transition-transform duration-200 group-hover:-translate-y-0.5 text-text-muted group-hover:text-primary-500"
86
+ className="size-4 mt-0.5 text-text-muted group-hover:text-primary-500"
90
87
  />
91
88
  <span className="font-medium text-[0.8125rem]">
92
89
  Copy Markdown
93
90
  </span>
94
91
  </MenuItem>
95
- <MenuItem
96
- onAction={handleOpenRaw}
97
- className="flex flex-row items-start gap-2.5 group"
98
- >
92
+ <MenuItem onAction={handleOpenRaw}>
99
93
  <ExternalLink
100
94
  size={16}
101
- className="w-4 h-4 shrink-0 mt-0.5 transition-transform duration-200 group-hover:-translate-y-0.5 text-text-muted group-hover:text-primary-500"
95
+ className="size-4 mt-0.5 text-text-muted group-hover:text-primary-500"
102
96
  />
103
97
  <span className="font-medium text-[0.8125rem]">
104
98
  View as Markdown
@@ -10,7 +10,6 @@ export { NotFound } from './not-found'
10
10
  export { OnThisPage } from './on-this-page'
11
11
  export { PageNav } from './page-nav'
12
12
  export { PoweredBy } from './powered-by'
13
- export { ProgressBar } from './progress-bar'
14
13
  export { SearchDialog } from './search-dialog'
15
14
  export { Sidebar } from './sidebar'
16
15
  export { Tabs } from './tabs'