@docubook/create 2.4.0 → 2.5.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/package.json +1 -1
- package/src/dist/app/docs/[[...slug]]/page.tsx +55 -59
- package/src/dist/app/docs/layout.tsx +11 -4
- package/src/dist/app/layout.tsx +10 -7
- package/src/dist/app/page.tsx +1 -1
- package/src/dist/components/{context-popover.tsx → ContextPopover.tsx} +3 -2
- package/src/dist/components/{docs-breadcrumb.tsx → DocsBreadcrumb.tsx} +1 -1
- package/src/dist/components/DocsNavbar.tsx +46 -0
- package/src/dist/components/DocsSidebar.tsx +196 -0
- package/src/dist/components/Github.tsx +26 -0
- package/src/dist/components/{scroll-to-top.tsx → ScrollToTop.tsx} +16 -9
- package/src/dist/components/SearchBox.tsx +37 -0
- package/src/dist/components/SearchContext.tsx +47 -0
- package/src/dist/components/SearchModal.tsx +1 -1
- package/src/dist/components/SearchTrigger.tsx +5 -5
- package/src/dist/components/Sponsor.tsx +2 -2
- package/src/dist/components/{theme-toggle.tsx → ThemeToggle.tsx} +10 -10
- package/src/dist/components/TocObserver.tsx +197 -0
- package/src/dist/components/footer.tsx +16 -12
- package/src/dist/components/leftbar.tsx +45 -73
- package/src/dist/components/markdown/AccordionGroupMdx.tsx +1 -1
- package/src/dist/components/markdown/AccordionMdx.tsx +1 -1
- package/src/dist/components/navbar.tsx +130 -53
- package/src/dist/components/toc.tsx +16 -14
- package/src/dist/components/typography.tsx +1 -1
- package/src/dist/components/ui/icon-cloud.tsx +353 -0
- package/src/dist/components/ui/scroll-area.tsx +2 -2
- package/src/dist/components/ui/sheet.tsx +4 -4
- package/src/dist/components/ui/toggle.tsx +3 -3
- package/src/dist/hooks/useActiveSection.ts +34 -32
- package/src/dist/hooks/useScrollPosition.ts +16 -14
- package/src/dist/package.json +1 -1
- package/src/dist/styles/algolia.css +11 -9
- package/src/dist/styles/{syntax.css → override.css} +82 -39
- package/src/dist/tailwind.config.ts +11 -110
- package/src/dist/components/GithubStart.tsx +0 -44
- package/src/dist/components/mob-toc.tsx +0 -134
- package/src/dist/components/search.tsx +0 -55
- package/src/dist/components/toc-observer.tsx +0 -254
- /package/src/dist/components/{docs-menu.tsx → DocsMenu.tsx} +0 -0
- /package/src/dist/components/{edit-on-github.tsx → EditWithGithub.tsx} +0 -0
- /package/src/dist/components/{theme-provider.tsx → ThemeProvider.tsx} +0 -0
- /package/src/dist/{components/AccordionContext.ts → lib/accordion-context.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
|
-
import { notFound } from "next/navigation"
|
|
2
|
-
import { getDocsForSlug, getDocsTocs } from "@/lib/markdown"
|
|
3
|
-
import DocsBreadcrumb from "@/components/
|
|
4
|
-
import Pagination from "@/components/pagination"
|
|
5
|
-
import Toc from "@/components/toc"
|
|
6
|
-
import { Typography } from "@/components/typography"
|
|
7
|
-
import EditThisPage from "@/components/
|
|
8
|
-
import { formatDate2 } from "@/lib/utils"
|
|
9
|
-
import docuConfig from "@/docu.json"
|
|
10
|
-
import MobToc from "@/components/
|
|
1
|
+
import { notFound } from "next/navigation"
|
|
2
|
+
import { getDocsForSlug, getDocsTocs } from "@/lib/markdown"
|
|
3
|
+
import DocsBreadcrumb from "@/components/DocsBreadcrumb"
|
|
4
|
+
import Pagination from "@/components/pagination"
|
|
5
|
+
import Toc from "@/components/toc"
|
|
6
|
+
import { Typography } from "@/components/typography"
|
|
7
|
+
import EditThisPage from "@/components/EditWithGithub"
|
|
8
|
+
import { formatDate2 } from "@/lib/utils"
|
|
9
|
+
import docuConfig from "@/docu.json"
|
|
10
|
+
import MobToc from "@/components/DocsSidebar"
|
|
11
11
|
|
|
12
|
-
const { meta } = docuConfig
|
|
12
|
+
const { meta } = docuConfig
|
|
13
13
|
|
|
14
14
|
type PageProps = {
|
|
15
15
|
params: Promise<{
|
|
16
|
-
slug: string[]
|
|
17
|
-
}
|
|
18
|
-
}
|
|
16
|
+
slug: string[]
|
|
17
|
+
}>
|
|
18
|
+
}
|
|
19
19
|
|
|
20
20
|
// Function to generate metadata dynamically
|
|
21
21
|
export async function generateMetadata(props: PageProps) {
|
|
22
|
-
const params = await props.params
|
|
22
|
+
const params = await props.params
|
|
23
23
|
|
|
24
|
-
const {
|
|
25
|
-
slug = []
|
|
26
|
-
} = params;
|
|
24
|
+
const { slug = [] } = params
|
|
27
25
|
|
|
28
|
-
const pathName = slug.join("/")
|
|
29
|
-
const res = await getDocsForSlug(pathName)
|
|
26
|
+
const pathName = slug.join("/")
|
|
27
|
+
const res = await getDocsForSlug(pathName)
|
|
30
28
|
|
|
31
29
|
if (!res) {
|
|
32
30
|
return {
|
|
33
31
|
title: "Page Not Found",
|
|
34
32
|
description: "The requested page was not found.",
|
|
35
|
-
}
|
|
33
|
+
}
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
const { title, description, image } = res.frontmatter
|
|
36
|
+
const { title, description, image } = res.frontmatter
|
|
39
37
|
|
|
40
38
|
// Absolute URL for og:image
|
|
41
|
-
const ogImage = image
|
|
42
|
-
? `${meta.baseURL}/images/${image}`
|
|
43
|
-
: `${meta.baseURL}/images/og-image.png`;
|
|
39
|
+
const ogImage = image ? `${meta.baseURL}/images/${image}` : `${meta.baseURL}/images/og-image.png`
|
|
44
40
|
|
|
45
41
|
return {
|
|
46
42
|
title: `${title}`,
|
|
@@ -65,49 +61,49 @@ export async function generateMetadata(props: PageProps) {
|
|
|
65
61
|
description,
|
|
66
62
|
images: [ogImage],
|
|
67
63
|
},
|
|
68
|
-
}
|
|
64
|
+
}
|
|
69
65
|
}
|
|
70
66
|
|
|
71
67
|
export default async function DocsPage(props: PageProps) {
|
|
72
|
-
const params = await props.params
|
|
68
|
+
const params = await props.params
|
|
73
69
|
|
|
74
|
-
const {
|
|
75
|
-
slug = []
|
|
76
|
-
} = params;
|
|
70
|
+
const { slug = [] } = params
|
|
77
71
|
|
|
78
|
-
const pathName = slug.join("/")
|
|
79
|
-
const res = await getDocsForSlug(pathName)
|
|
72
|
+
const pathName = slug.join("/")
|
|
73
|
+
const res = await getDocsForSlug(pathName)
|
|
80
74
|
|
|
81
|
-
if (!res) notFound()
|
|
75
|
+
if (!res) notFound()
|
|
82
76
|
|
|
83
|
-
const { title, description, image: _image, date } = res.frontmatter
|
|
84
|
-
const filePath = res.filePath
|
|
85
|
-
const tocs = await getDocsTocs(pathName)
|
|
77
|
+
const { title, description, image: _image, date } = res.frontmatter
|
|
78
|
+
const filePath = res.filePath
|
|
79
|
+
const tocs = await getDocsTocs(pathName)
|
|
86
80
|
|
|
87
81
|
return (
|
|
88
|
-
<div className="flex
|
|
89
|
-
<div className="flex-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
<div className="flex w-full flex-1 px-0 pb-4 lg:px-8 lg:pb-8 h-[calc(100vh-4rem)]">
|
|
83
|
+
<div id="scroll-container" className="max-lg:scroll-p-16 bg-card dark:bg-card/20 border-muted-foreground/20 flex w-full h-full flex-col items-start rounded-xl border shadow-md backdrop-blur-sm lg:flex-row overflow-y-auto relative">
|
|
84
|
+
<div className="flex-7 w-full min-w-0 px-4 py-4 lg:px-8 lg:py-8">
|
|
85
|
+
<MobToc tocs={tocs} />
|
|
86
|
+
<DocsBreadcrumb paths={slug} />
|
|
87
|
+
<Typography>
|
|
88
|
+
<h1 className="-mt-0.5! text-3xl">{title}</h1>
|
|
89
|
+
<p className="text-muted-foreground -mt-4 text-[16.5px]">{description}</p>
|
|
90
|
+
<div>{res.content}</div>
|
|
91
|
+
<div
|
|
92
|
+
className={`border-x-muted-foreground my-8 flex items-center border-b-2 border-dashed ${docuConfig.repository?.editLink ? "justify-between" : "justify-end"
|
|
93
|
+
}`}
|
|
94
|
+
>
|
|
95
|
+
{docuConfig.repository?.editLink && <EditThisPage filePath={filePath} />}
|
|
96
|
+
{date && (
|
|
97
|
+
<p className="text-muted-foreground text-[13px]">
|
|
98
|
+
Published on {formatDate2(date)}
|
|
99
|
+
</p>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
<Pagination pathname={pathName} />
|
|
103
|
+
</Typography>
|
|
104
|
+
</div>
|
|
105
|
+
<Toc tocs={tocs} />
|
|
109
106
|
</div>
|
|
110
|
-
<Toc path={pathName} />
|
|
111
107
|
</div>
|
|
112
|
-
)
|
|
108
|
+
)
|
|
113
109
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Leftbar } from "@/components/leftbar";
|
|
2
|
+
import DocsNavbar from "@/components/DocsNavbar";
|
|
3
|
+
import "@/styles/override.css";
|
|
2
4
|
|
|
3
5
|
export default function DocsLayout({
|
|
4
6
|
children,
|
|
@@ -6,10 +8,15 @@ export default function DocsLayout({
|
|
|
6
8
|
children: React.ReactNode;
|
|
7
9
|
}>) {
|
|
8
10
|
return (
|
|
9
|
-
<div className="flex
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
<div className="docs-layout flex flex-col min-h-screen w-full">
|
|
12
|
+
<div className="flex flex-1 items-start w-full">
|
|
13
|
+
<Leftbar key="leftbar" />
|
|
14
|
+
<main className="flex-1 min-w-0 dark:bg-background/50 min-h-screen flex flex-col">
|
|
15
|
+
<DocsNavbar />
|
|
16
|
+
<div className="flex-1 w-full">
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
</main>
|
|
13
20
|
</div>
|
|
14
21
|
</div>
|
|
15
22
|
);
|
package/src/dist/app/layout.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
import { ThemeProvider } from "@/components/
|
|
2
|
+
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
3
3
|
import { Navbar } from "@/components/navbar";
|
|
4
4
|
import { GeistSans } from "geist/font/sans";
|
|
5
5
|
import { GeistMono } from "geist/font/mono";
|
|
6
6
|
import { Footer } from "@/components/footer";
|
|
7
|
+
import { SearchProvider } from "@/components/SearchContext";
|
|
7
8
|
import docuConfig from "@/docu.json";
|
|
8
9
|
import "@docsearch/css";
|
|
9
10
|
import "@/styles/algolia.css";
|
|
10
|
-
import "@/styles/
|
|
11
|
+
import "@/styles/override.css";
|
|
11
12
|
import "@/styles/globals.css";
|
|
12
13
|
|
|
13
14
|
const { meta } = docuConfig;
|
|
@@ -85,11 +86,13 @@ export default function RootLayout({
|
|
|
85
86
|
enableSystem
|
|
86
87
|
disableTransitionOnChange
|
|
87
88
|
>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
<SearchProvider>
|
|
90
|
+
<Navbar id="main-navbar" />
|
|
91
|
+
<main id="main-content" className="sm:container mx-auto w-[90vw] h-auto scroll-smooth">
|
|
92
|
+
{children}
|
|
93
|
+
</main>
|
|
94
|
+
<Footer id="main-footer" />
|
|
95
|
+
</SearchProvider>
|
|
93
96
|
</ThemeProvider>
|
|
94
97
|
</body>
|
|
95
98
|
</html>
|
package/src/dist/app/page.tsx
CHANGED
|
@@ -11,7 +11,7 @@ export const metadata = getMetadata({
|
|
|
11
11
|
|
|
12
12
|
export default function Home() {
|
|
13
13
|
return (
|
|
14
|
-
<div className="flex flex-col items-center justify-center px-2 py-
|
|
14
|
+
<div className="flex flex-col items-center justify-center px-2 py-36 text-center">
|
|
15
15
|
<div className="w-full max-w-[800px] pb-8">
|
|
16
16
|
<h1 className="mb-4 text-2xl font-bold sm:text-5xl">DocuBook Starter Templates</h1>
|
|
17
17
|
<p className="mb-8 sm:text-xl text-muted-foreground">
|
|
@@ -8,6 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
|
|
8
8
|
import { Button } from "@/components/ui/button";
|
|
9
9
|
import * as LucideIcons from "lucide-react";
|
|
10
10
|
import { ChevronsUpDown, Check, type LucideIcon } from "lucide-react";
|
|
11
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
11
12
|
|
|
12
13
|
interface ContextPopoverProps {
|
|
13
14
|
className?: string;
|
|
@@ -62,7 +63,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
|
|
|
62
63
|
<Button
|
|
63
64
|
variant="ghost"
|
|
64
65
|
className={cn(
|
|
65
|
-
"w-full
|
|
66
|
+
"w-full cursor-pointer flex items-center justify-between font-semibold text-foreground px-2 py-4 border border-muted",
|
|
66
67
|
"hover:bg-transparent hover:text-foreground",
|
|
67
68
|
className
|
|
68
69
|
)}
|
|
@@ -74,7 +75,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
|
|
|
74
75
|
</span>
|
|
75
76
|
)}
|
|
76
77
|
<span className="truncate text-sm">
|
|
77
|
-
{activeRoute?.context?.title || activeRoute?.title ||
|
|
78
|
+
{activeRoute?.context?.title || activeRoute?.title || <Skeleton className="h-3.5 w-24" />}
|
|
78
79
|
</span>
|
|
79
80
|
</div>
|
|
80
81
|
<ChevronsUpDown className="h-4 w-4 text-foreground/50" />
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ArrowUpRight } from "lucide-react";
|
|
4
|
+
import GitHubButton from "@/components/Github";
|
|
5
|
+
import Anchor from "@/components/anchor";
|
|
6
|
+
import docuConfig from "@/docu.json";
|
|
7
|
+
|
|
8
|
+
interface NavbarItem {
|
|
9
|
+
title: string;
|
|
10
|
+
href: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { navbar } = docuConfig;
|
|
14
|
+
|
|
15
|
+
export function DocsNavbar() {
|
|
16
|
+
// Show all nav items
|
|
17
|
+
const navItems = navbar?.menu || [];
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="hidden lg:flex items-center justify-end gap-6 h-14 px-8 mt-2">
|
|
21
|
+
{/* Navigation Links */}
|
|
22
|
+
<div className="flex items-center gap-6 text-sm font-medium text-foreground/80">
|
|
23
|
+
{navItems.map((item: NavbarItem) => {
|
|
24
|
+
const isExternal = item.href.startsWith("http");
|
|
25
|
+
return (
|
|
26
|
+
<Anchor
|
|
27
|
+
key={`${item.title}-${item.href}`}
|
|
28
|
+
href={item.href}
|
|
29
|
+
absolute
|
|
30
|
+
activeClassName="text-primary dark:text-accent md:font-semibold font-medium"
|
|
31
|
+
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
|
32
|
+
target={isExternal ? "_blank" : undefined}
|
|
33
|
+
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
34
|
+
>
|
|
35
|
+
{item.title}
|
|
36
|
+
{isExternal && <ArrowUpRight className="w-3.5 h-3.5" />}
|
|
37
|
+
</Anchor>
|
|
38
|
+
);
|
|
39
|
+
})}
|
|
40
|
+
<GitHubButton />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default DocsNavbar;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ChevronDown, ChevronUp, PanelRight, MoreVertical } from "lucide-react"
|
|
4
|
+
import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTrigger } from "@/components/ui/sheet"
|
|
5
|
+
import DocsMenu from "@/components/DocsMenu"
|
|
6
|
+
import { ModeToggle } from "@/components/ThemeToggle"
|
|
7
|
+
import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
|
8
|
+
import ContextPopover from "@/components/ContextPopover"
|
|
9
|
+
import TocObserver from "./TocObserver"
|
|
10
|
+
import * as React from "react"
|
|
11
|
+
import { useRef, useMemo } from "react"
|
|
12
|
+
import { usePathname } from "next/navigation"
|
|
13
|
+
import { Button } from "./ui/button"
|
|
14
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
15
|
+
import { useActiveSection } from "@/hooks"
|
|
16
|
+
import { TocItem } from "@/lib/toc"
|
|
17
|
+
import Search from "@/components/SearchBox"
|
|
18
|
+
import GitHubButton from "@/components/Github"
|
|
19
|
+
import { NavMenu } from "@/components/navbar"
|
|
20
|
+
import {
|
|
21
|
+
DropdownMenu,
|
|
22
|
+
DropdownMenuContent,
|
|
23
|
+
DropdownMenuTrigger,
|
|
24
|
+
} from "@/components/ui/dropdown-menu"
|
|
25
|
+
|
|
26
|
+
interface MobTocProps {
|
|
27
|
+
tocs: TocItem[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const useClickOutside = (ref: React.RefObject<HTMLElement | null>, callback: () => void) => {
|
|
31
|
+
const handleClick = React.useCallback(
|
|
32
|
+
(event: MouseEvent) => {
|
|
33
|
+
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
34
|
+
callback()
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[ref, callback]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
document.addEventListener("mousedown", handleClick)
|
|
42
|
+
return () => {
|
|
43
|
+
document.removeEventListener("mousedown", handleClick)
|
|
44
|
+
}
|
|
45
|
+
}, [handleClick])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default function MobToc({ tocs }: MobTocProps) {
|
|
49
|
+
const pathname = usePathname()
|
|
50
|
+
const [isExpanded, setIsExpanded] = React.useState(false)
|
|
51
|
+
const tocRef = useRef<HTMLDivElement>(null)
|
|
52
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
53
|
+
|
|
54
|
+
// Use custom hooks
|
|
55
|
+
const { activeId, setActiveId } = useActiveSection(tocs)
|
|
56
|
+
|
|
57
|
+
// Only show on /docs pages
|
|
58
|
+
const isDocsPage = useMemo(() => pathname?.startsWith("/docs"), [pathname])
|
|
59
|
+
|
|
60
|
+
// Get title from path segment (last part of URL)
|
|
61
|
+
const pageTitle = pathname?.split("/").filter(Boolean).pop() || ""
|
|
62
|
+
|
|
63
|
+
const [mounted, setMounted] = React.useState(false)
|
|
64
|
+
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
setMounted(true)
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
// Toggle expanded state
|
|
70
|
+
const toggleExpanded = React.useCallback((e: React.MouseEvent) => {
|
|
71
|
+
e.stopPropagation()
|
|
72
|
+
setIsExpanded((prev) => !prev)
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
// Close TOC when clicking outside
|
|
76
|
+
useClickOutside(tocRef, () => {
|
|
77
|
+
if (isExpanded) {
|
|
78
|
+
setIsExpanded(false)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Handle body overflow when TOC is expanded
|
|
83
|
+
React.useEffect(() => {
|
|
84
|
+
if (isExpanded) {
|
|
85
|
+
document.body.style.overflow = "hidden"
|
|
86
|
+
} else {
|
|
87
|
+
document.body.style.overflow = ""
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return () => {
|
|
91
|
+
document.body.style.overflow = ""
|
|
92
|
+
}
|
|
93
|
+
}, [isExpanded])
|
|
94
|
+
|
|
95
|
+
// Don't render anything if not on docs page
|
|
96
|
+
if (!isDocsPage || !mounted) return null
|
|
97
|
+
|
|
98
|
+
const chevronIcon = isExpanded ? (
|
|
99
|
+
<ChevronUp className="text-muted-foreground h-4 w-4 shrink-0" />
|
|
100
|
+
) : (
|
|
101
|
+
<ChevronDown className="text-muted-foreground h-4 w-4 shrink-0" />
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<AnimatePresence>
|
|
106
|
+
<motion.div
|
|
107
|
+
ref={tocRef}
|
|
108
|
+
className="sticky top-0 z-50 -mx-4 -mt-4 mb-4 lg:hidden"
|
|
109
|
+
initial={{ y: -100, opacity: 0 }}
|
|
110
|
+
animate={{ y: 0, opacity: 1 }}
|
|
111
|
+
exit={{ y: -100, opacity: 0 }}
|
|
112
|
+
transition={{ duration: 0.2, ease: "easeInOut" }}
|
|
113
|
+
>
|
|
114
|
+
<div className="bg-background/95 w-full border-b border-muted shadow-sm backdrop-blur-sm dark:border-foreground/10 dark:bg-background">
|
|
115
|
+
<div className="p-2">
|
|
116
|
+
<div className="flex items-center gap-2">
|
|
117
|
+
<DropdownMenu>
|
|
118
|
+
<DropdownMenuTrigger asChild>
|
|
119
|
+
<Button variant="ghost" size="icon" className="h-8 w-8 shrink-0" aria-label="Navigation menu">
|
|
120
|
+
<MoreVertical className="h-5 w-5 text-muted-foreground" />
|
|
121
|
+
</Button>
|
|
122
|
+
</DropdownMenuTrigger>
|
|
123
|
+
<DropdownMenuContent align="start" className="flex flex-col gap-1 p-2 min-w-[160px]">
|
|
124
|
+
<NavMenu />
|
|
125
|
+
</DropdownMenuContent>
|
|
126
|
+
</DropdownMenu>
|
|
127
|
+
<Button
|
|
128
|
+
variant="ghost"
|
|
129
|
+
size="sm"
|
|
130
|
+
className="-mx-1 h-auto flex-1 justify-between rounded-md px-2 py-2 hover:bg-transparent hover:text-inherit"
|
|
131
|
+
onClick={toggleExpanded}
|
|
132
|
+
aria-label={isExpanded ? "Collapse table of contents" : "Expand table of contents"}
|
|
133
|
+
>
|
|
134
|
+
<div className="flex items-center gap-2">
|
|
135
|
+
<span className="text-sm font-medium capitalize">
|
|
136
|
+
{pageTitle || "On this page"}
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
{chevronIcon}
|
|
140
|
+
</Button>
|
|
141
|
+
<Search />
|
|
142
|
+
<Sheet>
|
|
143
|
+
<SheetTrigger asChild>
|
|
144
|
+
<Button variant="ghost" size="icon" className="hidden max-lg:flex">
|
|
145
|
+
<PanelRight className="h-6 w-6 shrink-0 text-muted-foreground" />
|
|
146
|
+
</Button>
|
|
147
|
+
</SheetTrigger>
|
|
148
|
+
<SheetContent className="w-full flex flex-col gap-4 px-0 lg:w-auto" side="right">
|
|
149
|
+
<DialogTitle className="sr-only">Navigation Menu</DialogTitle>
|
|
150
|
+
<DialogDescription className="sr-only">
|
|
151
|
+
Main navigation menu with links to different sections
|
|
152
|
+
</DialogDescription>
|
|
153
|
+
<SheetHeader>
|
|
154
|
+
<SheetClose className="px-4" asChild>
|
|
155
|
+
<div className="flex items-center justify-between">
|
|
156
|
+
<GitHubButton />
|
|
157
|
+
<div className="mr-8">
|
|
158
|
+
<ModeToggle />
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</SheetClose>
|
|
162
|
+
</SheetHeader>
|
|
163
|
+
<div className="flex flex-col gap-4 overflow-y-auto">
|
|
164
|
+
<div className="mx-2 space-y-2 px-5">
|
|
165
|
+
<ContextPopover />
|
|
166
|
+
<DocsMenu isSheet />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</SheetContent>
|
|
170
|
+
</Sheet>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<AnimatePresence>
|
|
174
|
+
{isExpanded && (
|
|
175
|
+
<motion.div
|
|
176
|
+
ref={contentRef}
|
|
177
|
+
className="-mx-1 mt-2 max-h-[60vh] overflow-y-auto px-1 pb-2"
|
|
178
|
+
initial={{ opacity: 0, height: 0 }}
|
|
179
|
+
animate={{ opacity: 1, height: "auto" }}
|
|
180
|
+
exit={{ opacity: 0, height: 0 }}
|
|
181
|
+
transition={{ duration: 0.2, ease: "easeInOut" }}
|
|
182
|
+
>
|
|
183
|
+
{tocs?.length ? (
|
|
184
|
+
<TocObserver data={tocs} activeId={activeId} onActiveIdChange={setActiveId} />
|
|
185
|
+
) : (
|
|
186
|
+
<p className="text-muted-foreground py-2 text-sm">No headings</p>
|
|
187
|
+
)}
|
|
188
|
+
</motion.div>
|
|
189
|
+
)}
|
|
190
|
+
</AnimatePresence>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</motion.div>
|
|
194
|
+
</AnimatePresence>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import docuConfig from "@/docu.json";
|
|
3
|
+
|
|
4
|
+
export default function GitHubButton() {
|
|
5
|
+
const { repository } = docuConfig;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<Link
|
|
9
|
+
href={repository.url}
|
|
10
|
+
target="_blank"
|
|
11
|
+
rel="noopener noreferrer"
|
|
12
|
+
className="inline-flex items-center rounded-full p-1 text-sm font-medium text-muted-foreground border no-underline hover:bg-muted/50 transition-colors"
|
|
13
|
+
aria-label="View on GitHub"
|
|
14
|
+
>
|
|
15
|
+
<svg
|
|
16
|
+
height="16"
|
|
17
|
+
width="16"
|
|
18
|
+
viewBox="0 0 16 16"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
className="fill-current"
|
|
21
|
+
>
|
|
22
|
+
<path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.32c-2.22.48-2.69-1.07-2.69-1.07-.36-.92-.89-1.17-.89-1.17-.73-.5.06-.49.06-.49.81.06 1.23.83 1.23.83.72 1.23 1.89.88 2.35.67.07-.52.28-.88.5-1.08-1.77-.2-3.64-.88-3.64-3.93 0-.87.31-1.58.82-2.14-.08-.2-.36-1.01.08-2.12 0 0 .67-.21 2.2.82a7.7 7.7 0 012.01-.27 7.7 7.7 0 012.01.27c1.53-1.03 2.2-.82 2.2-.82.44 1.11.16 1.92.08 2.12.51.56.82 1.27.82 2.14 0 3.06-1.87 3.73-3.65 3.93.29.25.54.73.54 1.48v2.2c0 .21.15.46.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z" />
|
|
23
|
+
</svg>
|
|
24
|
+
</Link>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -19,10 +19,14 @@ export function ScrollToTop({
|
|
|
19
19
|
const [isVisible, setIsVisible] = useState(false);
|
|
20
20
|
|
|
21
21
|
const checkScroll = useCallback(() => {
|
|
22
|
+
// Check local scroll container or document
|
|
23
|
+
const container = document.getElementById("scroll-container");
|
|
24
|
+
const scrollY = container ? container.scrollTop : window.scrollY;
|
|
25
|
+
|
|
22
26
|
// Calculate 50% of viewport height
|
|
23
27
|
const halfViewportHeight = window.innerHeight * 0.5;
|
|
24
28
|
// Check if scrolled past half viewport height (plus any offset)
|
|
25
|
-
const scrolledPastHalfViewport =
|
|
29
|
+
const scrolledPastHalfViewport = scrollY > (halfViewportHeight + offset);
|
|
26
30
|
|
|
27
31
|
// Only update state if it changes to prevent unnecessary re-renders
|
|
28
32
|
if (scrolledPastHalfViewport !== isVisible) {
|
|
@@ -42,21 +46,24 @@ export function ScrollToTop({
|
|
|
42
46
|
timeoutId = setTimeout(checkScroll, 100);
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
const container = document.getElementById("scroll-container") || window;
|
|
50
|
+
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
46
51
|
|
|
47
52
|
// Cleanup
|
|
48
53
|
return () => {
|
|
49
|
-
|
|
54
|
+
container.removeEventListener('scroll', handleScroll);
|
|
50
55
|
if (timeoutId) clearTimeout(timeoutId);
|
|
51
56
|
};
|
|
52
57
|
}, [checkScroll]);
|
|
53
58
|
|
|
54
59
|
const scrollToTop = useCallback((e: React.MouseEvent) => {
|
|
55
60
|
e.preventDefault();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
behavior: 'smooth'
|
|
59
|
-
}
|
|
61
|
+
const container = document.getElementById("scroll-container");
|
|
62
|
+
if (container) {
|
|
63
|
+
container.scrollTo({ top: 0, behavior: 'smooth' });
|
|
64
|
+
} else {
|
|
65
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
66
|
+
}
|
|
60
67
|
}, []);
|
|
61
68
|
|
|
62
69
|
if (!isVisible) return null;
|
|
@@ -75,11 +82,11 @@ export function ScrollToTop({
|
|
|
75
82
|
onClick={scrollToTop}
|
|
76
83
|
className={cn(
|
|
77
84
|
"inline-flex items-center text-sm text-muted-foreground hover:text-foreground",
|
|
78
|
-
"transition-all duration-200 hover:translate-y-
|
|
85
|
+
"transition-all duration-200 hover:translate-y-px"
|
|
79
86
|
)}
|
|
80
87
|
aria-label="Scroll to top"
|
|
81
88
|
>
|
|
82
|
-
{showIcon && <ArrowUpIcon className="mr-1 h-3.5 w-3.5
|
|
89
|
+
{showIcon && <ArrowUpIcon className="mr-1 h-3.5 w-3.5 shrink-0" />}
|
|
83
90
|
<span>Scroll to Top</span>
|
|
84
91
|
</Link>
|
|
85
92
|
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Dialog } from "@/components/ui/dialog"
|
|
4
|
+
import { SearchTrigger } from "@/components/SearchTrigger"
|
|
5
|
+
import { SearchModal } from "@/components/SearchModal"
|
|
6
|
+
import DocSearchComponent from "@/components/DocSearch"
|
|
7
|
+
import { useSearch } from "./SearchContext"
|
|
8
|
+
import { DialogTrigger } from "@/components/ui/dialog"
|
|
9
|
+
|
|
10
|
+
interface SearchProps {
|
|
11
|
+
/**
|
|
12
|
+
* Specify which search engine to use.
|
|
13
|
+
* @default 'default'
|
|
14
|
+
*/
|
|
15
|
+
type?: "default" | "algolia"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function Search({ type = "default" }: SearchProps) {
|
|
19
|
+
const { isOpen, setIsOpen } = useSearch()
|
|
20
|
+
|
|
21
|
+
if (type === "algolia") {
|
|
22
|
+
// Just render the component without passing any state props
|
|
23
|
+
return <DocSearchComponent />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Logic for 'default' search
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
30
|
+
<DialogTrigger asChild>
|
|
31
|
+
<SearchTrigger />
|
|
32
|
+
</DialogTrigger>
|
|
33
|
+
<SearchModal isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
34
|
+
</Dialog>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|