@docubook/create 1.9.0 → 1.11.2

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 (129) hide show
  1. package/README.md +1 -3
  2. package/package.json +7 -7
  3. package/src/cli/program.js +32 -0
  4. package/src/cli/promptHandler.js +73 -0
  5. package/src/dist/LICENSE +21 -0
  6. package/src/dist/README.md +37 -0
  7. package/src/dist/app/docs/[[...slug]]/page.tsx +105 -0
  8. package/src/dist/app/docs/layout.tsx +16 -0
  9. package/src/dist/app/error.tsx +44 -0
  10. package/src/dist/app/layout.tsx +96 -0
  11. package/src/dist/app/not-found.tsx +19 -0
  12. package/src/dist/app/page.tsx +96 -0
  13. package/src/dist/components/GithubStart.tsx +44 -0
  14. package/src/dist/components/Sponsor.tsx +69 -0
  15. package/src/dist/components/anchor.tsx +84 -0
  16. package/src/dist/components/contexts/theme-provider.tsx +9 -0
  17. package/src/dist/components/docs-breadcrumb.tsx +47 -0
  18. package/src/dist/components/docs-menu.tsx +45 -0
  19. package/src/dist/components/edit-on-github.tsx +33 -0
  20. package/src/dist/components/footer.tsx +85 -0
  21. package/src/dist/components/leftbar.tsx +95 -0
  22. package/src/dist/components/markdown/AccordionMdx.tsx +47 -0
  23. package/src/dist/components/markdown/ButtonMdx.tsx +52 -0
  24. package/src/dist/components/markdown/CardGroupMdx.tsx +28 -0
  25. package/src/dist/components/markdown/CardMdx.tsx +41 -0
  26. package/src/dist/components/markdown/CopyMdx.tsx +33 -0
  27. package/src/dist/components/markdown/ImageMdx.tsx +25 -0
  28. package/src/dist/components/markdown/KeyboardMdx.tsx +102 -0
  29. package/src/dist/components/markdown/LinkMdx.tsx +14 -0
  30. package/src/dist/components/markdown/NoteMdx.tsx +52 -0
  31. package/src/dist/components/markdown/OutletMdx.tsx +29 -0
  32. package/src/dist/components/markdown/PreMdx.tsx +19 -0
  33. package/src/dist/components/markdown/ReleaseMdx.tsx +109 -0
  34. package/src/dist/components/markdown/StepperMdx.tsx +41 -0
  35. package/src/dist/components/markdown/TooltipsMdx.tsx +28 -0
  36. package/src/dist/components/markdown/YoutubeMdx.tsx +22 -0
  37. package/src/dist/components/markdown/mdx-provider.tsx +29 -0
  38. package/src/dist/components/mob-toc.tsx +128 -0
  39. package/src/dist/components/navbar.tsx +87 -0
  40. package/src/dist/components/pagination.tsx +49 -0
  41. package/src/dist/components/scroll-to-top.tsx +86 -0
  42. package/src/dist/components/search.tsx +214 -0
  43. package/src/dist/components/sublink.tsx +133 -0
  44. package/src/dist/components/theme-toggle.tsx +71 -0
  45. package/src/dist/components/toc-observer.tsx +264 -0
  46. package/src/dist/components/toc.tsx +27 -0
  47. package/src/dist/components/typography.tsx +9 -0
  48. package/src/dist/components/ui/accordion.tsx +58 -0
  49. package/src/dist/components/ui/animated-shiny-text.tsx +40 -0
  50. package/src/dist/components/ui/aurora.tsx +45 -0
  51. package/src/dist/components/ui/avatar.tsx +50 -0
  52. package/src/dist/components/ui/badge.tsx +37 -0
  53. package/src/dist/components/ui/breadcrumb.tsx +115 -0
  54. package/src/dist/components/ui/button.tsx +57 -0
  55. package/src/dist/components/ui/card.tsx +76 -0
  56. package/src/dist/components/ui/collapsible.tsx +11 -0
  57. package/src/dist/components/ui/command.tsx +153 -0
  58. package/src/dist/components/ui/dialog.tsx +124 -0
  59. package/src/dist/components/ui/dropdown-menu.tsx +200 -0
  60. package/src/dist/components/ui/icon-cloud.tsx +324 -0
  61. package/src/dist/components/ui/input.tsx +25 -0
  62. package/src/dist/components/ui/interactive-hover-button.tsx +35 -0
  63. package/src/dist/components/ui/popover.tsx +33 -0
  64. package/src/dist/components/ui/scroll-area.tsx +48 -0
  65. package/src/dist/components/ui/separator.tsx +30 -0
  66. package/src/dist/components/ui/sheet.tsx +140 -0
  67. package/src/dist/components/ui/shine-border.tsx +64 -0
  68. package/src/dist/components/ui/skeleton.tsx +15 -0
  69. package/src/dist/components/ui/sonner.tsx +31 -0
  70. package/src/dist/components/ui/table.tsx +117 -0
  71. package/src/dist/components/ui/tabs.tsx +55 -0
  72. package/src/dist/components/ui/toggle-group.tsx +61 -0
  73. package/src/dist/components/ui/toggle.tsx +46 -0
  74. package/src/dist/components.json +17 -0
  75. package/src/dist/contents/docs/getting-started/changelog/index.mdx +512 -0
  76. package/src/dist/contents/docs/getting-started/components/accordion/index.mdx +72 -0
  77. package/src/dist/contents/docs/getting-started/components/button/index.mdx +42 -0
  78. package/src/dist/contents/docs/getting-started/components/card/index.mdx +70 -0
  79. package/src/dist/contents/docs/getting-started/components/card-group/index.mdx +49 -0
  80. package/src/dist/contents/docs/getting-started/components/code-block/index.mdx +41 -0
  81. package/src/dist/contents/docs/getting-started/components/custom/index.mdx +38 -0
  82. package/src/dist/contents/docs/getting-started/components/image/index.mdx +37 -0
  83. package/src/dist/contents/docs/getting-started/components/index.mdx +9 -0
  84. package/src/dist/contents/docs/getting-started/components/keyboard/index.mdx +117 -0
  85. package/src/dist/contents/docs/getting-started/components/link/index.mdx +34 -0
  86. package/src/dist/contents/docs/getting-started/components/note/index.mdx +46 -0
  87. package/src/dist/contents/docs/getting-started/components/release-note/index.mdx +130 -0
  88. package/src/dist/contents/docs/getting-started/components/stepper/index.mdx +47 -0
  89. package/src/dist/contents/docs/getting-started/components/tabs/index.mdx +70 -0
  90. package/src/dist/contents/docs/getting-started/components/tooltips/index.mdx +22 -0
  91. package/src/dist/contents/docs/getting-started/components/youtube/index.mdx +21 -0
  92. package/src/dist/contents/docs/getting-started/customize/index.mdx +94 -0
  93. package/src/dist/contents/docs/getting-started/installation/index.mdx +84 -0
  94. package/src/dist/contents/docs/getting-started/introduction/index.mdx +50 -0
  95. package/src/dist/contents/docs/getting-started/project-structure/index.mdx +87 -0
  96. package/src/dist/contents/docs/getting-started/quick-start-guide/index.mdx +127 -0
  97. package/src/dist/docu.json +100 -0
  98. package/src/dist/hooks/index.ts +2 -0
  99. package/src/dist/hooks/useActiveSection.ts +68 -0
  100. package/src/dist/hooks/useScrollPosition.ts +28 -0
  101. package/src/dist/lib/markdown.ts +244 -0
  102. package/src/dist/lib/routes-config.ts +28 -0
  103. package/src/dist/lib/toc.ts +9 -0
  104. package/src/dist/lib/utils.ts +80 -0
  105. package/src/dist/next-env.d.ts +5 -0
  106. package/src/dist/next.config.mjs +14 -0
  107. package/src/dist/package.json +58 -0
  108. package/src/dist/postcss.config.js +6 -0
  109. package/src/dist/public/favicon.ico +0 -0
  110. package/src/dist/public/images/docu.svg +6 -0
  111. package/src/dist/public/images/example-img.png +0 -0
  112. package/src/dist/public/images/img-playground.png +0 -0
  113. package/src/dist/public/images/new-editor.png +0 -0
  114. package/src/dist/public/images/og-image.png +0 -0
  115. package/src/dist/public/images/release-note.png +0 -0
  116. package/src/dist/public/images/snippet.png +0 -0
  117. package/src/dist/public/images/vercel.png +0 -0
  118. package/src/dist/public/images/view-changelog.png +0 -0
  119. package/src/dist/styles/editor.css +57 -0
  120. package/src/dist/styles/globals.css +156 -0
  121. package/src/dist/styles/syntax.css +100 -0
  122. package/src/dist/tailwind.config.ts +112 -0
  123. package/src/dist/tsconfig.json +26 -0
  124. package/src/index.js +19 -0
  125. package/src/installer/projectInstaller.js +125 -0
  126. package/src/utils/display.js +83 -0
  127. package/src/utils/logger.js +11 -0
  128. package/src/utils/packageManager.js +54 -0
  129. package/create.js +0 -223
