boltdocs 2.6.2 → 2.7.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 (177) hide show
  1. package/bin/boltdocs.js +0 -1
  2. package/dist/cache-CQKlT4fI.mjs +6 -0
  3. package/dist/cache-DorPMFgW.cjs +6 -0
  4. package/dist/cards-BLoSiRuL.d.ts +30 -0
  5. package/dist/cards-CQn9mXZS.d.cts +30 -0
  6. package/dist/chunk-Ds5LZdWN.cjs +6 -0
  7. package/dist/client/index.cjs +1 -1
  8. package/dist/client/index.d.cts +167 -1338
  9. package/dist/client/index.d.ts +166 -1337
  10. package/dist/client/index.js +1 -1
  11. package/dist/{package-CFP44vfn.cjs → client/mdx.cjs} +1 -1
  12. package/dist/client/mdx.d.cts +128 -0
  13. package/dist/client/mdx.d.ts +129 -0
  14. package/dist/client/mdx.js +6 -0
  15. package/dist/client/primitives.cjs +6 -0
  16. package/dist/client/primitives.d.cts +818 -0
  17. package/dist/client/primitives.d.ts +818 -0
  18. package/dist/client/primitives.js +6 -0
  19. package/dist/client/theme/neutral.css +74 -361
  20. package/dist/client/theme/reset.css +189 -0
  21. package/dist/docs-layout-BlDhcQRv.cjs +6 -0
  22. package/dist/docs-layout-BvAOWEJw.js +6 -0
  23. package/dist/doctor-BQiQhCTl.cjs +6 -0
  24. package/dist/doctor-COpf35L2.cjs +20 -0
  25. package/dist/doctor-Dh1XP7Pz.mjs +20 -0
  26. package/dist/generator-DGW6pkCC.cjs +22 -0
  27. package/dist/generator-Dv3wEmhZ.mjs +22 -0
  28. package/dist/icons-dev-CrQLjoQp.js +6 -0
  29. package/dist/icons-dev-rzdz6Lf3.cjs +6 -0
  30. package/dist/image-BkIfa9oo.js +6 -0
  31. package/dist/image-DIGjCPe6.cjs +6 -0
  32. package/dist/mdx-K0WYBAJ3.js +7 -0
  33. package/dist/mdx-hpErbRUe.cjs +7 -0
  34. package/dist/meta-loader-0gJ4PtBC.cjs +6 -0
  35. package/dist/meta-loader-9IpAHWDS.mjs +6 -0
  36. package/dist/node/cli-entry.cjs +1 -2
  37. package/dist/node/cli-entry.mjs +1 -2
  38. package/dist/node/index.cjs +1 -1
  39. package/dist/node/index.d.cts +55 -11
  40. package/dist/node/index.d.mts +55 -12
  41. package/dist/node/index.mjs +1 -1
  42. package/dist/node/routes/worker.cjs +6 -0
  43. package/dist/node/routes/worker.d.cts +2 -0
  44. package/dist/node/routes/worker.d.mts +2 -0
  45. package/dist/node/routes/worker.mjs +6 -0
  46. package/dist/node-C2nWXElP.mjs +112 -0
  47. package/dist/node-CinkUtxV.cjs +112 -0
  48. package/dist/package-BMYLDBBP.cjs +6 -0
  49. package/dist/{package-Bqbn1AYK.mjs → package-HegMOTL_.mjs} +1 -1
  50. package/dist/parser-Bh11BsdA.cjs +6 -0
  51. package/dist/parser-D8eQvE7N.mjs +6 -0
  52. package/dist/parser-DYRzXWmA.cjs +6 -0
  53. package/dist/routes-CHf76Ye4.cjs +6 -0
  54. package/dist/routes-CMUZGI6T.mjs +6 -0
  55. package/dist/routes-Co1mRM58.cjs +6 -0
  56. package/dist/search-dialog-BACuzoVX.cjs +6 -0
  57. package/dist/search-dialog-BKagVT17.js +6 -0
  58. package/dist/search-dialog-C8w12eUx.js +6 -0
  59. package/dist/search-dialog-CGyrozZE.cjs +6 -0
  60. package/dist/search-dialog-D26rUnJ_.cjs +6 -0
  61. package/dist/sidebar-DKvg6KOc.d.cts +491 -0
  62. package/dist/sidebar-Dr1TiRIy.d.ts +491 -0
  63. package/dist/utils-BxNAXhZZ.mjs +7 -0
  64. package/dist/utils-Clzu7jvb.cjs +7 -0
  65. package/dist/worker-pool-Bd8Y9KDv.mjs +6 -0
  66. package/dist/worker-pool-BwU8ckrg.cjs +6 -0
  67. package/package.json +27 -8
  68. package/src/client/app/doc-page.tsx +9 -5
  69. package/src/client/app/docs-layout.tsx +17 -3
  70. package/src/client/app/head.tsx +122 -0
  71. package/src/client/app/helmet-compat.tsx +36 -0
  72. package/src/client/app/mdx-component.tsx +5 -52
  73. package/src/client/app/mdx-components-context.tsx +32 -8
  74. package/src/client/app/routes-context.tsx +2 -2
  75. package/src/client/app/scroll-handler.tsx +1 -1
  76. package/src/client/app/theme-context.tsx +5 -5
  77. package/src/client/app/ui-context.tsx +42 -0
  78. package/src/client/components/docs-layout-default.tsx +85 -0
  79. package/src/client/components/icons-dev.tsx +38 -15
  80. package/src/client/components/mdx/callout.tsx +97 -0
  81. package/src/client/components/mdx/card.tsx +73 -98
  82. package/src/client/components/mdx/cards.tsx +27 -0
  83. package/src/client/components/mdx/code-block.tsx +37 -17
  84. package/src/client/components/mdx/field.tsx +24 -56
  85. package/src/client/components/mdx/image.tsx +36 -15
  86. package/src/client/components/mdx/index.ts +19 -53
  87. package/src/client/components/mdx/table.tsx +46 -148
  88. package/src/client/components/mdx/typographics.tsx +120 -0
  89. package/src/client/components/mdx/{hooks/use-code-block.ts → use-code-block.ts} +5 -7
  90. package/src/client/components/primitives/breadcrumbs.tsx +5 -24
  91. package/src/client/components/primitives/button.tsx +3 -142
  92. package/src/client/components/primitives/code-block.tsx +104 -97
  93. package/src/client/components/{docs-layout.tsx → primitives/docs-layout.tsx} +15 -24
  94. package/src/client/components/primitives/error-boundary.tsx +107 -0
  95. package/src/client/components/primitives/heading.tsx +128 -0
  96. package/src/client/components/primitives/helpers/observer.ts +62 -32
  97. package/src/client/components/primitives/image.tsx +26 -0
  98. package/src/client/components/primitives/link.tsx +50 -52
  99. package/src/client/components/primitives/menu.tsx +25 -49
  100. package/src/client/components/primitives/navbar.tsx +234 -59
  101. package/src/client/components/primitives/on-this-page.tsx +169 -40
  102. package/src/client/components/primitives/page-nav.tsx +11 -39
  103. package/src/client/components/primitives/popover.tsx +12 -30
  104. package/src/client/components/primitives/search-dialog.tsx +77 -71
  105. package/src/client/components/primitives/sidebar.tsx +312 -119
  106. package/src/client/components/primitives/skeleton.tsx +1 -1
  107. package/src/client/components/primitives/tabs.tsx +5 -16
  108. package/src/client/components/primitives/tooltip.tsx +1 -1
  109. package/src/client/components/ui-base/banner.tsx +66 -0
  110. package/src/client/components/ui-base/breadcrumbs.tsx +26 -20
  111. package/src/client/components/ui-base/copy-markdown.tsx +43 -35
  112. package/src/client/components/ui-base/error-boundary.tsx +9 -46
  113. package/src/client/components/ui-base/github-stars.tsx +5 -3
  114. package/src/client/components/ui-base/index.ts +3 -3
  115. package/src/client/components/ui-base/last-updated.tsx +27 -0
  116. package/src/client/components/ui-base/navbar.tsx +183 -89
  117. package/src/client/components/ui-base/not-found.tsx +11 -9
  118. package/src/client/components/ui-base/on-this-page.tsx +8 -104
  119. package/src/client/components/ui-base/page-nav.tsx +23 -9
  120. package/src/client/components/ui-base/search-dialog.tsx +111 -36
  121. package/src/client/components/ui-base/search-highlight.tsx +10 -0
  122. package/src/client/components/ui-base/sidebar.tsx +77 -154
  123. package/src/client/components/ui-base/tabs.tsx +20 -7
  124. package/src/client/components/ui-base/theme-toggle.tsx +88 -10
  125. package/src/client/components/ui-base/version-i18n.tsx +80 -0
  126. package/src/client/hooks/index.ts +2 -1
  127. package/src/client/hooks/use-analytics.ts +272 -0
  128. package/src/client/hooks/use-i18n.ts +116 -50
  129. package/src/client/hooks/use-localized-to.ts +70 -27
  130. package/src/client/hooks/use-navbar.ts +69 -39
  131. package/src/client/hooks/use-page-nav.ts +28 -25
  132. package/src/client/hooks/use-routes.ts +63 -80
  133. package/src/client/hooks/use-search-highlight.ts +185 -0
  134. package/src/client/hooks/use-search.ts +12 -3
  135. package/src/client/hooks/use-sidebar.ts +183 -80
  136. package/src/client/hooks/use-tabs.ts +3 -4
  137. package/src/client/hooks/use-version.ts +44 -29
  138. package/src/client/index.ts +13 -87
  139. package/src/client/mdx.ts +2 -0
  140. package/src/client/primitives.ts +19 -0
  141. package/src/client/ssg/boltdocs-shell.tsx +68 -79
  142. package/src/client/ssg/create-routes.tsx +268 -72
  143. package/src/client/ssg/mdx-page.tsx +2 -1
  144. package/src/client/store/boltdocs-context.tsx +72 -20
  145. package/src/client/theme/neutral.css +74 -361
  146. package/src/client/theme/reset.css +189 -0
  147. package/src/client/types.ts +10 -2
  148. package/src/client/utils/path.ts +9 -0
  149. package/src/client/utils/react-to-text.ts +24 -24
  150. package/src/client/virtual.d.ts +1 -1
  151. package/src/shared/types.ts +82 -22
  152. package/dist/node-Bogvkxao.mjs +0 -101
  153. package/dist/node-CXaog6St.cjs +0 -101
  154. package/dist/search-dialog-CV3eJzMm.cjs +0 -6
  155. package/dist/search-dialog-DNTomKgu.js +0 -6
  156. package/dist/use-search-CS3gH19M.js +0 -6
  157. package/dist/use-search-DBpJZQuw.cjs +0 -6
  158. package/src/client/components/mdx/admonition.tsx +0 -91
  159. package/src/client/components/mdx/badge.tsx +0 -41
  160. package/src/client/components/mdx/button.tsx +0 -35
  161. package/src/client/components/mdx/component-preview.tsx +0 -37
  162. package/src/client/components/mdx/component-props.tsx +0 -83
  163. package/src/client/components/mdx/file-tree.tsx +0 -325
  164. package/src/client/components/mdx/hooks/use-component-preview.ts +0 -16
  165. package/src/client/components/mdx/hooks/useTable.ts +0 -74
  166. package/src/client/components/mdx/hooks/useTabs.ts +0 -68
  167. package/src/client/components/mdx/link.tsx +0 -38
  168. package/src/client/components/mdx/list.tsx +0 -192
  169. package/src/client/components/mdx/tabs.tsx +0 -135
  170. package/src/client/components/mdx/video.tsx +0 -68
  171. package/src/client/components/primitives/index.ts +0 -19
  172. package/src/client/components/primitives/navigation-menu.tsx +0 -114
  173. package/src/client/components/ui-base/head.tsx +0 -83
  174. package/src/client/components/ui-base/loading.tsx +0 -57
  175. package/src/client/components/ui-base/powered-by.tsx +0 -25
  176. package/src/client/hooks/use-onthispage.ts +0 -23
  177. package/src/client/utils/use-on-change.ts +0 -15
