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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/cache-7G6D532T.mjs +1 -0
- package/dist/chunk-A4HQPEPU.mjs +1 -0
- package/dist/chunk-BA5NH5HU.mjs +1 -0
- package/dist/chunk-BQCD3DWG.mjs +1 -0
- package/dist/chunk-H63UMKYF.mjs +1 -0
- package/dist/chunk-IWHRQHS7.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-MFU7Q6WF.mjs +1 -0
- package/dist/chunk-QYPNX5UN.mjs +1 -0
- package/dist/chunk-XEAPSFMB.mjs +1 -0
- package/dist/client/components/mdx/index.d.mts +209 -0
- package/dist/client/components/mdx/index.d.ts +209 -0
- package/dist/client/components/mdx/index.js +1 -0
- package/dist/client/components/mdx/index.mjs +1 -0
- package/dist/client/hooks/index.d.mts +133 -0
- package/dist/client/hooks/index.d.ts +133 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/hooks/index.mjs +1 -0
- package/dist/client/index.d.mts +138 -298
- package/dist/client/index.d.ts +138 -298
- package/dist/client/index.js +1 -3630
- package/dist/client/index.mjs +1 -697
- package/dist/client/ssr.d.mts +7 -3
- package/dist/client/ssr.d.ts +7 -3
- package/dist/client/ssr.js +1 -2928
- package/dist/client/ssr.mjs +1 -33
- package/dist/{config-BsFQ-ErD.d.ts → config-CX4l-ZNp.d.mts} +42 -35
- package/dist/{config-BsFQ-ErD.d.mts → config-CX4l-ZNp.d.ts} +42 -35
- package/dist/node/index.d.mts +2 -4
- package/dist/node/index.d.ts +2 -4
- package/dist/node/index.js +31 -1161
- package/dist/node/index.mjs +31 -736
- package/dist/search-dialog-EB3N4TYM.mjs +1 -0
- package/dist/types-BuZWFT7r.d.ts +159 -0
- package/dist/types-CvT-SGbK.d.mts +159 -0
- package/dist/use-routes-5bAtAAYX.d.mts +30 -0
- package/dist/use-routes-BefRXY3v.d.ts +30 -0
- package/package.json +34 -12
- package/src/client/app/config-context.tsx +18 -0
- package/src/client/app/docs-layout.tsx +14 -0
- package/src/client/app/index.tsx +137 -262
- package/src/client/app/mdx-component.tsx +52 -0
- package/src/client/app/mdx-components-context.tsx +23 -0
- package/src/client/app/mdx-page.tsx +20 -0
- package/src/client/app/preload.tsx +38 -30
- package/src/client/app/router.tsx +30 -0
- package/src/client/app/scroll-handler.tsx +40 -0
- package/src/client/app/theme-context.tsx +75 -0
- package/src/client/components/default-layout.tsx +80 -0
- package/src/client/components/docs-layout.tsx +105 -0
- package/src/client/components/icons-dev.tsx +74 -0
- package/src/client/components/mdx/admonition.tsx +107 -0
- package/src/client/components/mdx/badge.tsx +41 -0
- package/src/client/components/mdx/button.tsx +35 -0
- package/src/client/components/mdx/card.tsx +124 -0
- package/src/client/components/mdx/code-block.tsx +119 -0
- package/src/client/components/mdx/component-preview.tsx +47 -0
- package/src/client/components/mdx/component-props.tsx +83 -0
- package/src/client/components/mdx/field.tsx +66 -0
- package/src/client/components/mdx/file-tree.tsx +287 -0
- package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
- package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
- package/src/client/components/mdx/hooks/useTable.ts +74 -0
- package/src/client/components/mdx/hooks/useTabs.ts +68 -0
- package/src/client/components/mdx/image.tsx +23 -0
- package/src/client/components/mdx/index.ts +53 -0
- package/src/client/components/mdx/link.tsx +38 -0
- package/src/client/components/mdx/list.tsx +192 -0
- package/src/client/components/mdx/table.tsx +156 -0
- package/src/client/components/mdx/tabs.tsx +135 -0
- package/src/client/components/mdx/video.tsx +68 -0
- package/src/client/components/primitives/breadcrumbs.tsx +79 -0
- package/src/client/components/primitives/button-group.tsx +54 -0
- package/src/client/components/primitives/button.tsx +145 -0
- package/src/client/components/primitives/helpers/observer.ts +120 -0
- package/src/client/components/primitives/index.ts +17 -0
- package/src/client/components/primitives/link.tsx +122 -0
- package/src/client/components/primitives/menu.tsx +159 -0
- package/src/client/components/primitives/navbar.tsx +359 -0
- package/src/client/components/primitives/navigation-menu.tsx +116 -0
- package/src/client/components/primitives/on-this-page.tsx +461 -0
- package/src/client/components/primitives/page-nav.tsx +87 -0
- package/src/client/components/primitives/popover.tsx +47 -0
- package/src/client/components/primitives/search-dialog.tsx +183 -0
- package/src/client/components/primitives/sidebar.tsx +154 -0
- package/src/client/components/primitives/tabs.tsx +90 -0
- package/src/client/components/primitives/tooltip.tsx +83 -0
- package/src/client/components/primitives/types.ts +11 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
- package/src/client/components/ui-base/copy-markdown.tsx +112 -0
- package/src/client/components/ui-base/error-boundary.tsx +52 -0
- package/src/client/components/ui-base/github-stars.tsx +27 -0
- package/src/client/components/ui-base/head.tsx +69 -0
- package/src/client/components/ui-base/loading.tsx +87 -0
- package/src/client/components/ui-base/navbar.tsx +138 -0
- package/src/client/components/ui-base/not-found.tsx +24 -0
- package/src/client/components/ui-base/on-this-page.tsx +152 -0
- package/src/client/components/ui-base/page-nav.tsx +39 -0
- package/src/client/components/ui-base/powered-by.tsx +19 -0
- package/src/client/components/ui-base/progress-bar.tsx +67 -0
- package/src/client/components/ui-base/search-dialog.tsx +82 -0
- package/src/client/components/ui-base/sidebar.tsx +104 -0
- package/src/client/components/ui-base/tabs.tsx +65 -0
- package/src/client/components/ui-base/theme-toggle.tsx +32 -0
- package/src/client/hooks/index.ts +12 -0
- package/src/client/hooks/use-breadcrumbs.ts +22 -0
- package/src/client/hooks/use-i18n.ts +84 -0
- package/src/client/hooks/use-localized-to.ts +95 -0
- package/src/client/hooks/use-location.ts +5 -0
- package/src/client/hooks/use-navbar.ts +60 -0
- package/src/client/hooks/use-onthispage.ts +23 -0
- package/src/client/hooks/use-page-nav.ts +22 -0
- package/src/client/hooks/use-routes.ts +72 -0
- package/src/client/hooks/use-search.ts +71 -0
- package/src/client/hooks/use-sidebar.ts +49 -0
- package/src/client/hooks/use-tabs.ts +43 -0
- package/src/client/hooks/use-version.ts +78 -0
- package/src/client/index.ts +55 -17
- package/src/client/integrations/codesandbox.ts +179 -0
- package/src/client/ssr.tsx +27 -16
- package/src/client/theme/neutral.css +360 -0
- package/src/client/types.ts +131 -27
- package/src/client/utils/cn.ts +6 -0
- package/src/client/utils/copy-clipboard.ts +22 -0
- package/src/client/utils/get-base-file-path.ts +21 -0
- package/src/client/utils/github.ts +121 -0
- package/src/client/utils/use-on-change.ts +15 -0
- package/src/client/virtual.d.ts +24 -0
- package/src/node/cache.ts +156 -156
- package/src/node/config.ts +159 -103
- package/src/node/index.ts +13 -13
- package/src/node/mdx.ts +213 -61
- package/src/node/plugin/entry.ts +29 -18
- package/src/node/plugin/html.ts +11 -11
- package/src/node/plugin/index.ts +161 -84
- package/src/node/plugin/types.ts +2 -4
- package/src/node/routes/cache.ts +6 -6
- package/src/node/routes/index.ts +206 -113
- package/src/node/routes/parser.ts +102 -82
- package/src/node/routes/sorter.ts +15 -15
- package/src/node/routes/types.ts +24 -24
- package/src/node/ssg/index.ts +73 -47
- package/src/node/ssg/meta.ts +4 -4
- package/src/node/ssg/options.ts +5 -5
- package/src/node/ssg/sitemap.ts +14 -14
- package/src/node/utils.ts +54 -31
- package/tsconfig.json +25 -20
- package/tsup.config.ts +23 -14
- package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
- package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
- package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
- package/dist/Video-KNTY5BNO.mjs +0 -6
- package/dist/cache-KNL5B4EE.mjs +0 -12
- package/dist/chunk-7SFUJWTB.mjs +0 -211
- package/dist/chunk-FFBNU6IJ.mjs +0 -386
- package/dist/chunk-FMTOYQLO.mjs +0 -37
- package/dist/chunk-TKLQWU7H.mjs +0 -1920
- package/dist/chunk-Z7JHYNAS.mjs +0 -57
- package/dist/client/index.css +0 -2847
- package/dist/client/ssr.css +0 -2847
- package/dist/types-Dj-bfnC3.d.mts +0 -74
- package/dist/types-Dj-bfnC3.d.ts +0 -74
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
- package/src/client/theme/components/CodeBlock/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
- package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
- package/src/client/theme/components/Playground/Playground.tsx +0 -180
- package/src/client/theme/components/Playground/index.ts +0 -1
- package/src/client/theme/components/Playground/playground.css +0 -238
- package/src/client/theme/components/Video/Video.tsx +0 -84
- package/src/client/theme/components/Video/index.ts +0 -1
- package/src/client/theme/components/Video/video.css +0 -41
- package/src/client/theme/components/mdx/Admonition.tsx +0 -80
- package/src/client/theme/components/mdx/Badge.tsx +0 -31
- package/src/client/theme/components/mdx/Button.tsx +0 -50
- package/src/client/theme/components/mdx/Card.tsx +0 -80
- package/src/client/theme/components/mdx/Field.tsx +0 -60
- package/src/client/theme/components/mdx/FileTree.tsx +0 -229
- package/src/client/theme/components/mdx/List.tsx +0 -57
- package/src/client/theme/components/mdx/Table.tsx +0 -151
- package/src/client/theme/components/mdx/Tabs.tsx +0 -123
- package/src/client/theme/components/mdx/index.ts +0 -27
- package/src/client/theme/components/mdx/mdx-components.css +0 -764
- package/src/client/theme/icons/bun.tsx +0 -62
- package/src/client/theme/icons/deno.tsx +0 -20
- package/src/client/theme/icons/discord.tsx +0 -12
- package/src/client/theme/icons/github.tsx +0 -15
- package/src/client/theme/icons/npm.tsx +0 -13
- package/src/client/theme/icons/pnpm.tsx +0 -72
- package/src/client/theme/icons/twitter.tsx +0 -12
- package/src/client/theme/styles/markdown.css +0 -394
- package/src/client/theme/styles/variables.css +0 -175
- package/src/client/theme/styles.css +0 -39
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
- package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
- package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
- package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
- package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
- package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
- package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
- package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
- package/src/client/theme/ui/Footer/footer.css +0 -32
- package/src/client/theme/ui/Head/Head.tsx +0 -69
- package/src/client/theme/ui/Head/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
- package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
- package/src/client/theme/ui/Layout/Layout.tsx +0 -203
- package/src/client/theme/ui/Layout/base.css +0 -106
- package/src/client/theme/ui/Layout/index.ts +0 -2
- package/src/client/theme/ui/Layout/pagination.css +0 -72
- package/src/client/theme/ui/Layout/responsive.css +0 -47
- package/src/client/theme/ui/Link/Link.tsx +0 -392
- package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
- package/src/client/theme/ui/Link/index.ts +0 -2
- package/src/client/theme/ui/Link/link-preview.css +0 -48
- package/src/client/theme/ui/Loading/Loading.tsx +0 -10
- package/src/client/theme/ui/Loading/index.ts +0 -1
- package/src/client/theme/ui/Loading/loading.css +0 -30
- package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
- package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
- package/src/client/theme/ui/Navbar/index.ts +0 -2
- package/src/client/theme/ui/Navbar/navbar.css +0 -347
- package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
- package/src/client/theme/ui/NotFound/index.ts +0 -1
- package/src/client/theme/ui/NotFound/not-found.css +0 -64
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
- package/src/client/theme/ui/OnThisPage/index.ts +0 -1
- package/src/client/theme/ui/OnThisPage/toc.css +0 -152
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
- package/src/client/theme/ui/PoweredBy/index.ts +0 -1
- package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
- package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
- package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
- package/src/client/theme/ui/ProgressBar/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
- package/src/client/theme/ui/SearchDialog/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/search.css +0 -152
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
- package/src/client/theme/ui/Sidebar/index.ts +0 -1
- package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
- package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
- package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
- package/src/client/utils.ts +0 -49
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ErrorInfo, ReactNode } from 'react'
|
|
2
|
+
import { Component } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from '../primitives'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
children?: ReactNode
|
|
8
|
+
fallback?: ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface State {
|
|
12
|
+
hasError: boolean
|
|
13
|
+
error?: Error
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
17
|
+
public state: State = { hasError: false }
|
|
18
|
+
|
|
19
|
+
public static getDerivedStateFromError(error: Error): State {
|
|
20
|
+
return { hasError: true, error }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
24
|
+
console.error('Uncaught error in Boltdocs Layout:', error, errorInfo)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public render() {
|
|
28
|
+
if (this.state.hasError) {
|
|
29
|
+
return (
|
|
30
|
+
this.props.fallback || (
|
|
31
|
+
<div className="flex flex-col items-center justify-center min-h-[40vh] text-center gap-4 px-4">
|
|
32
|
+
<div className="text-lg font-bold text-red-400">
|
|
33
|
+
Something went wrong
|
|
34
|
+
</div>
|
|
35
|
+
<p className="text-sm text-text-muted max-w-md">
|
|
36
|
+
{this.state.error?.message ||
|
|
37
|
+
'An unexpected error occurred while rendering this page.'}
|
|
38
|
+
</p>
|
|
39
|
+
<Button
|
|
40
|
+
className="rounded-lg border border-border-subtle bg-bg-surface px-5 py-2 text-sm font-medium text-text-main transition-colors hover:bg-bg-muted cursor-pointer"
|
|
41
|
+
onPress={() => this.setState({ hasError: false })}
|
|
42
|
+
>
|
|
43
|
+
Try again
|
|
44
|
+
</Button>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.props.children
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { getStarsRepo } from '@client/utils/github'
|
|
3
|
+
import { Github } from '@components/icons-dev'
|
|
4
|
+
|
|
5
|
+
export function GithubStars({ repo }: { repo: string }) {
|
|
6
|
+
const [stars, setStars] = useState<string | null>(null)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (repo) {
|
|
10
|
+
getStarsRepo(repo)
|
|
11
|
+
.then((stars) => setStars(stars))
|
|
12
|
+
.catch(() => setStars('0'))
|
|
13
|
+
}
|
|
14
|
+
}, [repo])
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<a
|
|
18
|
+
href={`https://github.com/${repo}`}
|
|
19
|
+
target="_blank"
|
|
20
|
+
rel="noopener noreferrer"
|
|
21
|
+
className="inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2.5 py-1.5 text-xs font-medium text-text-muted transition-all hover:bg-bg-main hover:border-border-strong hover:text-text-main"
|
|
22
|
+
>
|
|
23
|
+
<Github className="h-4 w-4" />
|
|
24
|
+
{stars && <span className="tabular-nums">{stars}</span>}
|
|
25
|
+
</a>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { useLocation } from 'react-router-dom'
|
|
3
|
+
|
|
4
|
+
interface HeadProps {
|
|
5
|
+
siteTitle: string
|
|
6
|
+
siteDescription?: string
|
|
7
|
+
routes: Array<{ path: string; title: string; description?: string }>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Head({ siteTitle, siteDescription, routes }: HeadProps) {
|
|
11
|
+
const location = useLocation()
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
// Find the current route's metadata
|
|
15
|
+
const currentRoute = routes.find((r) => r.path === location.pathname)
|
|
16
|
+
const pageTitle = currentRoute?.title
|
|
17
|
+
const pageDescription = currentRoute?.description || siteDescription || ''
|
|
18
|
+
|
|
19
|
+
// Update document title
|
|
20
|
+
document.title = pageTitle ? `${pageTitle} | ${siteTitle}` : siteTitle
|
|
21
|
+
|
|
22
|
+
// Update or create meta description
|
|
23
|
+
let metaDesc = document.querySelector(
|
|
24
|
+
'meta[name="description"]',
|
|
25
|
+
) as HTMLMetaElement | null
|
|
26
|
+
if (!metaDesc) {
|
|
27
|
+
metaDesc = document.createElement('meta')
|
|
28
|
+
metaDesc.name = 'description'
|
|
29
|
+
document.head.appendChild(metaDesc)
|
|
30
|
+
}
|
|
31
|
+
metaDesc.content = pageDescription
|
|
32
|
+
|
|
33
|
+
// Update OG tags
|
|
34
|
+
setMetaTag('property', 'og:title', document.title)
|
|
35
|
+
setMetaTag('property', 'og:description', pageDescription)
|
|
36
|
+
setMetaTag('property', 'og:type', 'article')
|
|
37
|
+
setMetaTag('property', 'og:url', window.location.href)
|
|
38
|
+
|
|
39
|
+
// Twitter card
|
|
40
|
+
setMetaTag('name', 'twitter:card', 'summary')
|
|
41
|
+
setMetaTag('name', 'twitter:title', document.title)
|
|
42
|
+
setMetaTag('name', 'twitter:description', pageDescription)
|
|
43
|
+
|
|
44
|
+
// Canonical URL
|
|
45
|
+
let canonical = document.querySelector(
|
|
46
|
+
'link[rel="canonical"]',
|
|
47
|
+
) as HTMLLinkElement | null
|
|
48
|
+
if (!canonical) {
|
|
49
|
+
canonical = document.createElement('link')
|
|
50
|
+
canonical.rel = 'canonical'
|
|
51
|
+
document.head.appendChild(canonical)
|
|
52
|
+
}
|
|
53
|
+
canonical.href = window.location.origin + location.pathname
|
|
54
|
+
}, [location.pathname, siteTitle, siteDescription, routes])
|
|
55
|
+
|
|
56
|
+
return null // This component only manages <head>, no visual output
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setMetaTag(attr: 'name' | 'property', key: string, content: string) {
|
|
60
|
+
let tag = document.querySelector(
|
|
61
|
+
`meta[${attr}="${key}"]`,
|
|
62
|
+
) as HTMLMetaElement | null
|
|
63
|
+
if (!tag) {
|
|
64
|
+
tag = document.createElement('meta')
|
|
65
|
+
tag.setAttribute(attr, key)
|
|
66
|
+
document.head.appendChild(tag)
|
|
67
|
+
}
|
|
68
|
+
tag.content = content
|
|
69
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A premium loading component that includes an SVG fill animation
|
|
5
|
+
* and a synchronized progress indicator.
|
|
6
|
+
*
|
|
7
|
+
* It features a glassmorphism container and a Bolt-style SVG logo
|
|
8
|
+
* with a dynamic fill effect.
|
|
9
|
+
*/
|
|
10
|
+
export function Loading() {
|
|
11
|
+
const [progress, setProgress] = useState(0)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let currentProgress = 0
|
|
15
|
+
let up = true
|
|
16
|
+
|
|
17
|
+
const interval = setInterval(() => {
|
|
18
|
+
if (up) {
|
|
19
|
+
currentProgress += 1
|
|
20
|
+
if (currentProgress >= 100) {
|
|
21
|
+
currentProgress = 100
|
|
22
|
+
up = false
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
currentProgress -= 1
|
|
26
|
+
if (currentProgress <= 0) {
|
|
27
|
+
currentProgress = 0
|
|
28
|
+
up = true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
setProgress(currentProgress)
|
|
32
|
+
}, 20)
|
|
33
|
+
|
|
34
|
+
return () => clearInterval(interval)
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
const clipPathValue = `inset(${100 - progress}% 0 0 0)`
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex flex-col items-center justify-center min-h-[60vh] p-4 text-center">
|
|
41
|
+
<div className="relative group">
|
|
42
|
+
<div className="relative inline-block">
|
|
43
|
+
{/* SVG Background (Dimmed Base) */}
|
|
44
|
+
<svg
|
|
45
|
+
className="w-24 h-auto opacity-10 filter grayscale brightness-50"
|
|
46
|
+
viewBox="0 0 60 51"
|
|
47
|
+
fill="none"
|
|
48
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
49
|
+
role="img"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
>
|
|
52
|
+
<title>Loading indicator background</title>
|
|
53
|
+
<path
|
|
54
|
+
d="M29.4449 0H19.4449V16.5L29.4449 6.5V0Z"
|
|
55
|
+
fill="currentColor"
|
|
56
|
+
/>
|
|
57
|
+
<path
|
|
58
|
+
d="M26.9449 22.7265C26.9449 22.5077 21.2201 27.0658 16.9449 28.5C13.7491 29.5721 12.3156 29.5038 8.94486 29.5C5.59532 29.4963 0 28.5 0 28.5C0 28.5 5.57953 28.5146 8.94486 27.5C12.5409 26.4158 14.8203 25.5843 17.9449 23.5C23.3445 19.898 29.4449 11.5 29.4449 11.5L29.9449 18C29.9449 18 33.5825 15.8308 36.4449 15C39.4452 14.1291 44.4449 14 44.4449 14C44.4449 14 36.9449 19 34.4449 21.5C31.5322 24.4126 29.8582 26.9017 29.4449 31C29.1217 34.2041 29.4771 36.4508 31.4449 39C33.5792 41.765 35.952 43.0183 39.4449 43C42.677 42.9831 45.3003 42.4182 47.4449 40C49.7406 37.4113 50.2495 34.4466 49.9449 31C49.6603 27.7804 48.4876 25.4953 45.9449 23.5C43.2931 21.4191 36.4449 24 36.4449 24L47.9449 15C47.9449 15 51.5761 16.771 53.4449 18.5C55.711 20.5967 56.7467 22.1546 57.9449 25C59.1784 27.9295 59.4832 29.8216 59.4449 33C59.4089 35.9867 59.179 37.78 57.9449 40.5C56.8475 42.9185 55.8511 44.6507 53.9449 46.5C51.9236 48.4609 50.5803 49.0076 47.9449 50C45.5414 50.9051 44.0131 51 41.4449 51C38.8766 51 37.3235 50.9685 34.9449 50C32.4851 48.9985 29.4449 46 29.4449 46V51H19.4449V37.9904L22.9449 31.4226L26.9449 22.7265Z"
|
|
59
|
+
fill="currentColor"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
|
|
63
|
+
{/* SVG Animated (Vibrant Fill Overlay) */}
|
|
64
|
+
<svg
|
|
65
|
+
className="absolute top-0 left-0 w-24 h-auto text-primary-500 drop-shadow-[0_0_20px_rgba(var(--primary-rgb),0.5)] transition-[clip-path] duration-100 ease-linear"
|
|
66
|
+
style={{ clipPath: clipPathValue }}
|
|
67
|
+
viewBox="0 0 60 51"
|
|
68
|
+
fill="none"
|
|
69
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
70
|
+
role="img"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>
|
|
73
|
+
<title>Loading indicator animated fill</title>
|
|
74
|
+
<path
|
|
75
|
+
d="M29.4449 0H19.4449V16.5L29.4449 6.5V0Z"
|
|
76
|
+
fill="currentColor"
|
|
77
|
+
/>
|
|
78
|
+
<path
|
|
79
|
+
d="M26.9449 22.7265C26.9449 22.5077 21.2201 27.0658 16.9449 28.5C13.7491 29.5721 12.3156 29.5038 8.94486 29.5C5.59532 29.4963 0 28.5 0 28.5C0 28.5 5.57953 28.5146 8.94486 27.5C12.5409 26.4158 14.8203 25.5843 17.9449 23.5C23.3445 19.898 29.4449 11.5 29.4449 11.5L29.9449 18C29.9449 18 33.5825 15.8308 36.4449 15C39.4452 14.1291 44.4449 14 44.4449 14C44.4449 14 36.9449 19 34.4449 21.5C31.5322 24.4126 29.8582 26.9017 29.4449 31C29.1217 34.2041 29.4771 36.4508 31.4449 39C33.5792 41.765 35.952 43.0183 39.4449 43C42.677 42.9831 45.3003 42.4182 47.4449 40C49.7406 37.4113 50.2495 34.4466 49.9449 31C49.6603 27.7804 48.4876 25.4953 45.9449 23.5C43.2931 21.4191 36.4449 24 36.4449 24L47.9449 15C47.9449 15 51.5761 16.771 53.4449 18.5C55.711 20.5967 56.7467 22.1546 57.9449 25C59.1784 27.9295 59.4832 29.8216 59.4449 33C59.4089 35.9867 59.179 37.78 57.9449 40.5C56.8475 42.9185 55.8511 44.6507 53.9449 46.5C51.9236 48.4609 50.5803 49.0076 47.9449 50C45.5414 50.9051 44.0131 51 41.4449 51C38.8766 51 37.3235 50.9685 34.9449 50C32.4851 48.9985 29.4449 46 29.4449 46V51H19.4449V37.9904L22.9449 31.4226L26.9449 22.7265Z"
|
|
80
|
+
fill="currentColor"
|
|
81
|
+
/>
|
|
82
|
+
</svg>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Suspense, lazy } from 'react'
|
|
2
|
+
import { useNavbar } from '@hooks/use-navbar'
|
|
3
|
+
import { useVersion } from '@hooks/use-version'
|
|
4
|
+
import { useI18n } from '@hooks/use-i18n'
|
|
5
|
+
import { useRoutes } from '@hooks/use-routes'
|
|
6
|
+
import NavbarPrimitive from '@components/primitives/navbar'
|
|
7
|
+
import { ThemeToggle } from './theme-toggle'
|
|
8
|
+
import { GithubStars } from './github-stars'
|
|
9
|
+
import { Tabs } from './tabs'
|
|
10
|
+
import { useLocation } from 'react-router-dom'
|
|
11
|
+
import type { BoltdocsSocialLink } from '@node/config'
|
|
12
|
+
import Menu from '@components/primitives/menu'
|
|
13
|
+
import { Button } from '@components/primitives/button'
|
|
14
|
+
import { ChevronDown } from 'lucide-react'
|
|
15
|
+
import { useConfig } from '@client/app/config-context'
|
|
16
|
+
|
|
17
|
+
const SearchDialog = lazy(() =>
|
|
18
|
+
import('./search-dialog').then((m) => ({
|
|
19
|
+
default: m.SearchDialog,
|
|
20
|
+
})),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export function Navbar() {
|
|
24
|
+
const { links, title, logo, logoProps, github, social, config } = useNavbar()
|
|
25
|
+
const { routes, allRoutes, currentVersion, currentLocale } = useRoutes()
|
|
26
|
+
const { pathname } = useLocation()
|
|
27
|
+
const { themeConfig } = useConfig()
|
|
28
|
+
|
|
29
|
+
const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<NavbarPrimitive.NavbarRoot className={hasTabs ? 'border-b-0' : ''}>
|
|
33
|
+
<NavbarPrimitive.Content>
|
|
34
|
+
<NavbarPrimitive.NavbarLeft>
|
|
35
|
+
<NavbarPrimitive.NavbarLogo
|
|
36
|
+
src={logo ?? ''}
|
|
37
|
+
alt={logoProps?.alt || title}
|
|
38
|
+
width={logoProps?.width ?? 24}
|
|
39
|
+
height={logoProps?.height ?? 24}
|
|
40
|
+
/>
|
|
41
|
+
<NavbarPrimitive.Title>{title}</NavbarPrimitive.Title>
|
|
42
|
+
|
|
43
|
+
{config.versions && currentVersion && <NavbarVersion />}
|
|
44
|
+
|
|
45
|
+
<NavbarPrimitive.Links>
|
|
46
|
+
{links.map((link) => (
|
|
47
|
+
<NavbarPrimitive.Link key={link.href} {...(link as any)} />
|
|
48
|
+
))}
|
|
49
|
+
</NavbarPrimitive.Links>
|
|
50
|
+
</NavbarPrimitive.NavbarLeft>
|
|
51
|
+
<NavbarPrimitive.NavbarCenter>
|
|
52
|
+
<Suspense
|
|
53
|
+
fallback={
|
|
54
|
+
<div className="h-9 w-32 animate-pulse rounded-md bg-bg-surface" />
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
<SearchDialog routes={routes || []} />
|
|
58
|
+
</Suspense>
|
|
59
|
+
</NavbarPrimitive.NavbarCenter>
|
|
60
|
+
<NavbarPrimitive.NavbarRight>
|
|
61
|
+
{config.i18n && currentLocale && <NavbarI18n />}
|
|
62
|
+
<NavbarPrimitive.Split />
|
|
63
|
+
<ThemeToggle />
|
|
64
|
+
{github && <GithubStars repo={themeConfig?.githubRepo ?? ''} />}
|
|
65
|
+
{social.length > 0 && <NavbarPrimitive.Split />}
|
|
66
|
+
<div className="flex items-center gap-1">
|
|
67
|
+
{social.map(({ icon, link }: BoltdocsSocialLink) => (
|
|
68
|
+
<NavbarPrimitive.Socials
|
|
69
|
+
key={link}
|
|
70
|
+
icon={icon}
|
|
71
|
+
link={link}
|
|
72
|
+
className="p-1.5"
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</NavbarPrimitive.NavbarRight>
|
|
77
|
+
</NavbarPrimitive.Content>
|
|
78
|
+
|
|
79
|
+
{pathname !== '/' && hasTabs && config.themeConfig?.tabs && (
|
|
80
|
+
<div className="w-full border-b border-border-subtle bg-bg-main">
|
|
81
|
+
<Tabs
|
|
82
|
+
tabs={config.themeConfig.tabs}
|
|
83
|
+
routes={allRoutes || routes || []}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</NavbarPrimitive.NavbarRoot>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function NavbarVersion() {
|
|
92
|
+
const { currentVersionLabel, availableVersions, handleVersionChange } =
|
|
93
|
+
useVersion()
|
|
94
|
+
|
|
95
|
+
if (availableVersions.length === 0) return null
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Menu.Trigger>
|
|
99
|
+
<Button variant={'outline'} iconPosition="right" icon={<ChevronDown />}>
|
|
100
|
+
{currentVersionLabel}
|
|
101
|
+
</Button>
|
|
102
|
+
<Menu.Section items={availableVersions}>
|
|
103
|
+
{(version) => (
|
|
104
|
+
<Menu.Item
|
|
105
|
+
key={`${version.value ?? ''}`}
|
|
106
|
+
onPress={() => handleVersionChange(version.value)}
|
|
107
|
+
>
|
|
108
|
+
{version.label as string}
|
|
109
|
+
</Menu.Item>
|
|
110
|
+
)}
|
|
111
|
+
</Menu.Section>
|
|
112
|
+
</Menu.Trigger>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function NavbarI18n() {
|
|
117
|
+
const { currentLocaleLabel, availableLocales, handleLocaleChange } = useI18n()
|
|
118
|
+
|
|
119
|
+
if (availableLocales.length === 0) return null
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Menu.Trigger>
|
|
123
|
+
<Button variant={'outline'} iconPosition="right" icon={<ChevronDown />}>
|
|
124
|
+
{currentLocaleLabel}
|
|
125
|
+
</Button>
|
|
126
|
+
<Menu.Section items={availableLocales}>
|
|
127
|
+
{(locale) => (
|
|
128
|
+
<Menu.Item
|
|
129
|
+
key={`${locale.value ?? ''}`}
|
|
130
|
+
onPress={() => handleLocaleChange(locale.value)}
|
|
131
|
+
>
|
|
132
|
+
{locale.label as string}
|
|
133
|
+
</Menu.Item>
|
|
134
|
+
)}
|
|
135
|
+
</Menu.Section>
|
|
136
|
+
</Menu.Trigger>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ArrowLeft } from 'lucide-react'
|
|
2
|
+
import { Link } from '@components/primitives/link'
|
|
3
|
+
|
|
4
|
+
export function NotFound() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex items-center justify-center min-h-[60vh] text-center">
|
|
7
|
+
<div className="space-y-4">
|
|
8
|
+
<span className="text-8xl font-black tracking-tighter text-primary-500/20">
|
|
9
|
+
404
|
|
10
|
+
</span>
|
|
11
|
+
<h1 className="text-2xl font-bold text-text-main">Page Not Found</h1>
|
|
12
|
+
<p className="text-sm text-text-muted max-w-sm mx-auto">
|
|
13
|
+
The page you're looking for doesn't exist or has been moved.
|
|
14
|
+
</p>
|
|
15
|
+
<Link
|
|
16
|
+
href="/"
|
|
17
|
+
className="inline-flex items-center gap-2 rounded-lg bg-primary-500 px-5 py-2.5 text-sm font-semibold text-white outline-none transition-all hover:brightness-110 hover:shadow-lg focus-visible:ring-2 focus-visible:ring-primary-500/30"
|
|
18
|
+
>
|
|
19
|
+
<ArrowLeft size={16} /> Go to Home
|
|
20
|
+
</Link>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import OTP, {
|
|
2
|
+
AnchorProvider,
|
|
3
|
+
ScrollProvider,
|
|
4
|
+
useActiveAnchor,
|
|
5
|
+
} from '@components/primitives/on-this-page'
|
|
6
|
+
import React, { useRef, useEffect, useState, useCallback } from 'react'
|
|
7
|
+
import { useOnThisPage } from '@hooks/use-onthispage'
|
|
8
|
+
import type { OnThisPageProps } from '@client/types'
|
|
9
|
+
import { Pencil, CircleHelp, TextAlignStart } from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
export function OnThisPage({
|
|
12
|
+
headings: rawHeadings = [],
|
|
13
|
+
editLink,
|
|
14
|
+
communityHelp,
|
|
15
|
+
filePath,
|
|
16
|
+
}: OnThisPageProps) {
|
|
17
|
+
const { headings } = useOnThisPage(rawHeadings)
|
|
18
|
+
|
|
19
|
+
const toc = React.useMemo(
|
|
20
|
+
() =>
|
|
21
|
+
headings.map((h) => ({ title: h.text, url: `#${h.id}`, depth: h.level })),
|
|
22
|
+
[headings],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if (headings.length === 0) return null
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AnchorProvider toc={toc}>
|
|
29
|
+
<OnThisPageInner
|
|
30
|
+
headings={headings}
|
|
31
|
+
editLink={editLink}
|
|
32
|
+
communityHelp={communityHelp}
|
|
33
|
+
filePath={filePath}
|
|
34
|
+
/>
|
|
35
|
+
</AnchorProvider>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function OnThisPageInner({
|
|
40
|
+
headings,
|
|
41
|
+
editLink,
|
|
42
|
+
communityHelp,
|
|
43
|
+
filePath,
|
|
44
|
+
}: OnThisPageProps & {
|
|
45
|
+
headings: { level: number; text: string; id: string }[]
|
|
46
|
+
}) {
|
|
47
|
+
const activeId = useActiveAnchor()
|
|
48
|
+
const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({
|
|
49
|
+
opacity: 0,
|
|
50
|
+
})
|
|
51
|
+
const listRef = useRef<HTMLUListElement>(null)
|
|
52
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!activeId || !listRef.current) return
|
|
56
|
+
|
|
57
|
+
const activeLink = listRef.current.querySelector(
|
|
58
|
+
`a[href="#${activeId}"]`,
|
|
59
|
+
) as HTMLElement
|
|
60
|
+
|
|
61
|
+
if (activeLink) {
|
|
62
|
+
setIndicatorStyle({
|
|
63
|
+
transform: `translateY(${activeLink.offsetTop}px)`,
|
|
64
|
+
height: `${activeLink.offsetHeight}px`,
|
|
65
|
+
opacity: 1,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}, [activeId])
|
|
69
|
+
|
|
70
|
+
const handleClick = useCallback(
|
|
71
|
+
(e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
|
|
72
|
+
e.preventDefault()
|
|
73
|
+
const el = document.getElementById(id)
|
|
74
|
+
|
|
75
|
+
if (el) {
|
|
76
|
+
el.scrollIntoView({ behavior: 'smooth' })
|
|
77
|
+
window.history.pushState(null, '', `#${id}`)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<OTP.OnThisPageRoot>
|
|
85
|
+
<OTP.OnThisPageHeader className="flex flex-row gap-x-2">
|
|
86
|
+
<TextAlignStart size={16} />
|
|
87
|
+
On this page
|
|
88
|
+
</OTP.OnThisPageHeader>
|
|
89
|
+
<ScrollProvider containerRef={scrollContainerRef}>
|
|
90
|
+
<OTP.OnThisPageContent
|
|
91
|
+
className="max-h-[450px] boltdocs-otp-scroll-area"
|
|
92
|
+
ref={scrollContainerRef}
|
|
93
|
+
>
|
|
94
|
+
<OTP.OnThisPageIndicator style={indicatorStyle} />
|
|
95
|
+
<ul
|
|
96
|
+
className="relative space-y-2 border-l border-border-subtle"
|
|
97
|
+
ref={listRef}
|
|
98
|
+
>
|
|
99
|
+
{headings.map((h) => (
|
|
100
|
+
<OTP.OnThisPageItem key={h.id} level={h.level}>
|
|
101
|
+
<OTP.OnThisPageLink
|
|
102
|
+
href={`#${h.id}`}
|
|
103
|
+
active={activeId === h.id}
|
|
104
|
+
onClick={(e) => handleClick(e, h.id)}
|
|
105
|
+
className="pl-4"
|
|
106
|
+
>
|
|
107
|
+
{h.text}
|
|
108
|
+
</OTP.OnThisPageLink>
|
|
109
|
+
</OTP.OnThisPageItem>
|
|
110
|
+
))}
|
|
111
|
+
</ul>
|
|
112
|
+
</OTP.OnThisPageContent>
|
|
113
|
+
</ScrollProvider>
|
|
114
|
+
|
|
115
|
+
{(editLink || communityHelp) && (
|
|
116
|
+
<div className="mt-8 pt-8 border-t border-border-subtle space-y-4">
|
|
117
|
+
<p className="text-xs font-bold uppercase tracking-wider text-text-main">
|
|
118
|
+
Need help?
|
|
119
|
+
</p>
|
|
120
|
+
<ul className="space-y-3">
|
|
121
|
+
{editLink && filePath && (
|
|
122
|
+
<li>
|
|
123
|
+
<a
|
|
124
|
+
href={editLink.replace(':path', filePath)}
|
|
125
|
+
target="_blank"
|
|
126
|
+
rel="noopener noreferrer"
|
|
127
|
+
className="flex items-center gap-2 text-sm text-text-muted hover:text-text-main transition-colors"
|
|
128
|
+
>
|
|
129
|
+
<Pencil size={16} />
|
|
130
|
+
Edit this page
|
|
131
|
+
</a>
|
|
132
|
+
</li>
|
|
133
|
+
)}
|
|
134
|
+
{communityHelp && (
|
|
135
|
+
<li>
|
|
136
|
+
<a
|
|
137
|
+
href={communityHelp}
|
|
138
|
+
target="_blank"
|
|
139
|
+
rel="noopener noreferrer"
|
|
140
|
+
className="flex items-center gap-2 text-sm text-text-muted hover:text-text-main transition-colors"
|
|
141
|
+
>
|
|
142
|
+
<CircleHelp size={16} />
|
|
143
|
+
Community help
|
|
144
|
+
</a>
|
|
145
|
+
</li>
|
|
146
|
+
)}
|
|
147
|
+
</ul>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
</OTP.OnThisPageRoot>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { usePageNav } from '@hooks/use-page-nav'
|
|
2
|
+
import PageNavPrimitive from '@components/primitives/page-nav'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component to display the previous and next page navigation buttons.
|
|
6
|
+
*/
|
|
7
|
+
export function PageNav() {
|
|
8
|
+
const { prevPage, nextPage } = usePageNav()
|
|
9
|
+
|
|
10
|
+
if (!prevPage && !nextPage) return null
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<PageNavPrimitive.PageNavRoot>
|
|
14
|
+
{prevPage ? (
|
|
15
|
+
<PageNavPrimitive.PageNavLink to={prevPage.path} direction="prev">
|
|
16
|
+
<PageNavPrimitive.PageNavLink.Title>
|
|
17
|
+
Previous
|
|
18
|
+
</PageNavPrimitive.PageNavLink.Title>
|
|
19
|
+
<PageNavPrimitive.PageNavLink.Description>
|
|
20
|
+
{prevPage.title}
|
|
21
|
+
</PageNavPrimitive.PageNavLink.Description>
|
|
22
|
+
</PageNavPrimitive.PageNavLink>
|
|
23
|
+
) : (
|
|
24
|
+
<div />
|
|
25
|
+
)}
|
|
26
|
+
|
|
27
|
+
{nextPage && (
|
|
28
|
+
<PageNavPrimitive.PageNavLink to={nextPage.path} direction="next">
|
|
29
|
+
<PageNavPrimitive.PageNavLink.Title>
|
|
30
|
+
Next
|
|
31
|
+
</PageNavPrimitive.PageNavLink.Title>
|
|
32
|
+
<PageNavPrimitive.PageNavLink.Description>
|
|
33
|
+
{nextPage.title}
|
|
34
|
+
</PageNavPrimitive.PageNavLink.Description>
|
|
35
|
+
</PageNavPrimitive.PageNavLink>
|
|
36
|
+
)}
|
|
37
|
+
</PageNavPrimitive.PageNavRoot>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Zap } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
export function PoweredBy() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="rounded-full px-4 py-2 bg-gray-100 text-xs text-gray-500 flex items-center gap-1 mt-6 justify-center">
|
|
6
|
+
<a
|
|
7
|
+
href="https://github.com/jesusalcaladev/boltdocs"
|
|
8
|
+
target="_blank"
|
|
9
|
+
rel="noopener noreferrer"
|
|
10
|
+
className="flex items-center gap-1"
|
|
11
|
+
>
|
|
12
|
+
<Zap className="powered-by-icon" size={12} fill="currentColor" />
|
|
13
|
+
<span>
|
|
14
|
+
Powered by <strong>Boltdocs</strong>
|
|
15
|
+
</span>
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { ProgressBar as AriaProgressBar } from 'react-aria-components'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A reading progress bar component that tracks the scroll position
|
|
6
|
+
* within the Boltdocs content area.
|
|
7
|
+
*
|
|
8
|
+
* It utilizes react-aria-components for accessibility and is fixed
|
|
9
|
+
* to the top of the viewport for a premium, non-intrusive UI experience.
|
|
10
|
+
*/
|
|
11
|
+
export function ProgressBar() {
|
|
12
|
+
const [progress, setProgress] = useState(0)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let container: Element | null = null
|
|
16
|
+
let timer: NodeJS.Timeout
|
|
17
|
+
|
|
18
|
+
const handleScroll = () => {
|
|
19
|
+
if (!container) return
|
|
20
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
21
|
+
if (scrollHeight <= clientHeight) {
|
|
22
|
+
setProgress(0)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
const scrollPercent = (scrollTop / (scrollHeight - clientHeight)) * 100
|
|
26
|
+
setProgress(Math.min(100, Math.max(0, scrollPercent)))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const attachListener = () => {
|
|
30
|
+
// Find the main content area for Boltdocs
|
|
31
|
+
container = document.querySelector('.boltdocs-content')
|
|
32
|
+
if (container) {
|
|
33
|
+
container.addEventListener('scroll', handleScroll)
|
|
34
|
+
handleScroll()
|
|
35
|
+
if (timer) clearInterval(timer)
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Since the container might not be present at mount (if content is lazy loaded),
|
|
42
|
+
// we poll until it's found or the component unmounts.
|
|
43
|
+
if (!attachListener()) {
|
|
44
|
+
timer = setInterval(attachListener, 100)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
if (container) container.removeEventListener('scroll', handleScroll)
|
|
49
|
+
if (timer) clearInterval(timer)
|
|
50
|
+
}
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<AriaProgressBar
|
|
55
|
+
value={progress}
|
|
56
|
+
aria-label="Reading progress"
|
|
57
|
+
className="fixed top-0 left-0 right-0 z-999 h-0.5 bg-transparent w-full pointer-events-none"
|
|
58
|
+
>
|
|
59
|
+
{({ percentage }) => (
|
|
60
|
+
<div
|
|
61
|
+
className="h-full bg-primary-500 transition-[width] duration-300 ease-out shadow-[0_0_8px_rgba(var(--primary-rgb),0.4)]"
|
|
62
|
+
style={{ width: `${percentage}%` }}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</AriaProgressBar>
|
|
66
|
+
)
|
|
67
|
+
}
|