@docubook/create 2.3.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.
Files changed (84) hide show
  1. package/package.json +1 -1
  2. package/src/dist/app/docs/[[...slug]]/page.tsx +56 -63
  3. package/src/dist/app/docs/layout.tsx +11 -4
  4. package/src/dist/app/layout.tsx +10 -9
  5. package/src/dist/app/page.tsx +3 -22
  6. package/src/dist/components/{context-popover.tsx → ContextPopover.tsx} +4 -3
  7. package/src/dist/components/{docs-breadcrumb.tsx → DocsBreadcrumb.tsx} +1 -1
  8. package/src/dist/components/{docs-menu.tsx → DocsMenu.tsx} +2 -2
  9. package/src/dist/components/DocsNavbar.tsx +46 -0
  10. package/src/dist/components/DocsSidebar.tsx +196 -0
  11. package/src/dist/components/Github.tsx +26 -0
  12. package/src/dist/components/{scroll-to-top.tsx → ScrollToTop.tsx} +16 -9
  13. package/src/dist/components/SearchBox.tsx +37 -0
  14. package/src/dist/components/SearchContext.tsx +47 -0
  15. package/src/dist/components/SearchModal.tsx +2 -2
  16. package/src/dist/components/SearchTrigger.tsx +5 -5
  17. package/src/dist/components/Sponsor.tsx +2 -2
  18. package/src/dist/components/{theme-toggle.tsx → ThemeToggle.tsx} +10 -10
  19. package/src/dist/components/TocObserver.tsx +197 -0
  20. package/src/dist/components/footer.tsx +16 -12
  21. package/src/dist/components/leftbar.tsx +45 -73
  22. package/src/dist/components/markdown/AccordionGroupMdx.tsx +1 -1
  23. package/src/dist/components/markdown/AccordionMdx.tsx +4 -4
  24. package/src/dist/components/markdown/CardMdx.tsx +4 -4
  25. package/src/dist/components/markdown/FileTreeMdx.tsx +4 -4
  26. package/src/dist/components/markdown/ImageMdx.tsx +1 -1
  27. package/src/dist/components/markdown/ReleaseMdx.tsx +1 -1
  28. package/src/dist/components/markdown/TooltipsMdx.tsx +1 -1
  29. package/src/dist/components/navbar.tsx +130 -53
  30. package/src/dist/components/sublink.tsx +1 -1
  31. package/src/dist/components/toc.tsx +16 -14
  32. package/src/dist/components/typography.tsx +1 -1
  33. package/src/dist/components/ui/icon-cloud.tsx +353 -0
  34. package/src/dist/components/ui/scroll-area.tsx +2 -2
  35. package/src/dist/components/ui/sheet.tsx +4 -4
  36. package/src/dist/components/ui/toggle.tsx +3 -3
  37. package/src/dist/docs/components/accordion-group.mdx +68 -0
  38. package/src/dist/docs/components/accordion.mdx +59 -0
  39. package/src/dist/docs/components/button.mdx +40 -0
  40. package/src/dist/docs/components/card-group.mdx +47 -0
  41. package/src/dist/docs/components/card.mdx +68 -0
  42. package/src/dist/docs/components/code-block.mdx +67 -0
  43. package/src/dist/docs/components/custom.mdx +38 -0
  44. package/src/dist/docs/components/file-tree.mdx +109 -0
  45. package/src/dist/docs/components/image.mdx +37 -0
  46. package/src/dist/docs/components/index.mdx +9 -0
  47. package/src/dist/docs/components/keyboard.mdx +117 -0
  48. package/src/dist/docs/components/link.mdx +34 -0
  49. package/src/dist/docs/components/note.mdx +44 -0
  50. package/src/dist/docs/components/release-note.mdx +114 -0
  51. package/src/dist/docs/components/stepper.mdx +45 -0
  52. package/src/dist/docs/components/tables.mdx +35 -0
  53. package/src/dist/docs/components/tabs.mdx +72 -0
  54. package/src/dist/docs/components/tooltips.mdx +22 -0
  55. package/src/dist/docs/components/youtube.mdx +23 -0
  56. package/src/dist/docs/getting-started/introduction.mdx +18 -0
  57. package/src/dist/docs/getting-started/quick-start-guide.mdx +60 -0
  58. package/src/dist/docu.json +36 -33
  59. package/src/dist/eslint.config.mjs +17 -30
  60. package/src/dist/hooks/useActiveSection.ts +34 -32
  61. package/src/dist/hooks/useScrollPosition.ts +16 -14
  62. package/src/dist/lib/markdown.ts +32 -17
  63. package/src/dist/lib/utils.ts +1 -1
  64. package/src/dist/next.config.mjs +10 -11
  65. package/src/dist/package.json +1 -1
  66. package/src/dist/styles/algolia.css +11 -9
  67. package/src/dist/styles/{syntax.css → override.css} +82 -39
  68. package/src/dist/tailwind.config.ts +11 -110
  69. package/src/dist/components/GithubStart.tsx +0 -44
  70. package/src/dist/components/mob-toc.tsx +0 -134
  71. package/src/dist/components/search.tsx +0 -55
  72. package/src/dist/components/toc-observer.tsx +0 -254
  73. package/src/dist/docs/api-reference/delete/index.mdx +0 -63
  74. package/src/dist/docs/api-reference/fetch/index.mdx +0 -63
  75. package/src/dist/docs/api-reference/get/index.mdx +0 -63
  76. package/src/dist/docs/api-reference/post/index.mdx +0 -63
  77. package/src/dist/docs/changelog/index.mdx +0 -49
  78. package/src/dist/docs/getting-started/development/index.mdx +0 -92
  79. package/src/dist/docs/getting-started/introduction/index.mdx +0 -92
  80. package/src/dist/docs/getting-started/quick-start-guide/index.mdx +0 -92
  81. /package/src/dist/components/{edit-on-github.tsx → EditWithGithub.tsx} +0 -0
  82. /package/src/dist/components/{contexts/theme-provider.tsx → ThemeProvider.tsx} +0 -0
  83. /package/src/dist/{components/contexts/AccordionContext.ts → lib/accordion-context.ts} +0 -0
  84. /package/src/dist/lib/{routes-config.ts → routes.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/create",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "CLI to create DocuBook projects",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,46 +1,42 @@
1
- import { notFound } from "next/navigation";
2
- import { getDocsForSlug, getDocsTocs } from "@/lib/markdown";
3
- import DocsBreadcrumb from "@/components/docs-breadcrumb";
4
- import Pagination from "@/components/pagination";
5
- import Toc from "@/components/toc";
6
- import { Typography } from "@/components/typography";
7
- import EditThisPage from "@/components/edit-on-github";
8
- import { formatDate2 } from "@/lib/utils";
9
- import docuConfig from "@/docu.json";
10
- import MobToc from "@/components/mob-toc";
11
-
12
- const { meta } = docuConfig;
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
+
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,52 +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;
73
-
74
- const {
75
- slug = []
76
- } = params;
77
-
78
- const pathName = slug.join("/");
79
- const res = await getDocsForSlug(pathName);
68
+ const params = await props.params
80
69
 
81
- if (!res) notFound();
70
+ const { slug = [] } = params
82
71
 
83
- const { title, description, image: _image, date } = res.frontmatter;
72
+ const pathName = slug.join("/")
73
+ const res = await getDocsForSlug(pathName)
84
74
 
85
- // File path for edit link
86
- const filePath = `docs/${slug.join("/") || ""}/index.mdx`;
75
+ if (!res) notFound()
87
76
 
88
- 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)
89
80
 