@@ -1,113 +1,120 @@
1
- import type { ComponentProps } from "react";
2
- import { cn } from "../../utils/cn";
1
+ import type { ComponentProps } from 'react'
2
+ import { cn } from '../../utils/cn'
3
3
 
4
- interface CodeBlockRootProps extends ComponentProps<"div"> {
5
- /**
6
- * Whether the code block is in plain mode (no borders/padding)
7
- * @default false
8
- */
9
- plain?: boolean;
4
+ interface CodeBlockRootProps extends ComponentProps<'div'> {
5
+ /**
6
+ * Whether the code block is in plain mode (no borders/padding)
7
+ * @default false
8
+ */
9
+ plain?: boolean
10
10
  }
11
11
 
12
+ export interface CodeBlockHeaderProps extends ComponentProps<'div'> {}
13
+ export interface CodeBlockGroupProps extends ComponentProps<'div'> {}
14
+ export interface CodeBlockContentProps extends ComponentProps<'div'> {
15
+ /**
16
+ * Whether the code content should be truncated with an expand button
17
+ * @default false
18
+ */
19
+ shouldTruncate?: boolean
20
+ }
21
+
22
+ /**
23
+ * Root component for code blocks.
24
+ * Handles background, borders, and general layout.
25
+ */
12
26
  const CodeBlock = ({
13
- children,
14
- className,
15
- plain = false,
16
- ...props
27
+ children,
28
+ className,
29
+ plain = false,
30
+ ...props
17
31
  }: CodeBlockRootProps) => {
18
- return (
19
- <div
20
- className={cn(
21
- "not-prose boltdocs-code-block",
22
- 'group relative overflow-hidden bg-(--color-code-bg)',
23
- 'contain-layout contain-paint',
24
- {
25
- 'my-6 rounded-lg border border-border-subtle': !plain,
26
- },
27
- className,
28
- )}
29
- {...props}
30
- >
31
- {children}
32
- </div>
33
- );
34
- };
35
-
36
- type CodeBlockHeaderProps = ComponentProps<"div">;
32
+ return (
33
+ <div
34
+ className={cn(
35
+ 'not-prose boltdocs-code-block',
36
+ 'group relative overflow-hidden bg-(--color-code-bg)',
37
+ 'contain-layout contain-paint',
38
+ {
39
+ 'my-6 rounded-xl border border-subtle': !plain,
40
+ },
41
+ className,
42
+ )}
43
+ {...props}
44
+ >
45
+ {children}
46
+ </div>
47
+ )
48
+ }
37
49
 