@@ -0,0 +1,25 @@
1
+ import { ComponentProps } from "react";
2
+ import NextImage from "next/image";
3
+
4
+ type Height = ComponentProps<typeof NextImage>["height"];
5
+ type Width = ComponentProps<typeof NextImage>["width"];
6
+
7
+ export default function Image({
8
+ src,
9
+ alt = "alt",
10
+ width = 800,
11
+ height = 350,
12
+ ...props
13
+ }: ComponentProps<"img">) {
14
+ if (!src) return null;
15
+ return (
16
+ <NextImage
17
+ src={src}
18
+ alt={alt}
19
+ width={width as Width}
20
+ height={height as Height}
21
+ quality={40}
22
+ {...props}
23
+ />
24
+ );
25
+ }
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+
3
+ // Map of special keys to their Mac symbols
4
+ const macKeyMap: Record<string, string> = {
5
+ command: '⌘',
6
+ cmd: '⌘',
7
+ option: '⌥',
8
+ alt: '⌥',
9
+ shift: '⇧',
10
+ ctrl: '⌃',
11
+ control: '⌃',
12
+ tab: '⇥',
13
+ caps: '⇪',
14
+ enter: '⏎',
15
+ return: '⏎',
16
+ delete: '⌫',
17
+ escape: '⎋',
18
+ esc: '⎋',
19
+ up: '↑',
20
+ down: '↓',
21
+ left: '←',
22
+ right: '→',
23
+ space: '␣',
24
+ };
25
+
26
+ // Map of special keys to their Windows display text
27
+ const windowsKeyMap: Record<string, string> = {
28
+ command: 'Win',
29
+ cmd: 'Win',
30
+ option: 'Alt',
31
+ alt: 'Alt',
32
+ ctrl: 'Ctrl',
33
+ control: 'Ctrl',
34
+ delete: 'Del',
35
+ escape: 'Esc',
36
+ esc: 'Esc',
37
+ enter: 'Enter',
38
+ return: 'Enter',
39
+ tab: 'Tab',
40
+ caps: 'Caps',
41
+ shift: 'Shift',
42
+ space: 'Space',
43
+ up: '↑',
44
+ down: '↓',
45
+ left: '←',
46
+ right: '→',
47
+ };
48
+
49
+ export interface KbdProps extends React.HTMLAttributes<HTMLElement> {
50
+ /** The key to display (e.g., 'cmd', 'ctrl', 'a') */
51
+ show?: string;
52
+ /** Platform style - 'window' or 'mac' */
53
+ type?: 'window' | 'mac';
54
+ /** Custom content to display (overrides automatic rendering) */
55
+ children?: React.ReactNode;
56
+ }
57
+
58
+ const KbdComponent: React.FC<KbdProps> = ({
59
+ show: keyProp,
60
+ type = 'window',
61
+ children,
62
+ ...props
63
+ }) => {
64
+ // Get the display text based on the key and type
65
+ const getKeyDisplay = (): React.ReactNode => {
66
+ if (!keyProp || typeof keyProp !== 'string') return null;
67
+
68
+ const lowerKey = keyProp.toLowerCase();
69
+
70
+ // For Mac type, return the symbol if it exists
71
+ if (type === 'mac') {
72
+ return macKeyMap[lowerKey] || keyProp;
73
+ }
74
+
75
+ // For Windows, return the formatted key if it exists, otherwise capitalize the first letter
76
+ return windowsKeyMap[lowerKey] || (keyProp.charAt(0).toUpperCase() + keyProp.slice(1));
77
+ };
78
+
79
+ // Determine what to render
80
+ const renderContent = () => {
81
+ // If children are provided, always use them
82
+ if (children !== undefined && children !== null && children !== '') {
83
+ return children;
84
+ }
85
+ // Otherwise use the generated display
86
+ return getKeyDisplay() || keyProp || '';
87
+ };
88
+
89
+ return (
90
+ <kbd
91
+ className="inline-flex items-center justify-center px-2 py-1 mx-0.5 text-xs font-mono font-medium text-gray-800 bg-gray-100 border border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
92
+ {...props}
93
+ >
94
+ {renderContent()}
95
+ </kbd>
96
+ );
97
+ };
98
+
99
+ // Export the component
100
+ export const Kbd = KbdComponent;
101
+ // Default export for backward compatibility
102
+ export default KbdComponent;
@@ -0,0 +1,14 @@
1
+ import NextLink from "next/link";
2
+ import { ComponentProps } from "react";
3
+
4
+ export default function Link({ href, ...props }: ComponentProps<"a">) {
5
+ if (!href) return null;
6
+ return (
7
+ <NextLink
8
+ href={href}
9
+ {...props}
10
+ target="_blank"
11
+ rel="noopener noreferrer"
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,52 @@
1
+ import { cn } from "@/lib/utils";
2
+ import clsx from "clsx";
3
+ import { PropsWithChildren } from "react";
4
+ import {
5
+ Info,
6
+ AlertTriangle,
7
+ ShieldAlert,
8
+ CheckCircle,
9
+ } from "lucide-react";
10
+
11
+ type NoteProps = PropsWithChildren & {
12
+ title?: string;
13
+ type?: "note" | "danger" | "warning" | "success";
14
+ };
15
+
16
+ const iconMap = {
17
+ note: <Info size={16} className="text-blue-500" />,
18
+ danger: <ShieldAlert size={16} className="text-red-500" />,
19
+ warning: <AlertTriangle size={16} className="text-orange-500" />,
20
+ success: <CheckCircle size={16} className="text-green-500" />,
21
+ };
22
+
23
+ export default function Note({
24
+ children,
25
+ title = "Note",
26
+ type = "note",
27
+ }: NoteProps) {
28
+ const noteClassNames = clsx({
29
+ "dark:bg-stone-950/25 bg-stone-50": type === "note",
30
+ "dark:bg-red-950 bg-red-100 border-red-200 dark:border-red-900":
31
+ type === "danger",
32
+ "dark:bg-orange-950 bg-orange-100 border-orange-200 dark:border-orange-900":
33
+ type === "warning",
34
+ "dark:bg-green-950 bg-green-100 border-green-200 dark:border-green-900":
35
+ type === "success",
36
+ });
37
+
38
+ return (
39
+ <div
40
+ className={cn(
41
+ "border rounded-md px-5 pb-0.5 mt-5 mb-7 text-sm tracking-wide",
42
+ noteClassNames
43
+ )}
44
+ >
45
+ <div className="flex items-center gap-2 font-bold -mb-2.5 pt-6">
46
+ {iconMap[type]}
47
+ <span className="text-base">{title}:</span>
48
+ </div>
49
+ {children}
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,29 @@
1
+ import { BaseMdxFrontmatter, getAllChilds } from "@/lib/markdown";
2
+ import Link from "next/link";
3
+
4
+ export default async function Outlet({ path }: { path: string }) {
5
+ if (!path) throw new Error("path not provided");
6
+ const output = await getAllChilds(path);
7
+
8
+ return (
9
+ <div className="grid md:grid-cols-2 gap-5">
10
+ {output.map((child) => (
11
+ <ChildCard {...child} key={child.title} />
12
+ ))}
13
+ </div>
14
+ );
15
+ }
16
+
17
+ type ChildCardProps = BaseMdxFrontmatter & { href: string };
18
+
19
+ function ChildCard({ description, href, title }: ChildCardProps) {
20
+ return (
21
+ <Link
22
+ href={href}
23
+ className="border rounded-md p-4 no-underline flex flex-col gap-0.5"
24
+ >
25
+ <h4 className="!my-0">{title}</h4>
26
+ <p className="text-sm text-muted-foreground !my-0">{description}</p>
27
+ </Link>
28
+ );
29
+ }
@@ -0,0 +1,19 @@
1
+ import { ComponentProps } from "react";
2
+ import Copy from "./CopyMdx";
3
+
4
+ export default function Pre({
5
+ children,
6
+ raw,
7
+ ...rest
8
+ }: ComponentProps<"pre"> & { raw?: string }) {
9
+ return (
10
+ <div className="my-5 relative">
11
+ <div className="absolute top-3 right-2.5 z-10 sm:block hidden">
12
+ <Copy content={raw!} />
13
+ </div>
14
+ <div className="relative">
15
+ <pre {...rest}>{children}</pre>
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,109 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+ import { cn } from '@/lib/utils';
3
+ import { PlusCircle, Wrench, Zap, AlertTriangle, XCircle } from 'lucide-react';
4
+
5
+ interface ReleaseProps extends PropsWithChildren {
6
+ version: string;
7
+ title: string;
8
+ date?: string;
9
+ }
10
+
11
+ function Release({ version, title, date, children }: ReleaseProps) {
12
+
13
+ return (
14
+ <div className="mb-16 group">
15
+ <div className="mb-6">
16
+ <div className="flex items-center gap-3 mb-2">
17
+ <div className="bg-primary/10 text-primary border-2 border-primary/20 rounded-full px-4 py-1.5 text-base font-medium">
18
+ v{version}
19
+ </div>
20
+ {date && (
21
+ <div className="text-muted-foreground text-sm">
22
+ {new Date(date).toLocaleDateString('en-US', {
23
+ year: 'numeric',
24
+ month: 'long',
25
+ day: 'numeric'
26
+ })}
27
+ </div>
28
+ )}
29
+ </div>
30
+ <h2 className="text-2xl font-bold text-foreground/90 mb-3">
31
+ {title}
32
+ </h2>
33
+ </div>
34
+ <div className="space-y-8">
35
+ {children}
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ interface ChangesProps extends PropsWithChildren {
42
+ type: 'added' | 'fixed' | 'improved' | 'deprecated' | 'removed';
43
+ }
44
+
45
+ const typeConfig = {
46
+ added: {
47
+ label: 'Added',
48
+ className: 'bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300',
49
+ icon: PlusCircle,
50
+ },
51
+ fixed: {
52
+ label: 'Fixed',
53
+ className: 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300',
54
+ icon: Wrench,
55
+ },
56
+ improved: {
57
+ label: 'Improved',
58
+ className: 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300',
59
+ icon: Zap,
60
+ },
61
+ deprecated: {
62
+ label: 'Deprecated',
63
+ className: 'bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300',
64
+ icon: AlertTriangle,
65
+ },
66
+ removed: {
67
+ label: 'Removed',
68
+ className: 'bg-pink-100 dark:bg-pink-900/50 text-pink-700 dark:text-pink-300',
69
+ icon: XCircle,
70
+ },
71
+ } as const;
72
+
73
+ function Changes({ type, children }: ChangesProps) {
74
+ const config = typeConfig[type] || typeConfig.added;
75
+
76
+ return (
77
+ <div className="space-y-3 mb-8">
78
+ <div className="flex items-center gap-2">
79
+ <div className={cn("px-3 py-1 rounded-full text-sm font-medium flex items-center gap-1.5", config.className)}>
80
+ <config.icon className="h-3.5 w-3.5" />
81
+ <span>{config.label}</span>
82
+ </div>
83
+ </div>
84
+ <ul className="list-none pl-0 space-y-2 text-foreground/80">
85
+ {React.Children.map(children, (child, index) => {
86
+ // Jika teks dimulai dengan - atau *, hapus karakter tersebut
87
+ const processedChild = typeof child === 'string'
88
+ ? child.trim().replace(/^[-*]\s+/, '')
89
+ : child;
90
+
91
+ return (
92
+ <li key={index} className="leading-relaxed">
93
+ {processedChild}
94
+ </li>
95
+ );
96
+ })}
97
+ </ul>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ export { Release, Changes };
103
+
104
+ const ReleaseMdx = {
105
+ Release,
106
+ Changes
107
+ };
108
+
109
+ export default ReleaseMdx;
@@ -0,0 +1,41 @@
1
+ import { cn } from "@/lib/utils";
2
+ import clsx from "clsx";
3
+ import { Children, PropsWithChildren } from "react";
4
+
5
+ export function Stepper({ children }: PropsWithChildren) {
6
+ const length = Children.count(children);
7
+
8
+ return (
9
+ <div className="flex flex-col">
10
+ {Children.map(children, (child, index) => {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "border-l pl-9 ml-3 relative",
15
+ clsx({
16
+ "pb-5 ": index < length - 1,
17
+ })
18
+ )}
19
+ >
20
+ <div className="bg-muted w-8 h-8 text-xs font-medium rounded-md border flex items-center justify-center absolute -left-4 font-code">
21
+ {index + 1}
22
+ </div>
23
+ {child}
24
+ </div>
25
+ );
26
+ })}
27
+ </div>
28
+ );
29
+ }
30
+
31
+ export function StepperItem({
32
+ children,
33
+ title,
34
+ }: PropsWithChildren & { title?: string }) {
35
+ return (
36
+ <div className="pt-0.5">
37
+ <h4 className="mt-0">{title}</h4>
38
+ <div>{children}</div>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import React, { useState } from "react";
3
+
4
+ interface TooltipProps {
5
+ text: string;
6
+ tip: string;
7
+ }
8
+
9
+ const Tooltip: React.FC<TooltipProps> = ({ text, tip }) => {
10
+ const [visible, setVisible] = useState(false);
11
+
12
+ return (
13
+ <span
14
+ className="relative inline-block cursor-pointer underline decoration-dotted text-blue-500"
15
+ onMouseEnter={() => setVisible(true)}
16
+ onMouseLeave={() => setVisible(false)}
17
+ >
18
+ {text}
19
+ {visible && (
20
+ <span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-max max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl bg-background text-foreground text-sm p-2 rounded shadow-md break-words text-center outline outline-1 outline-offset-2">
21
+ {tip}
22
+ </span>
23
+ )}
24
+ </span>
25
+ );
26
+ };
27
+
28
+ export default Tooltip;
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+
3
+ interface YoutubeProps {
4
+ videoId: string;
5
+ className?: string;
6
+ }
7
+
8
+ const Youtube: React.FC<YoutubeProps> = ({ videoId, className }) => {
9
+ return (
10
+ <div className={`youtube ${className || ""}`}>
11
+ <iframe
12
+ src={`https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1&showinfo=0&autohide=1&controls=1`}
13
+ title="YouTube video player"
14
+ frameBorder="0"
15
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
16
+ allowFullScreen
17
+ ></iframe>
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default Youtube;
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { MDXRemote, MDXRemoteProps } from 'next-mdx-remote/rsc';
4
+ import { Kbd } from './KeyboardMdx';
5
+
6
+ // Define components mapping
7
+ const components = {
8
+ // Keyboard components
9
+ Kbd: Kbd as React.ComponentType<React.HTMLAttributes<HTMLElement> & { type?: 'window' | 'mac' }>,
10
+ kbd: Kbd as React.ComponentType<React.HTMLAttributes<HTMLElement> & { type?: 'window' | 'mac' }>,
11
+ };
12
+
13
+ interface MDXProviderWrapperProps {
14
+ source: string;
15
+ }
16
+
17
+ export function MDXProviderWrapper({ source }: MDXProviderWrapperProps) {
18
+ return (
19
+ <div className="prose dark:prose-invert max-w-none">
20
+ <MDXRemote
21
+ source={source}
22
+ components={components}
23
+ options={{
24
+ parseFrontmatter: true,
25
+ }}
26
+ />
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,128 @@
1
+ "use client";
2
+
3
+ import { List, ChevronDown, ChevronUp } from "lucide-react";
4
+ import TocObserver from "./toc-observer";
5
+ import * as React from "react";
6
+ import { useRef, useMemo } from "react";
7
+ import { usePathname } from "next/navigation";
8
+ import { Button } from "./ui/button";
9
+ import { motion, AnimatePresence } from "framer-motion";
10
+ import { useScrollPosition, useActiveSection } from "@/hooks";
11
+ import { TocItem } from "@/lib/toc";
12
+
13
+ interface MobTocProps {
14
+ tocs: TocItem[];
15
+ }
16
+
17
+ const useClickOutside = (ref: React.RefObject<HTMLElement>, callback: () => void) => {
18
+ const handleClick = React.useCallback((event: MouseEvent) => {
19
+ if (ref.current && !ref.current.contains(event.target as Node)) {
20
+ callback();
21
+ }
22
+ }, [ref, callback]);
23
+
24
+ React.useEffect(() => {
25
+ document.addEventListener('mousedown', handleClick);
26
+ return () => {
27
+ document.removeEventListener('mousedown', handleClick);
28
+ };
29
+ }, [handleClick]);
30
+ };
31
+
32
+ export default function MobToc({ tocs }: MobTocProps) {
33
+ const pathname = usePathname();
34
+ const [isExpanded, setIsExpanded] = React.useState(false);
35
+ const tocRef = useRef<HTMLDivElement>(null);
36
+ const contentRef = useRef<HTMLDivElement>(null);
37
+
38
+ // Use custom hooks
39
+ const { activeId, setActiveId } = useActiveSection(tocs);
40
+
41
+ // Only show on /docs pages
42
+ const isDocsPage = useMemo(() => pathname?.startsWith('/docs'), [pathname]);
43
+
44
+ // Toggle expanded state
45
+ const toggleExpanded = React.useCallback((e: React.MouseEvent) => {
46
+ e.stopPropagation();
47
+ setIsExpanded(prev => !prev);
48
+ }, []);
49
+
50
+ // Close TOC when clicking outside
51
+ useClickOutside(tocRef, () => {
52
+ if (isExpanded) {
53
+ setIsExpanded(false);
54
+ }
55
+ });
56
+
57
+ // Handle body overflow when TOC is expanded
58
+ React.useEffect(() => {
59
+ if (isExpanded) {
60
+ document.body.style.overflow = 'hidden';
61
+ } else {
62
+ document.body.style.overflow = '';
63
+ }
64
+
65
+ return () => {
66
+ document.body.style.overflow = '';
67
+ };
68
+ }, [isExpanded]);
69
+
70
+ // Don't render anything if not on docs page or no TOC items
71
+ if (!isDocsPage || !tocs?.length) return null;
72
+
73
+ const chevronIcon = isExpanded ? (
74
+ <ChevronUp className="w-4 h-4 text-muted-foreground flex-shrink-0" />
75
+ ) : (
76
+ <ChevronDown className="w-4 h-4 text-muted-foreground flex-shrink-0" />
77
+ );
78
+
79
+ return (
80
+ <AnimatePresence>
81
+ <motion.div
82
+ ref={tocRef}
83
+ className="lg:hidden fixed top-16 left-0 right-0 z-50"
84
+ initial={{ y: -100, opacity: 0 }}
85
+ animate={{ y: 0, opacity: 1 }}
86
+ exit={{ y: -100, opacity: 0 }}
87
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
88
+ >
89
+ <div className="w-full bg-background/95 backdrop-blur-sm border-b border-stone-200 dark:border-stone-800 shadow-sm">
90
+ <div className="sm:px-8 px-4 py-2">
91
+ <Button
92
+ variant="ghost"
93
+ size="sm"
94
+ className="w-full justify-between h-auto py-2 px-2 -mx-1 rounded-md hover:bg-transparent hover:text-inherit"
95
+ onClick={toggleExpanded}
96
+ aria-label={isExpanded ? 'Collapse table of contents' : 'Expand table of contents'}
97
+ >
98
+ <div className="flex items-center gap-2">
99
+ <List className="w-4 h-4 text-muted-foreground flex-shrink-0" />
100
+ <span className="font-medium text-sm">On this page</span>
101
+ </div>
102
+ {chevronIcon}
103
+ </Button>
104
+
105
+ <AnimatePresence>
106
+ {isExpanded && (
107
+ <motion.div
108
+ ref={contentRef}
109
+ className="mt-2 pb-2 max-h-[60vh] overflow-y-auto px-1 -mx-1"
110
+ initial={{ opacity: 0, height: 0 }}
111
+ animate={{ opacity: 1, height: 'auto' }}
112
+ exit={{ opacity: 0, height: 0 }}
113
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
114
+ >
115
+ <TocObserver
116
+ data={tocs}
117
+ activeId={activeId}
118
+ onActiveIdChange={setActiveId}
119
+ />
120
+ </motion.div>
121
+ )}
122
+ </AnimatePresence>
123
+ </div>
124
+ </div>
125
+ </motion.div>
126
+ </AnimatePresence>
127
+ );
128
+ }