boltdocs 2.6.1 → 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.
- package/bin/boltdocs.js +0 -1
- package/dist/cache-CQKlT4fI.mjs +6 -0
- package/dist/cache-DorPMFgW.cjs +6 -0
- package/dist/cards-BLoSiRuL.d.ts +30 -0
- package/dist/cards-CQn9mXZS.d.cts +30 -0
- package/dist/chunk-Ds5LZdWN.cjs +6 -0
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +173 -1328
- package/dist/client/index.d.ts +172 -1327
- package/dist/client/index.js +1 -1
- package/dist/{package-c99Cs7mD.cjs → client/mdx.cjs} +1 -1
- package/dist/client/mdx.d.cts +128 -0
- package/dist/client/mdx.d.ts +129 -0
- package/dist/client/mdx.js +6 -0
- package/dist/client/primitives.cjs +6 -0
- package/dist/client/primitives.d.cts +818 -0
- package/dist/client/primitives.d.ts +818 -0
- package/dist/client/primitives.js +6 -0
- package/dist/client/theme/neutral.css +74 -361
- package/dist/client/theme/reset.css +189 -0
- package/dist/docs-layout-BlDhcQRv.cjs +6 -0
- package/dist/docs-layout-BvAOWEJw.js +6 -0
- package/dist/doctor-BQiQhCTl.cjs +6 -0
- package/dist/doctor-COpf35L2.cjs +20 -0
- package/dist/doctor-Dh1XP7Pz.mjs +20 -0
- package/dist/generator-DGW6pkCC.cjs +22 -0
- package/dist/generator-Dv3wEmhZ.mjs +22 -0
- package/dist/icons-dev-CrQLjoQp.js +6 -0
- package/dist/icons-dev-rzdz6Lf3.cjs +6 -0
- package/dist/image-BkIfa9oo.js +6 -0
- package/dist/image-DIGjCPe6.cjs +6 -0
- package/dist/mdx-K0WYBAJ3.js +7 -0
- package/dist/mdx-hpErbRUe.cjs +7 -0
- package/dist/meta-loader-0gJ4PtBC.cjs +6 -0
- package/dist/meta-loader-9IpAHWDS.mjs +6 -0
- package/dist/node/cli-entry.cjs +1 -2
- package/dist/node/cli-entry.mjs +1 -2
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +66 -13
- package/dist/node/index.d.mts +66 -14
- package/dist/node/index.mjs +1 -1
- package/dist/node/routes/worker.cjs +6 -0
- package/dist/node/routes/worker.d.cts +2 -0
- package/dist/node/routes/worker.d.mts +2 -0
- package/dist/node/routes/worker.mjs +6 -0
- package/dist/node-C2nWXElP.mjs +112 -0
- package/dist/node-CinkUtxV.cjs +112 -0
- package/dist/package-BMYLDBBP.cjs +6 -0
- package/dist/{package-DukYeKmD.mjs → package-HegMOTL_.mjs} +1 -1
- package/dist/parser-Bh11BsdA.cjs +6 -0
- package/dist/parser-D8eQvE7N.mjs +6 -0
- package/dist/parser-DYRzXWmA.cjs +6 -0
- package/dist/routes-CHf76Ye4.cjs +6 -0
- package/dist/routes-CMUZGI6T.mjs +6 -0
- package/dist/routes-Co1mRM58.cjs +6 -0
- package/dist/search-dialog-BACuzoVX.cjs +6 -0
- package/dist/search-dialog-BKagVT17.js +6 -0
- package/dist/search-dialog-C8w12eUx.js +6 -0
- package/dist/search-dialog-CGyrozZE.cjs +6 -0
- package/dist/search-dialog-D26rUnJ_.cjs +6 -0
- package/dist/sidebar-DKvg6KOc.d.cts +491 -0
- package/dist/sidebar-Dr1TiRIy.d.ts +491 -0
- package/dist/utils-BxNAXhZZ.mjs +7 -0
- package/dist/utils-Clzu7jvb.cjs +7 -0
- package/dist/worker-pool-Bd8Y9KDv.mjs +6 -0
- package/dist/worker-pool-BwU8ckrg.cjs +6 -0
- package/package.json +27 -8
- package/src/client/app/doc-page.tsx +9 -5
- package/src/client/app/docs-layout.tsx +17 -3
- package/src/client/app/head.tsx +122 -0
- package/src/client/app/helmet-compat.tsx +36 -0
- package/src/client/app/mdx-component.tsx +5 -52
- package/src/client/app/mdx-components-context.tsx +32 -8
- package/src/client/app/routes-context.tsx +2 -2
- package/src/client/app/scroll-handler.tsx +1 -1
- package/src/client/app/theme-context.tsx +5 -5
- package/src/client/app/ui-context.tsx +42 -0
- package/src/client/components/docs-layout-default.tsx +85 -0
- package/src/client/components/icons-dev.tsx +38 -15
- package/src/client/components/mdx/callout.tsx +97 -0
- package/src/client/components/mdx/card.tsx +73 -98
- package/src/client/components/mdx/cards.tsx +27 -0
- package/src/client/components/mdx/code-block.tsx +37 -17
- package/src/client/components/mdx/field.tsx +24 -56
- package/src/client/components/mdx/image.tsx +36 -15
- package/src/client/components/mdx/index.ts +19 -53
- package/src/client/components/mdx/table.tsx +46 -148
- package/src/client/components/mdx/typographics.tsx +120 -0
- package/src/client/components/mdx/{hooks/use-code-block.ts → use-code-block.ts} +5 -7
- package/src/client/components/primitives/breadcrumbs.tsx +5 -24
- package/src/client/components/primitives/button.tsx +3 -142
- package/src/client/components/primitives/code-block.tsx +104 -97
- package/src/client/components/{docs-layout.tsx → primitives/docs-layout.tsx} +15 -24
- package/src/client/components/primitives/error-boundary.tsx +107 -0
- package/src/client/components/primitives/heading.tsx +128 -0
- package/src/client/components/primitives/helpers/observer.ts +62 -32
- package/src/client/components/primitives/image.tsx +26 -0
- package/src/client/components/primitives/link.tsx +50 -52
- package/src/client/components/primitives/menu.tsx +25 -49
- package/src/client/components/primitives/navbar.tsx +234 -59
- package/src/client/components/primitives/on-this-page.tsx +169 -40
- package/src/client/components/primitives/page-nav.tsx +11 -39
- package/src/client/components/primitives/popover.tsx +12 -30
- package/src/client/components/primitives/search-dialog.tsx +77 -71
- package/src/client/components/primitives/sidebar.tsx +312 -119
- package/src/client/components/primitives/skeleton.tsx +1 -1
- package/src/client/components/primitives/tabs.tsx +5 -16
- package/src/client/components/primitives/tooltip.tsx +1 -1
- package/src/client/components/ui-base/banner.tsx +66 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +26 -20
- package/src/client/components/ui-base/copy-markdown.tsx +43 -35
- package/src/client/components/ui-base/error-boundary.tsx +9 -46
- package/src/client/components/ui-base/github-stars.tsx +5 -3
- package/src/client/components/ui-base/index.ts +3 -3
- package/src/client/components/ui-base/last-updated.tsx +27 -0
- package/src/client/components/ui-base/navbar.tsx +183 -89
- package/src/client/components/ui-base/not-found.tsx +11 -9
- package/src/client/components/ui-base/on-this-page.tsx +8 -104
- package/src/client/components/ui-base/page-nav.tsx +23 -9
- package/src/client/components/ui-base/search-dialog.tsx +111 -36
- package/src/client/components/ui-base/search-highlight.tsx +10 -0
- package/src/client/components/ui-base/sidebar.tsx +77 -154
- package/src/client/components/ui-base/tabs.tsx +20 -7
- package/src/client/components/ui-base/theme-toggle.tsx +88 -10
- package/src/client/components/ui-base/version-i18n.tsx +80 -0
- package/src/client/hooks/index.ts +2 -1
- package/src/client/hooks/use-analytics.ts +272 -0
- package/src/client/hooks/use-i18n.ts +120 -53
- package/src/client/hooks/use-localized-to.ts +70 -30
- package/src/client/hooks/use-navbar.ts +69 -39
- package/src/client/hooks/use-page-nav.ts +28 -25
- package/src/client/hooks/use-routes.ts +64 -81
- package/src/client/hooks/use-search-highlight.ts +185 -0
- package/src/client/hooks/use-search.ts +12 -3
- package/src/client/hooks/use-sidebar.ts +183 -77
- package/src/client/hooks/use-tabs.ts +3 -4
- package/src/client/hooks/use-version.ts +46 -18
- package/src/client/index.ts +13 -86
- package/src/client/mdx.ts +2 -0
- package/src/client/primitives.ts +19 -0
- package/src/client/ssg/boltdocs-shell.tsx +78 -57
- package/src/client/ssg/create-routes.tsx +290 -50
- package/src/client/ssg/mdx-page.tsx +2 -1
- package/src/client/store/boltdocs-context.tsx +83 -12
- package/src/client/theme/neutral.css +74 -361
- package/src/client/theme/reset.css +189 -0
- package/src/client/types.ts +10 -2
- package/src/client/utils/path.ts +9 -0
- package/src/client/utils/react-to-text.ts +24 -24
- package/src/client/virtual.d.ts +1 -1
- package/src/shared/types.ts +97 -21
- package/dist/node-CWN8U_p8.mjs +0 -88
- package/dist/node-D5iosYXv.cjs +0 -88
- package/dist/search-dialog-3lvKsbVG.js +0 -6
- package/dist/search-dialog-DMK5OpgH.cjs +0 -6
- package/dist/use-search-C9bxCqfF.js +0 -6
- package/dist/use-search-DcfZSunO.cjs +0 -6
- package/src/client/components/mdx/admonition.tsx +0 -91
- package/src/client/components/mdx/badge.tsx +0 -41
- package/src/client/components/mdx/button.tsx +0 -35
- package/src/client/components/mdx/component-preview.tsx +0 -37
- package/src/client/components/mdx/component-props.tsx +0 -83
- package/src/client/components/mdx/file-tree.tsx +0 -325
- package/src/client/components/mdx/hooks/use-component-preview.ts +0 -16
- package/src/client/components/mdx/hooks/useTable.ts +0 -74
- package/src/client/components/mdx/hooks/useTabs.ts +0 -68
- package/src/client/components/mdx/link.tsx +0 -38
- package/src/client/components/mdx/list.tsx +0 -192
- package/src/client/components/mdx/tabs.tsx +0 -135
- package/src/client/components/mdx/video.tsx +0 -68
- package/src/client/components/primitives/index.ts +0 -19
- package/src/client/components/primitives/navigation-menu.tsx +0 -114
- package/src/client/components/ui-base/head.tsx +0 -76
- package/src/client/components/ui-base/loading.tsx +0 -57
- package/src/client/components/ui-base/powered-by.tsx +0 -25
- package/src/client/hooks/use-onthispage.ts +0 -23
- package/src/client/utils/use-on-change.ts +0 -15
|
@@ -1,113 +1,120 @@
|
|
|
1
|
-
import type { ComponentProps } from
|
|
2
|
-
import { cn } from
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import { cn } from '../../utils/cn'
|
|
3
3
|
|
|
4
|
-
interface CodeBlockRootProps extends ComponentProps<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
children,
|
|
28
|
+
className,
|
|
29
|
+
plain = false,
|
|
30
|
+
...props
|
|
17
31
|
}: CodeBlockRootProps) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
children,
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
42
58
|
}: CodeBlockHeaderProps) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
children,
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
64
80
|
}: CodeBlockGroupProps) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
className,
|
|
94
|
+
children,
|
|
95
|
+
shouldTruncate = false,
|
|
96
|
+
...props
|
|
91
97
|
}: CodeBlockContentProps) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 '
|
|
2
|
-
import {
|
|
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-
|
|
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-
|
|
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
|
|
89
|
+
* Content header area (breadcrumbs, title, description, etc).
|
|
96
90
|
*/
|
|
97
|
-
function
|
|
91
|
+
function Header({ children, className, style }: SlotProps) {
|
|
98
92
|
return (
|
|
99
|
-
<
|
|
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
|
-
</
|
|
95
|
+
</header>
|
|
105
96
|
)
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
/**
|
|
109
100
|
* Footer area inside the content section (page nav).
|
|
110
101
|
*/
|
|
111
|
-
function
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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(
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|