50
+ /**
51
+ * Header section of the code block.
52
+ * Usually contains the title, language label, and action buttons.
53
+ */
38
54
  const CodeBlockHeader = ({
39
- children,
40
- className,
41
- ...props
55
+ children,
56
+ className,
57
+ ...props
42
58
  }: CodeBlockHeaderProps) => {
43
- return (
44
- <div
45
- className={cn(
46
- "flex h-9 items-center justify-between px-4 py-1.5",
47
- "border-b border-border-subtle bg-bg-surface/50",
48
- "text-[13px] font-medium text-text-muted",
49
- className,
50
- )}
51
- {...props}
52
- >
53
- {children}
54
- </div>
55
- );
56
- };
57
-
58
- type CodeBlockGroupProps = ComponentProps<"div">;
59
+ return (
60
+ <div
61
+ className={cn(
62
+ 'flex h-9 items-center justify-between px-4 py-1.5',
63
+ 'text-[13px] font-medium text-muted',
64
+ className,
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ </div>
70
+ )
71
+ }
59
72
 
73
+ /**
74
+ * Horizontal group for organizing items within the header (e.g., logo + label).
75
+ */
60
76
  const CodeBlockGroup = ({
61
- children,
62
- className,
63
- ...props
77
+ children,
78
+ className,
79
+ ...props
64
80
  }: CodeBlockGroupProps) => {
65
- return (
66
- <div
67
- className={cn(
68
- "flex items-center space-x-2",
69
- className,
70
- )}
71
- {...props}
72
- >
73
- {children}
74
- </div>
75
- );
76
- };
77
-
78
- interface CodeBlockContentProps extends ComponentProps<"div"> {
79
- /**
80
- * Whether the code should be truncated with an expand button
81
- * @default false
82
- */
83
- shouldTruncate?: boolean;
81
+ return (
82
+ <div className={cn('flex items-center space-x-2', className)} {...props}>
83
+ {children}
84
+ </div>
85
+ )
84
86
  }
85
87
 
88
+ /**
89
+ * Content area of the code block.
90
+ * Wraps the `<pre>` or `<div>` containing the code.
91
+ */
86
92
  const CodeBlockContent = ({
87
- className,
88
- children,
89
- shouldTruncate = false,
90
- ...props
93
+ className,
94
+ children,
95
+ shouldTruncate = false,
96
+ ...props
91
97
  }: CodeBlockContentProps) => {
92
- return (
93
- <div
94
- className={cn(
95
- "relative",
96
- {
97
- '[&>pre]:max-h-62.5 [&>pre]:overflow-hidden': shouldTruncate,
98
- },
99
- className,
100
- )}
101
- {...props}
102
- >
103
- {children}
104
- </div>
105
- );
106
- };
98
+ return (
99
+ <div
100
+ className={cn(
101
+ 'relative',
102
+ {
103
+ '[&>pre]:max-h-[300px] [&>pre]:overflow-hidden [&>div>pre]:max-h-[300px] [&>div>pre]:overflow-hidden':
104
+ shouldTruncate,
105
+ },
106
+ className,
107
+ )}
108
+ {...props}
109
+ >
110
+ {children}
111
+ </div>
112
+ )
113
+ }
114
+
115
+ // Assign sub-components
116
+ CodeBlock.Header = CodeBlockHeader
117
+ CodeBlock.Group = CodeBlockGroup
118
+ CodeBlock.Content = CodeBlockContent
107
119
 