90
81
  return (
91
- <div className="flex items-start gap-10">
92
- <div className="flex-[4.5] pt-5">
93
- <MobToc tocs={tocs} />
94
- <DocsBreadcrumb paths={slug} />
95
- <Typography>
96
- <h1 className="text-3xl !-mt-0.5">{title}</h1>
97
- <p className="-mt-4 text-muted-foreground text-[16.5px]">{description}</p>
98
- <div>{res.content}</div>
99
- <div
100
- className={`my-8 flex items-center border-b-2 border-dashed border-x-muted-foreground ${docuConfig.repository?.editLink ? "justify-between" : "justify-end"
101
- }`}
102
- >
103
- {docuConfig.repository?.editLink && <EditThisPage filePath={filePath} />}
104
- {date && (
105
- <p className="text-[13px] text-muted-foreground">
106
- Published on {formatDate2(date)}
107
- </p>
108
- )}
109
- </div>
110
- <Pagination pathname={pathName} />
111
- </Typography>
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} />
112
106
  </div>
113
- <Toc path={pathName} />
114
107
  </div>
115
- );
108
+ )
116
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 items-start gap-8">
10
- <Leftbar key="leftbar" />
11
- <div className="flex-[5.25] px-1">
12
- {children}
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
  );
@@ -1,14 +1,14 @@
1
1
  import type { Metadata } from "next";
2
- import { ThemeProvider } from "@/components/contexts/theme-provider";
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
- import { Toaster } from "@/components/ui/sonner";
9
9
  import "@docsearch/css";
10
10
  import "@/styles/algolia.css";
11
- import "@/styles/syntax.css";
11
+ import "@/styles/override.css";
12
12
  import "@/styles/globals.css";
13
13
 
