@docubook/create 2.4.0 → 2.5.1

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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/dist/app/docs/[[...slug]]/page.tsx +55 -59
  3. package/src/dist/app/docs/layout.tsx +11 -4
  4. package/src/dist/app/layout.tsx +10 -7
  5. package/src/dist/app/page.tsx +1 -1
  6. package/src/dist/components/{context-popover.tsx → ContextPopover.tsx} +3 -2
  7. package/src/dist/components/DocSearch.tsx +16 -15
  8. package/src/dist/components/{docs-breadcrumb.tsx → DocsBreadcrumb.tsx} +1 -1
  9. package/src/dist/components/DocsNavbar.tsx +46 -0
  10. package/src/dist/components/DocsSidebar.tsx +207 -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 +39 -0
  14. package/src/dist/components/SearchContext.tsx +47 -0
  15. package/src/dist/components/SearchModal.tsx +77 -79
  16. package/src/dist/components/SearchTrigger.tsx +20 -15
  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/AccordionContext.tsx +21 -0
  23. package/src/dist/components/markdown/AccordionGroupMdx.tsx +11 -22
  24. package/src/dist/components/markdown/AccordionMdx.tsx +58 -59
  25. package/src/dist/components/markdown/PreMdx.tsx +2 -2
  26. package/src/dist/components/navbar.tsx +130 -53
  27. package/src/dist/components/toc.tsx +16 -14
  28. package/src/dist/components/typography.tsx +1 -1
  29. package/src/dist/components/ui/icon-cloud.tsx +353 -0
  30. package/src/dist/components/ui/scroll-area.tsx +2 -2
  31. package/src/dist/components/ui/sheet.tsx +4 -4
  32. package/src/dist/components/ui/toggle.tsx +3 -3
  33. package/src/dist/docs/components/accordion-group.mdx +13 -12
  34. package/src/dist/docs/components/accordion.mdx +11 -14
  35. package/src/dist/docu.json +3 -0
  36. package/src/dist/hooks/useActiveSection.ts +45 -33
  37. package/src/dist/hooks/useScrollPosition.ts +16 -14
  38. package/src/dist/lib/search/algolia.ts +5 -0
  39. package/src/dist/lib/search/built-in.ts +43 -0
  40. package/src/dist/lib/search/config.ts +7 -0
  41. package/src/dist/lib/toc.ts +1 -0
  42. package/src/dist/lib/utils.ts +13 -54
  43. package/src/dist/package.json +1 -1
  44. package/src/dist/styles/algolia.css +12 -35
  45. package/src/dist/styles/{syntax.css → override.css} +82 -39
  46. package/src/dist/tailwind.config.ts +11 -110
  47. package/src/dist/components/AccordionContext.ts +0 -4
  48. package/src/dist/components/GithubStart.tsx +0 -44
  49. package/src/dist/components/mob-toc.tsx +0 -134
  50. package/src/dist/components/search.tsx +0 -55
  51. package/src/dist/components/toc-observer.tsx +0 -254
  52. /package/src/dist/components/{docs-menu.tsx → DocsMenu.tsx} +0 -0
  53. /package/src/dist/components/{edit-on-github.tsx → EditWithGithub.tsx} +0 -0
  54. /package/src/dist/components/{theme-provider.tsx → ThemeProvider.tsx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/create",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
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";
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 items-start gap-10">
89
- <div className="flex-[4.5] pt-5">
90
- <MobToc tocs={tocs} />
91
- <DocsBreadcrumb paths={slug} />
92
- <Typography>
93
- <h1 className="text-3xl -mt-0.5!">{title}</h1>
94
- <p className="-mt-4 text-muted-foreground text-[16.5px]">{description}</p>
95
- <div>{res.content}</div>
96
- <div
97
- className={`my-8 flex items-center border-b-2 border-dashed border-x-muted-foreground ${docuConfig.repository?.editLink ? "justify-between" : "justify-end"
98
- }`}
99
- >
100
- {docuConfig.repository?.editLink && <EditThisPage filePath={filePath} />}
101
- {date && (
102
- <p className="text-[13px] text-muted-foreground">
103
- Published on {formatDate2(date)}
104
- </p>
105
- )}
106
- </div>
107
- <Pagination pathname={pathName} />
108
- </Typography>
82
+ <div className="flex w-full flex-1 px-0 pb-4 lg:px-8 lg:pb-8 lg:h-[calc(100vh-4rem)]">
83
+ <div id="scroll-container" className="max-lg:scroll-p-54 bg-card dark:bg-card/20 border-muted-foreground/20 flex w-full flex-col items-start lg:h-full lg:rounded-xl rounded-b-3xl border shadow-md backdrop-blur-sm lg:flex-row lg: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} title={title} />
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 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,13 +1,14 @@
1
1
  import type { Metadata } from "next";
2
- import { ThemeProvider } from "@/components/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
9
  import "@docsearch/css";
9
10
  import "@/styles/algolia.css";
10
- import "@/styles/syntax.css";
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
- <Navbar />
89
- <main className="sm:container mx-auto w-[90vw] h-auto scroll-smooth">
90
- {children}
91
- </main>
92
- <Footer />
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>
@@ -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-8 text-center sm:py-36">
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 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" />
@@ -1,26 +1,27 @@
1
- "use client";
1
+ "use client"
2
2
 
3
- import React from "react";
4
- import { DocSearch } from "@docsearch/react";
3
+ import { DocSearch } from "@docsearch/react"
4
+ import { algoliaConfig } from "@/lib/search/algolia"
5
+ import { cn } from "@/lib/utils"
5
6
 
6
- export default function DocSearchComponent() {
7
- const appId = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_APP_ID;
8
- const apiKey = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_API_KEY;
9
- const indexName = process.env.NEXT_PUBLIC_ALGOLIA_DOCSEARCH_INDEX_NAME;
7
+ interface AlgoliaSearchProps {
8
+ className?: string
9
+ }
10
+
11
+ export default function AlgoliaSearch({ className }: AlgoliaSearchProps) {
12
+ const { appId, apiKey, indexName } = algoliaConfig
10
13
 
11
14
  if (!appId || !apiKey || !indexName) {
12
- console.error(
13
- "DocSearch credentials are not set in the environment variables."
14
- );
15
+ console.error("DocSearch credentials are not set in the environment variables.")
15
16
  return (
16
- <button className="text-sm text-muted-foreground" disabled>
17
+ <button className="text-muted-foreground text-sm" disabled>
17
18
  Search... (misconfigured)
18
19
  </button>
19
- );
20
+ )
20
21
  }
21
22
 
22
23
  return (
23
- <div className="docsearch">
24
+ <div className={cn("docsearch", className)}>
24
25
  <DocSearch
25
26
  appId={appId}
26
27
  apiKey={apiKey}
@@ -28,5 +29,5 @@ export default function DocSearchComponent() {
28
29
  placeholder="Type something to search..."
29
30
  />
30
31
  </div>
31
- );
32
- }
32
+ )
33
+ }
@@ -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>
@@ -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,207 @@
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
+ title?: string
29
+ }
30
+
31
+ const useClickOutside = (ref: React.RefObject<HTMLElement | null>, callback: () => void) => {
32
+ const handleClick = React.useCallback(
33
+ (event: MouseEvent) => {
34
+ if (ref.current && !ref.current.contains(event.target as Node)) {
35
+ callback()
36
+ }
37
+ },
38
+ [ref, callback]
39
+ )
40
+
41
+ React.useEffect(() => {
42
+ document.addEventListener("mousedown", handleClick)
43
+ return () => {
44
+ document.removeEventListener("mousedown", handleClick)
45
+ }
46
+ }, [handleClick])
47
+ }
48
+
49
+ export default function MobToc({ tocs, title }: MobTocProps) {
50
+ const pathname = usePathname()
51
+ const [isExpanded, setIsExpanded] = React.useState(false)
52
+ const tocRef = useRef<HTMLDivElement>(null)
53
+ const contentRef = useRef<HTMLDivElement>(null)
54
+
55
+ // Use custom hooks
56
+ const { activeId, setActiveId } = useActiveSection(tocs)
57
+
58
+ // Only show on /docs pages
59
+ const isDocsPage = useMemo(() => pathname?.startsWith("/docs"), [pathname])
60
+
61
+ // Get title from active section if available, otherwise document title
62
+ const activeSection = useMemo(() => {
63
+ return tocs.find((toc) => toc.href.slice(1) === activeId)
64
+ }, [tocs, activeId])
65
+
66
+ const displayTitle = activeSection?.text || title || "On this page"
67
+
68
+ const [mounted, setMounted] = React.useState(false)
69
+
70
+ React.useEffect(() => {
71
+ setMounted(true)
72
+ }, [])
73
+
74
+ // Toggle expanded state
75
+ const toggleExpanded = React.useCallback((e: React.MouseEvent) => {
76
+ e.stopPropagation()
77
+ setIsExpanded((prev) => !prev)
78
+ }, [])
79
+
80
+ // Close TOC when clicking outside
81
+ useClickOutside(tocRef, () => {
82
+ if (isExpanded) {
83
+ setIsExpanded(false)
84
+ }
85
+ })
86
+
87
+ // Handle body overflow when TOC is expanded
88
+ React.useEffect(() => {
89
+ if (isExpanded) {
90
+ document.body.style.overflow = "hidden"
91
+ } else {
92
+ document.body.style.overflow = ""
93
+ }
94
+
95
+ return () => {
96
+ document.body.style.overflow = ""
97
+ }
98
+ }, [isExpanded])
99
+
100
+ // Don't render anything if not on docs page
101
+ if (!isDocsPage || !mounted) return null
102
+
103
+ const chevronIcon = isExpanded ? (
104
+ <ChevronUp className="text-muted-foreground h-4 w-4 shrink-0" />
105
+ ) : (
106
+ <ChevronDown className="text-muted-foreground h-4 w-4 shrink-0" />
107
+ )
108
+
109
+ return (
110
+ <AnimatePresence>
111
+ <motion.div
112
+ ref={tocRef}
113
+ className="sticky top-0 z-50 -mx-4 -mt-4 mb-4 lg:hidden"
114
+ initial={{ y: -100, opacity: 0 }}
115
+ animate={{ y: 0, opacity: 1 }}
116
+ exit={{ y: -100, opacity: 0 }}
117
+ transition={{ duration: 0.2, ease: "easeInOut" }}
118
+ >
119
+ <div className="bg-background/95 border-muted dark:border-foreground/10 dark:bg-background w-full border-b shadow-sm backdrop-blur-sm">
120
+ <div className="p-2">
121
+ <div className="flex items-center gap-2">
122
+ <DropdownMenu>
123
+ <DropdownMenuTrigger asChild>
124
+ <Button
125
+ variant="ghost"
126
+ size="icon"
127
+ className="h-8 w-8 shrink-0"
128
+ aria-label="Navigation menu"
129
+ >
130
+ <MoreVertical className="text-muted-foreground h-5 w-5" />
131
+ </Button>
132
+ </DropdownMenuTrigger>
133
+ <DropdownMenuContent
134
+ align="start"
135
+ className="flex min-w-[160px] flex-col gap-1 p-2"
136
+ >
137
+ <NavMenu />
138
+ </DropdownMenuContent>
139
+ </DropdownMenu>
140
+ <Button
141
+ variant="ghost"
142
+ size="sm"
143
+ className="-mx-1 h-auto flex-1 justify-between rounded-md px-2 py-2 hover:bg-transparent hover:text-inherit"
144
+ onClick={toggleExpanded}
145
+ aria-label={isExpanded ? "Collapse table of contents" : "Expand table of contents"}
146
+ >
147
+ <div className="flex items-center gap-2">
148
+ <span className="text-sm font-medium capitalize">{displayTitle}</span>
149
+ </div>
150
+ {chevronIcon}
151
+ </Button>
152
+ <Search />
153
+ <Sheet>
154
+ <SheetTrigger asChild>
155
+ <Button variant="ghost" size="icon" className="hidden max-lg:flex">
156
+ <PanelRight className="text-muted-foreground h-6 w-6 shrink-0" />
157
+ </Button>
158
+ </SheetTrigger>
159
+ <SheetContent className="flex w-full flex-col gap-4 px-0 lg:w-auto" side="right">
160
+ <DialogTitle className="sr-only">Navigation Menu</DialogTitle>
161
+ <DialogDescription className="sr-only">
162
+ Main navigation menu with links to different sections
163
+ </DialogDescription>
164
+ <SheetHeader>
165
+ <SheetClose className="px-4" asChild>
166
+ <div className="flex items-center justify-between">
167
+ <GitHubButton />
168
+ <div className="mr-8">
169
+ <ModeToggle />
170
+ </div>
171
+ </div>
172
+ </SheetClose>
173
+ </SheetHeader>
174
+ <div className="flex flex-col gap-4 overflow-y-auto">
175
+ <div className="mx-2 space-y-2 px-5">
176
+ <ContextPopover />
177
+ <DocsMenu isSheet />
178
+ </div>
179
+ </div>
180
+ </SheetContent>
181
+ </Sheet>
182
+ </div>
183
+
184
+ <AnimatePresence>
185
+ {isExpanded && (
186
+ <motion.div
187
+ ref={contentRef}
188
+ className="-mx-1 mt-2 max-h-[60vh] overflow-y-auto px-1 pb-2"
189
+ initial={{ opacity: 0, height: 0 }}
190
+ animate={{ opacity: 1, height: "auto" }}
191
+ exit={{ opacity: 0, height: 0 }}
192
+ transition={{ duration: 0.2, ease: "easeInOut" }}
193
+ >
194
+ {tocs?.length ? (
195
+ <TocObserver data={tocs} activeId={activeId} onActiveIdChange={setActiveId} />
196
+ ) : (
197
+ <p className="text-muted-foreground py-2 text-sm">No headings</p>
198
+ )}
199
+ </motion.div>
200
+ )}
201
+ </AnimatePresence>
202
+ </div>
203
+ </div>
204
+ </motion.div>
205
+ </AnimatePresence>
206
+ )
207
+ }
@@ -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>