108
- export {
109
- CodeBlock,
110
- CodeBlockHeader,
111
- CodeBlockGroup,
112
- CodeBlockContent,
113
- };
120
+ export { CodeBlock, CodeBlockHeader, CodeBlockGroup, CodeBlockContent }
@@ -1,5 +1,5 @@
1
- import { cn } from '../utils/cn'
2
- import { useLocation } from '../hooks'
1
+ import { cn } from '../../utils/cn'
2
+ import { SearchHighlight } from '../ui-base/search-highlight'
3
3
 
4
4
  /**
5
5
  * Props shared by all layout slot components.
@@ -25,7 +25,7 @@ function DocsLayoutRoot({ children, className, style }: SlotProps) {
25
25
  return (
26
26
  <div
27
27
  className={cn(
28
- 'h-screen flex flex-col overflow-hidden bg-bg-main text-text-main',
28
+ 'h-screen flex flex-col overflow-hidden bg-main text-body',
29
29
  className,
30
30
  )}
31
31
  style={style}
@@ -42,7 +42,7 @@ function Body({ children, className, style }: SlotProps) {
42
42
  return (
43
43
  <div
44
44
  className={cn(
45
- 'mx-auto flex flex-1 w-full max-w-(--breakpoint-3xl) bg-bg-main overflow-hidden',
45
+ 'mx-auto flex flex-1 w-full max-w-(--breakpoint-3xl) bg-main overflow-hidden',
46
46
  className,
47
47
  )}
48
48
  style={style}
@@ -74,41 +74,32 @@ function Content({ children, className, style }: SlotProps) {
74
74
  * MDX Content wrapper with standard page padding and max-width logic.
75
75
  */