14
14
  const { meta } = docuConfig;
@@ -86,12 +86,13 @@ export default function RootLayout({
86
86
  enableSystem
87
87
  disableTransitionOnChange
88
88
  >
89
- <Navbar />
90
- <main className="sm:container mx-auto w-[90vw] h-auto scroll-smooth">
91
- {children}
92
- </main>
93
- <Footer />
94
- <Toaster position="top-center" />
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>
95
96
  </ThemeProvider>
96
97
  </body>
97
98
  </html>
@@ -1,9 +1,7 @@
1
1
  import { buttonVariants } from "@/components/ui/button";
2
- import { page_routes } from "@/lib/routes-config";
3
- import { ArrowRightIcon, Terminal, AppWindow, FileText } from "lucide-react";
2
+ import { page_routes } from "@/lib/routes";
3
+ import { Terminal, AppWindow, FileText } from "lucide-react";
4
4
  import Link from "next/link";
5
- import { cn } from "@/lib/utils";
6
- import AnimatedShinyText from "@/components/ui/animated-shiny-text";
7
5
  import { getMetadata } from "@/app/layout";
8
6
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
9
7
 
@@ -13,24 +11,7 @@ export const metadata = getMetadata({
13
11
 
14
12
  export default function Home() {
15
13
  return (
16
- <div className="flex flex-col items-center justify-center px-2 py-8 text-center sm:py-36">
17
- <Link
18
- href="/docs/changelog"
19
- className="mb-5 sm:text-lg flex items-center gap-2 underline underline-offset-4 sm:-mt-12"
20
- >
21
- <div className="z-10 flex min-h-10 items-center justify-center max-[800px]:mt-10">
22
- <div
23
- className={cn(
24
- "group rounded-full border border-black/5 bg-black/5 text-base text-white transition-all ease-in hover:cursor-pointer hover:bg-accent dark:border-white/5 dark:bg-transparent dark:hover:bg-accent",
25
- )}
26
- >
27
- <AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-100 hover:duration-300 hover:dark:text-neutral-200">
28
- <span>🚀 Release v2.3.0</span>
29
- <ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
30
- </AnimatedShinyText>
31
- </div>
32
- </div>
33
- </Link>
14
+ <div className="flex flex-col items-center justify-center px-2 py-36 text-center">
34
15
  <div className="w-full max-w-[800px] pb-8">
35
16
  <h1 className="mb-4 text-2xl font-bold sm:text-5xl">DocuBook Starter Templates</h1>
36
17
  <p className="mb-8 sm:text-xl text-muted-foreground">
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { usePathname, useRouter } from "next/navigation";
4
4
  import { useState, useEffect } from "react";
5
- import { ROUTES, EachRoute } from "@/lib/routes-config";
5
+ import { ROUTES, EachRoute } from "@/lib/routes";
6
6
  import { cn } from "@/lib/utils";
7
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 max-w-[240px] cursor-pointer flex items-center justify-between font-semibold text-foreground px-0 pt-8",
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 || 'Select context'}
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" />
@@ -10,7 +10,7 @@ import { Fragment } from "react";
10
10
 
11
11
  export default function DocsBreadcrumb({ paths }: { paths: string[] }) {
12
12
  return (
13
- <div className="pb-5 max-lg:pt-12">
13
+ <div className="pb-5 max-lg:pt-6">
14
14
  <Breadcrumb>
15
15
  <BreadcrumbList>
16
16
  <BreadcrumbItem>
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { ROUTES, EachRoute } from "@/lib/routes-config";
3
+ import { ROUTES, EachRoute } from "@/lib/routes";
4
4
  import SubLink from "./sublink";
5
5
  import { usePathname } from "next/navigation";
6
6
  import { cn } from "@/lib/utils";
@@ -15,7 +15,7 @@ function getCurrentContext(path: string): string | undefined {
15
15
  if (!path.startsWith('/docs')) return undefined;
16
16
 
17
17
  // Extract the first segment after /docs/
18
- const match = path.match(/^\/docs\/([^\/]+)/);
18
+ const match = path.match(/^\/docs\/([^/]+)/);
19
19
  return match ? match[1] : undefined;
20
20
  }
21
21
 
@@ -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 = window.scrollY > (halfViewportHeight + offset);
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
- window.addEventListener('scroll', handleScroll, { passive: true });
49
+ const container = document.getElementById("scroll-container") || window;
50
+ container.addEventListener('scroll', handleScroll, { passive: true });
46
51
 
47
52
  // Cleanup
48
53
  return () => {
49
- window.removeEventListener('scroll', handleScroll);
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
- window.scrollTo({
57
- top: 0,
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-[-1px]"
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 flex-shrink-0" />}
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>