76
76
  function ContentMdx({ children, className, style }: SlotProps) {
77
- const { pathname } = useLocation()
78
77
  return (
79
78
  <div
80
- className={cn(
81
- 'boltdocs-page mx-auto pt-4 pb-20 px-4 sm:px-8',
82
- {
83
- 'max-w-content-max': pathname.includes('/docs/'),
84
- },
85
- className,
86
- )}
79
+ className={cn('boltdocs-page mx-auto pt-4 pb-20 px-4 sm:px-8', className)}
87
80
  style={style}
88
81
  >
82
+ <SearchHighlight />
89
83
  {children}
90
84
  </div>
91
85
  )
92
86
  }
93
87
 
94
88
  /**
95
- * Content header row (breadcrumbs + copy markdown).
89
+ * Content header area (breadcrumbs, title, description, etc).
96
90
  */
97
- function ContentHeader({ children, className, style }: SlotProps) {
91
+ function Header({ children, className, style }: SlotProps) {
98
92
  return (
99
- <div
100
- className={cn('flex items-center justify-between mb-10', className)}
101
- style={style}
102
- >
93
+ <header className={cn('mb-10', className)} style={style}>
103
94
  {children}
104
- </div>
95
+ </header>
105
96
  )
106
97
  }
107
98
 
108
99
  /**
109
100
  * Footer area inside the content section (page nav).
110
101
  */
111
- function ContentFooter({ children, className, style }: SlotProps) {
102
+ function Footer({ children, className, style }: SlotProps) {
112
103
  return (
113
104
  <div className={cn('mt-20', className)} style={style}>
114
105
  {children}
@@ -120,8 +111,8 @@ interface DocsLayoutComponent extends React.FC<SlotProps> {
120
111
  Body: typeof Body
121
112
  Content: typeof Content
122
113
  ContentMdx: typeof ContentMdx
123
- ContentHeader: typeof ContentHeader
124
- ContentFooter: typeof ContentFooter
114
+ Header: typeof Header
115
+ Footer: typeof Footer
125
116
  }
126
117
 
127
118
  // Attach sub-components to the root
@@ -129,6 +120,6 @@ export const DocsLayout = Object.assign(DocsLayoutRoot, {
129
120
  Body,
130
121
  Content,
131
122
  ContentMdx,
132
- ContentHeader,
133
- ContentFooter,
123
+ Header,
124
+ Footer,
134
125
  }) as DocsLayoutComponent
@@ -0,0 +1,107 @@
1
+ import * as React from 'react'
2
+ import type { ErrorInfo, ComponentType, ReactNode } from 'react'
3
+ import { Component } from 'react'
4
+ import { Button } from './button'
5
+
6
+ export interface FallbackProps {
7
+ error: Error
8
+ resetErrorBoundary: () => void
9
+ }
10
+
11
+ export interface ErrorBoundaryProps {
12
+ children?: ReactNode
13
+ fallback?: ReactNode
14
+ FallbackComponent?: ComponentType<FallbackProps>
15
+ onError?: (error: Error, info: ErrorInfo) => void
16
+ onReset?: () => void
17
+ }
18
+
19
+ interface ErrorBoundaryState {
20
+ hasError: boolean
21
+ error: Error | null
22
+ }
23
+
24
+ export class ErrorBoundary extends Component<
25
+ ErrorBoundaryProps,
26
+ ErrorBoundaryState
27
+ > {
28
+ public state: ErrorBoundaryState = { hasError: false, error: null }
29
+
30
+ public static getDerivedStateFromError(error: Error): ErrorBoundaryState {
31
+ return { hasError: true, error }
32
+ }
33
+
34
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
35
+ if (this.props.onError) {
36
+ this.props.onError(error, errorInfo)
37
+ } else {
38
+ console.error(
39
+ 'ErrorBoundary caught an unhandled error:',
40
+ error,
41
+ errorInfo,
42
+ )
43
+ }
44
+ }
45
+
46
+ public resetErrorBoundary = () => {
47
+ if (this.props.onReset) {
48
+ this.props.onReset()
49
+ }
50
+ this.setState({ hasError: false, error: null })
51
+ }
52
+
53
+ public render() {
54
+ const { hasError, error } = this.state
55
+ const { children, fallback, FallbackComponent } = this.props
56
+
57
+ if (hasError && error) {
58
+ if (FallbackComponent) {
59
+ return (
60
+ <FallbackComponent
61
+ error={error}
62
+ resetErrorBoundary={this.resetErrorBoundary}
63
+ />
64
+ )
65
+ }
66
+ if (fallback) {
67
+ return fallback
68
+ }
69
+ return (
70
+ <ErrorBoundaryFallback
71
+ error={error}
72
+ resetErrorBoundary={this.resetErrorBoundary}
73
+ />
74
+ )
75
+ }
76
+
77
+ return children
78
+ }
79
+ }
80
+
81
+ export interface ErrorBoundaryFallbackProps {
82
+ error: Error
83
+ resetErrorBoundary: () => void
84
+ }
85
+
86
+ export function ErrorBoundaryFallback({
87
+ error,
88
+ resetErrorBoundary,
89
+ }: ErrorBoundaryFallbackProps) {
90
+ return (
91
+ <div className="flex flex-col items-center justify-center min-h-[40vh] text-center gap-4 px-6 py-8 border border-subtle bg-surface rounded-2xl max-w-lg mx-auto shadow-xs">
92
+ <div className="text-lg font-bold text-rose-600 dark:text-rose-400">
93
+ Something went wrong
94
+ </div>
95
+ <p className="text-sm text-muted max-w-sm leading-relaxed">
96
+ {error?.message ||
97
+ 'An unexpected error occurred while rendering this page.'}
98
+ </p>
99
+ <Button
100
+ className="rounded-xl border border-subtle bg-main px-6 py-2.5 text-xs font-semibold text-body hover:bg-primary-50/50 hover:border-primary-500/50 transition-all duration-300 cursor-pointer outline-none select-none"
101
+ onPress={resetErrorBoundary}
102
+ >
103
+ Try again
104
+ </Button>
105
+ </div>
106
+ )
107
+ }
@@ -0,0 +1,128 @@
1
+ import * as React from 'react'
2
+ import { Link as LucideLink } from 'lucide-react'
3
+ import { Link } from './link'
4
+ import { cn } from '../../utils/cn'
5
+
6
+ export interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
7
+ level: 1 | 2 | 3 | 4 | 5 | 6
8
+ id?: string
9
+ /** Whether to show the anchor icon/link. Defaults to true if id is provided. */
10
+ showAnchor?: boolean
11
+ /** Position of the anchor link relative to children. Defaults to 'wrap'. */
12
+ anchorPosition?: 'wrap' | 'after' | 'before'
13
+ /** Custom icon to display for the anchor. */
14
+ anchorIcon?: React.ReactNode
15
+ /** Custom classes for the anchor link wrapper. */
16
+ anchorClassName?: string
17
+ }
18
+
19
+ export const Heading = React.forwardRef<HTMLHeadingElement, HeadingProps>(
20
+ (
21
+ {
22
+ level,
23
+ id,
24
+ children,
25
+ className,
26
+ showAnchor = true,
27
+ anchorPosition = 'wrap',
28
+ anchorIcon,
29
+ anchorClassName,
30
+ ...props
31
+ },
32
+ ref,
33
+ ) => {
34
+ const safeLevel = Math.min(Math.max(Math.floor(level), 1), 6) as
35
+ | 1
36
+ | 2
37
+ | 3
38
+ | 4
39
+ | 5
40
+ | 6
41
+ const Tag = `h${safeLevel}` as const
42
+
43
+ const hasAnchor = !!(id && showAnchor)
44
+
45
+ // Default icon with hover transition
46
+ const defaultIcon = (
47
+ <LucideLink
48
+ className={cn(
49
+ 'transition-all duration-200',
50
+ anchorPosition === 'wrap'
51
+ ? 'opacity-0 ml-2 text-muted/50 group-hover:text-primary-500 group-hover:opacity-100'
52
+ : 'text-muted/50 hover:text-primary-500',
53
+ )}
54
+ size={16}
55
+ />
56
+ )
57
+
58
+ const icon = anchorIcon ?? defaultIcon
59
+
60
+ const renderContent = () => {
61
+ if (!hasAnchor) {
62
+ return children
63
+ }
64
+
65
+ if (anchorPosition === 'wrap') {
66
+ return (
67
+ <Link
68
+ href={`#${id}`}
69
+ className={cn(
70
+ 'header-anchor flex flex-row items-center no-underline text-inherit',
71
+ anchorClassName,
72
+ )}
73
+ aria-label="Anchor"
74
+ >
75
+ {children}
76
+ {icon}
77
+ </Link>
78
+ )
79
+ }
80
+
81
+ return (
82
+ <>
83
+ {anchorPosition === 'before' && (
84
+ <Link
85
+ href={`#${id}`}
86
+ className={cn(
87
+ 'header-anchor mr-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200',
88
+ anchorClassName,
89
+ )}
90
+ aria-label="Anchor"
91
+ >
92
+ {icon}
93
+ </Link>
94
+ )}
95
+ {children}
96
+ {anchorPosition === 'after' && (
97
+ <Link
98
+ href={`#${id}`}
99
+ className={cn(
100
+ 'header-anchor ml-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200',
101
+ anchorClassName,
102
+ )}
103
+ aria-label="Anchor"
104
+ >
105
+ {icon}
106
+ </Link>
107
+ )}
108
+ </>
109
+ )
110
+ }
111
+
112
+ return (
113
+ <Tag
114
+ ref={ref}
115
+ id={id}
116
+ className={cn(
117
+ 'boltdocs-heading relative group flex items-center scroll-mt-24',
118
+ className,
119
+ )}
120
+ {...props}
121
+ >
122
+ {renderContent()}
123
+ </Tag>
124
+ )
125
+ },
126
+ )
127
+
128
+ Heading.displayName = 'Heading'
@@ -11,45 +11,75 @@ export class Observer {
11
11
  private observer: IntersectionObserver | null = null
12
12
  onChange?: () => void
13
13
 
14
- private callback(entries: IntersectionObserverEntry[]) {
15
- if (entries.length === 0) return
16
-
17
- // 1. Update internal state based on current intersection and position
18
- for (const entry of entries) {
19
- const item = this.items.find((i) => i.id === entry.target.id)
20
- if (item) {
21
- // item.active will track if the heading is currently "on or below" the trigger line
22
- item.active = entry.isIntersecting
23
-
24
- // item.fallback will track if the heading has scrolled "above" the trigger line
25
- // RootMargin top is -100px, so trigger line is at 100px.
26
- const activationLine = 100
27
- item.fallback =
28
- !entry.isIntersecting && entry.boundingClientRect.top < activationLine
14
+ private callback(_entries: IntersectionObserverEntry[]) {
15
+ // For each item, check if it's currently in viewport
16
+ for (const item of this.items) {
17
+ const element = document.getElementById(item.id)
18
+ if (!element) {
19
+ item.active = false
20
+ item.fallback = false
21
+ continue
29
22
  }
23
+
24
+ const rect = element.getBoundingClientRect()
25
+ const viewportHeight =
26
+ typeof window !== 'undefined' ? window.innerHeight : 1000
27
+
28
+ // Check if element is currently in viewport (visible)
29
+ // rect.bottom > 0
30
+ // rect.top < viewportHeight:
31
+ const isInViewport = rect.bottom > 0 && rect.top < viewportHeight
32
+
33
+ // Update active state based on current position
34
+ item.active = isInViewport
35
+
36
+ // Fallback: element has scrolled past but is still near viewport
37
+ item.fallback =
38
+ !isInViewport && rect.top > 0 && rect.top < viewportHeight * 2
30
39
  }
31
40
 
32
- // 2. The active heading is the LAST one in document order that has scrolled past the line.
33
- let highlightIdx = -1
34
- for (let i = this.items.length - 1; i >= 0; i--) {
35
- if (this.items[i].fallback) {
36
- highlightIdx = i
37
- break
41
+ // 3. Determine which items should be active based on single mode
42
+ if (this.single) {
43
+ // Single mode: only ONE active item (the one closest to the top of viewport)
44
+ let highlightIdx = -1
45
+
46
+ // Find all visible items and pick the one closest to the top of viewport
47
+ const visibleItems = this.items
48
+ .map((item, idx) => ({ item, idx }))
49
+ .filter(({ item }) => item.active)
50
+
51
+ if (visibleItems.length > 0) {
52
+ // Take the first one (closest to top of viewport)
53
+ highlightIdx = visibleItems[0].idx
54
+ } else {
55
+ // If nothing visible, check fallback items
56
+ const fallbackItems = this.items
57
+ .map((item, idx) => ({ item, idx }))
58
+ .filter(({ item }) => item.fallback)
59
+
60
+ if (fallbackItems.length > 0) {
61
+ highlightIdx = fallbackItems[0].idx
62
+ } else if (this.items.length > 0) {
63
+ highlightIdx = 0
64
+ }
38
65
  }
39
- }
40
66
 
41
- // 3. Initial state: If no headings have passed the line yet, default to the first heading.
42
- if (highlightIdx === -1 && this.items.length > 0) {
43
- highlightIdx = 0
67
+ // Map back to UI state - only one active
68
+ this.items = this.items.map((item, idx) => ({
69
+ ...item,
70
+ active: idx === highlightIdx,
71
+ t: idx === highlightIdx ? Date.now() : item.t,
72
+ }))
73
+ } else {
74
+ // Multi mode: items active when they are in viewport
75
+ // This ensures items activate when visible and deactivate when they leave viewport
76
+ this.items = this.items.map((item, idx) => ({
77
+ ...item,
78
+ active: item.active,
79
+ t: item.active ? Date.now() : item.t,
80
+ }))
44
81
  }
45
82
 
46
- // 4. Map back to UI state
47
- this.items = this.items.map((item, idx) => ({
48
- ...item,
49
- active: idx === highlightIdx,
50
- t: idx === highlightIdx ? Date.now() : item.t,
51
- }))
52
-
53
83
  this.onChange?.()
54
84
  }
55
85
 
@@ -0,0 +1,26 @@
1
+ import type { ImgHTMLAttributes } from 'react'
2
+ import { useTheme } from '../../app/theme-context'
3
+ import { cn } from '../../utils/cn'
4
+
5
+ export interface ImageProps extends ImgHTMLAttributes<HTMLImageElement> {
6
+ theme?: 'light' | 'dark'
7
+ }
8
+
9
+ /**
10
+ * A responsive image component that automatically supports dark and light theme variations
11
+ * via the `theme` prop.
12
+ */
13
+ export function Image({ theme, className, ...props }: ImageProps) {
14
+ const { resolvedTheme } = useTheme()
15
+
16
+ if (theme && theme !== resolvedTheme) {
17
+ return null
18
+ }
19
+
20
+ return (
21
+ <img
22
+ className={cn('max-w-full h-auto rounded-lg my-8', className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }