@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.
- package/README.md +1 -3
- package/package.json +7 -7
- package/src/cli/program.js +32 -0
- package/src/cli/promptHandler.js +73 -0
- package/src/dist/LICENSE +21 -0
- package/src/dist/README.md +37 -0
- package/src/dist/app/docs/[[...slug]]/page.tsx +105 -0
- package/src/dist/app/docs/layout.tsx +16 -0
- package/src/dist/app/error.tsx +44 -0
- package/src/dist/app/layout.tsx +96 -0
- package/src/dist/app/not-found.tsx +19 -0
- package/src/dist/app/page.tsx +96 -0
- package/src/dist/components/GithubStart.tsx +44 -0
- package/src/dist/components/Sponsor.tsx +69 -0
- package/src/dist/components/anchor.tsx +84 -0
- package/src/dist/components/contexts/theme-provider.tsx +9 -0
- package/src/dist/components/docs-breadcrumb.tsx +47 -0
- package/src/dist/components/docs-menu.tsx +45 -0
- package/src/dist/components/edit-on-github.tsx +33 -0
- package/src/dist/components/footer.tsx +85 -0
- package/src/dist/components/leftbar.tsx +95 -0
- package/src/dist/components/markdown/AccordionMdx.tsx +47 -0
- package/src/dist/components/markdown/ButtonMdx.tsx +52 -0
- package/src/dist/components/markdown/CardGroupMdx.tsx +28 -0
- package/src/dist/components/markdown/CardMdx.tsx +41 -0
- package/src/dist/components/markdown/CopyMdx.tsx +33 -0
- package/src/dist/components/markdown/ImageMdx.tsx +25 -0
- package/src/dist/components/markdown/KeyboardMdx.tsx +102 -0
- package/src/dist/components/markdown/LinkMdx.tsx +14 -0
- package/src/dist/components/markdown/NoteMdx.tsx +52 -0
- package/src/dist/components/markdown/OutletMdx.tsx +29 -0
- package/src/dist/components/markdown/PreMdx.tsx +19 -0
- package/src/dist/components/markdown/ReleaseMdx.tsx +109 -0
- package/src/dist/components/markdown/StepperMdx.tsx +41 -0
- package/src/dist/components/markdown/TooltipsMdx.tsx +28 -0
- package/src/dist/components/markdown/YoutubeMdx.tsx +22 -0
- package/src/dist/components/markdown/mdx-provider.tsx +29 -0
- package/src/dist/components/mob-toc.tsx +128 -0
- package/src/dist/components/navbar.tsx +87 -0
- package/src/dist/components/pagination.tsx +49 -0
- package/src/dist/components/scroll-to-top.tsx +86 -0
- package/src/dist/components/search.tsx +214 -0
- package/src/dist/components/sublink.tsx +133 -0
- package/src/dist/components/theme-toggle.tsx +71 -0
- package/src/dist/components/toc-observer.tsx +264 -0
- package/src/dist/components/toc.tsx +27 -0
- package/src/dist/components/typography.tsx +9 -0
- package/src/dist/components/ui/accordion.tsx +58 -0
- package/src/dist/components/ui/animated-shiny-text.tsx +40 -0
- package/src/dist/components/ui/aurora.tsx +45 -0
- package/src/dist/components/ui/avatar.tsx +50 -0
- package/src/dist/components/ui/badge.tsx +37 -0
- package/src/dist/components/ui/breadcrumb.tsx +115 -0
- package/src/dist/components/ui/button.tsx +57 -0
- package/src/dist/components/ui/card.tsx +76 -0
- package/src/dist/components/ui/collapsible.tsx +11 -0
- package/src/dist/components/ui/command.tsx +153 -0
- package/src/dist/components/ui/dialog.tsx +124 -0
- package/src/dist/components/ui/dropdown-menu.tsx +200 -0
- package/src/dist/components/ui/icon-cloud.tsx +324 -0
- package/src/dist/components/ui/input.tsx +25 -0
- package/src/dist/components/ui/interactive-hover-button.tsx +35 -0
- package/src/dist/components/ui/popover.tsx +33 -0
- package/src/dist/components/ui/scroll-area.tsx +48 -0
- package/src/dist/components/ui/separator.tsx +30 -0
- package/src/dist/components/ui/sheet.tsx +140 -0
- package/src/dist/components/ui/shine-border.tsx +64 -0
- package/src/dist/components/ui/skeleton.tsx +15 -0
- package/src/dist/components/ui/sonner.tsx +31 -0
- package/src/dist/components/ui/table.tsx +117 -0
- package/src/dist/components/ui/tabs.tsx +55 -0
- package/src/dist/components/ui/toggle-group.tsx +61 -0
- package/src/dist/components/ui/toggle.tsx +46 -0
- package/src/dist/components.json +17 -0
- package/src/dist/contents/docs/getting-started/changelog/index.mdx +512 -0
- package/src/dist/contents/docs/getting-started/components/accordion/index.mdx +72 -0
- package/src/dist/contents/docs/getting-started/components/button/index.mdx +42 -0
- package/src/dist/contents/docs/getting-started/components/card/index.mdx +70 -0
- package/src/dist/contents/docs/getting-started/components/card-group/index.mdx +49 -0
- package/src/dist/contents/docs/getting-started/components/code-block/index.mdx +41 -0
- package/src/dist/contents/docs/getting-started/components/custom/index.mdx +38 -0
- package/src/dist/contents/docs/getting-started/components/image/index.mdx +37 -0
- package/src/dist/contents/docs/getting-started/components/index.mdx +9 -0
- package/src/dist/contents/docs/getting-started/components/keyboard/index.mdx +117 -0
- package/src/dist/contents/docs/getting-started/components/link/index.mdx +34 -0
- package/src/dist/contents/docs/getting-started/components/note/index.mdx +46 -0
- package/src/dist/contents/docs/getting-started/components/release-note/index.mdx +130 -0
- package/src/dist/contents/docs/getting-started/components/stepper/index.mdx +47 -0
- package/src/dist/contents/docs/getting-started/components/tabs/index.mdx +70 -0
- package/src/dist/contents/docs/getting-started/components/tooltips/index.mdx +22 -0
- package/src/dist/contents/docs/getting-started/components/youtube/index.mdx +21 -0
- package/src/dist/contents/docs/getting-started/customize/index.mdx +94 -0
- package/src/dist/contents/docs/getting-started/installation/index.mdx +84 -0
- package/src/dist/contents/docs/getting-started/introduction/index.mdx +50 -0
- package/src/dist/contents/docs/getting-started/project-structure/index.mdx +87 -0
- package/src/dist/contents/docs/getting-started/quick-start-guide/index.mdx +127 -0
- package/src/dist/docu.json +100 -0
- package/src/dist/hooks/index.ts +2 -0
- package/src/dist/hooks/useActiveSection.ts +68 -0
- package/src/dist/hooks/useScrollPosition.ts +28 -0
- package/src/dist/lib/markdown.ts +244 -0
- package/src/dist/lib/routes-config.ts +28 -0
- package/src/dist/lib/toc.ts +9 -0
- package/src/dist/lib/utils.ts +80 -0
- package/src/dist/next-env.d.ts +5 -0
- package/src/dist/next.config.mjs +14 -0
- package/src/dist/package.json +58 -0
- package/src/dist/postcss.config.js +6 -0
- package/src/dist/public/favicon.ico +0 -0
- package/src/dist/public/images/docu.svg +6 -0
- package/src/dist/public/images/example-img.png +0 -0
- package/src/dist/public/images/img-playground.png +0 -0
- package/src/dist/public/images/new-editor.png +0 -0
- package/src/dist/public/images/og-image.png +0 -0
- package/src/dist/public/images/release-note.png +0 -0
- package/src/dist/public/images/snippet.png +0 -0
- package/src/dist/public/images/vercel.png +0 -0
- package/src/dist/public/images/view-changelog.png +0 -0
- package/src/dist/styles/editor.css +57 -0
- package/src/dist/styles/globals.css +156 -0
- package/src/dist/styles/syntax.css +100 -0
- package/src/dist/tailwind.config.ts +112 -0
- package/src/dist/tsconfig.json +26 -0
- package/src/index.js +19 -0
- package/src/installer/projectInstaller.js +125 -0
- package/src/utils/display.js +83 -0
- package/src/utils/logger.js +11 -0
- package/src/utils/packageManager.js +54 -0
- package/create.js +0 -223
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import docuData from "@/docu.json";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
|
|
5
|
+
// Define types for docu.json
|
|
6
|
+
interface SponsorItem {
|
|
7
|
+
url: string;
|
|
8
|
+
image: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface DocuConfig {
|
|
14
|
+
sponsor?: {
|
|
15
|
+
title?: string;
|
|
16
|
+
item?: SponsorItem;
|
|
17
|
+
};
|
|
18
|
+
navbar: any; // Anda bisa mendefinisikan tipe yang lebih spesifik jika diperlukan
|
|
19
|
+
footer: any;
|
|
20
|
+
meta: any;
|
|
21
|
+
repository: any;
|
|
22
|
+
routes: any[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Type assertion for docu.json
|
|
26
|
+
const docuConfig = docuData as DocuConfig;
|
|
27
|
+
|
|
28
|
+
export function Sponsor() {
|
|
29
|
+
// Safely get sponsor data with optional chaining and default values
|
|
30
|
+
const sponsor = docuConfig?.sponsor || {};
|
|
31
|
+
const item = sponsor?.item;
|
|
32
|
+
|
|
33
|
+
// Return null if required fields are missing
|
|
34
|
+
if (!item?.url || !item?.image || !item?.title) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="mt-4">
|
|
40
|
+
{sponsor?.title && (
|
|
41
|
+
<h2 className="mb-4 text-sm font-medium">{sponsor.title}</h2>
|
|
42
|
+
)}
|
|
43
|
+
<Link
|
|
44
|
+
href={item.url}
|
|
45
|
+
target="_blank"
|
|
46
|
+
rel="noopener noreferrer"
|
|
47
|
+
className="flex flex-col justify-center gap-2 p-4 border rounded-lg hover:shadow transition-shadow"
|
|
48
|
+
>
|
|
49
|
+
<div className="relative w-8 h-8 flex-shrink-0">
|
|
50
|
+
<Image
|
|
51
|
+
src={item.image}
|
|
52
|
+
alt={item.title}
|
|
53
|
+
fill
|
|
54
|
+
className="object-contain"
|
|
55
|
+
sizes="32px"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="text-center sm:text-left">
|
|
59
|
+
<h3 className="text-sm font-medium">{item.title}</h3>
|
|
60
|
+
{item.description && (
|
|
61
|
+
<p className="text-muted-foreground text-sm">{item.description}</p>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</Link>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Sponsor;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import Link, { LinkProps } from "next/link";
|
|
5
|
+
import { usePathname } from "next/navigation";
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
|
|
8
|
+
type AnchorProps = LinkProps & {
|
|
9
|
+
absolute?: boolean;
|
|
10
|
+
activeClassName?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps>;
|
|
15
|
+
|
|
16
|
+
const Anchor = forwardRef<HTMLAnchorElement, AnchorProps>(
|
|
17
|
+
({
|
|
18
|
+
absolute = false,
|
|
19
|
+
className = "",
|
|
20
|
+
activeClassName = "",
|
|
21
|
+
disabled = false,
|
|
22
|
+
children,
|
|
23
|
+
href,
|
|
24
|
+
...props
|
|
25
|
+
}, ref) => {
|
|
26
|
+
const path = usePathname();
|
|
27
|
+
const hrefStr = href?.toString() || '';
|
|
28
|
+
|
|
29
|
+
// Check if URL is external
|
|
30
|
+
const isExternal = /^(https?:\/\/|\/\/)/.test(hrefStr);
|
|
31
|
+
|
|
32
|
+
// Check if current path matches the link
|
|
33
|
+
const isActive = absolute
|
|
34
|
+
? hrefStr.split("/")[1] === path?.split("/")[1]
|
|
35
|
+
: path === hrefStr;
|
|
36
|
+
|
|
37
|
+
// Apply active class only for internal links
|
|
38
|
+
const linkClassName = cn(
|
|
39
|
+
'transition-colors hover:text-primary',
|
|
40
|
+
className,
|
|
41
|
+
!isExternal && isActive && activeClassName
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (disabled) {
|
|
45
|
+
return (
|
|
46
|
+
<span className={cn(linkClassName, "cursor-not-allowed opacity-50")}>
|
|
47
|
+
{children}
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if (isExternal) {
|
|
54
|
+
return (
|
|
55
|
+
<a
|
|
56
|
+
ref={ref}
|
|
57
|
+
href={hrefStr}
|
|
58
|
+
className={linkClassName}
|
|
59
|
+
target="_blank"
|
|
60
|
+
rel="noopener noreferrer"
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Link
|
|
71
|
+
ref={ref}
|
|
72
|
+
href={hrefStr}
|
|
73
|
+
className={linkClassName}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</Link>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
Anchor.displayName = "Anchor";
|
|
83
|
+
|
|
84
|
+
export default Anchor;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
5
|
+
import { type ThemeProviderProps } from "next-themes/dist/types";
|
|
6
|
+
|
|
7
|
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
8
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Breadcrumb,
|
|
3
|
+
BreadcrumbItem,
|
|
4
|
+
BreadcrumbLink,
|
|
5
|
+
BreadcrumbList,
|
|
6
|
+
BreadcrumbPage,
|
|
7
|
+
BreadcrumbSeparator,
|
|
8
|
+
} from "@/components/ui/breadcrumb";
|
|
9
|
+
import { Fragment } from "react";
|
|
10
|
+
|
|
11
|
+
export default function DocsBreadcrumb({ paths }: { paths: string[] }) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="pb-5 max-lg:pt-12">
|
|
14
|
+
<Breadcrumb>
|
|
15
|
+
<BreadcrumbList>
|
|
16
|
+
<BreadcrumbItem>
|
|
17
|
+
<BreadcrumbLink>Docs</BreadcrumbLink>
|
|
18
|
+
</BreadcrumbItem>
|
|
19
|
+
{paths.map((path, index) => (
|
|
20
|
+
<Fragment key={`${path}-${index}`}>
|
|
21
|
+
<BreadcrumbSeparator />
|
|
22
|
+
<BreadcrumbItem>
|
|
23
|
+
{index < paths.length - 1 ? (
|
|
24
|
+
<BreadcrumbLink className="a">
|
|
25
|
+
{toTitleCase(path)}
|
|
26
|
+
</BreadcrumbLink>
|
|
27
|
+
) : (
|
|
28
|
+
<BreadcrumbPage className="b">
|
|
29
|
+
{toTitleCase(path)}
|
|
30
|
+
</BreadcrumbPage>
|
|
31
|
+
)}
|
|
32
|
+
</BreadcrumbItem>
|
|
33
|
+
</Fragment>
|
|
34
|
+
))}
|
|
35
|
+
</BreadcrumbList>
|
|
36
|
+
</Breadcrumb>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toTitleCase(input: string): string {
|
|
42
|
+
const words = input.split("-");
|
|
43
|
+
const capitalizedWords = words.map(
|
|
44
|
+
(word) => word.charAt(0).toUpperCase() + word.slice(1)
|
|
45
|
+
);
|
|
46
|
+
return capitalizedWords.join(" ");
|
|
47
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ROUTES } from "@/lib/routes-config";
|
|
4
|
+
import SubLink from "./sublink";
|
|
5
|
+
import { usePathname } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
interface DocsMenuProps {
|
|
8
|
+
isSheet?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function DocsMenu({ isSheet = false, className = "" }: DocsMenuProps) {
|
|
13
|
+
const pathname = usePathname();
|
|
14
|
+
|
|
15
|
+
// Skip rendering if not on a docs page
|
|
16
|
+
if (!pathname.startsWith("/docs")) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<nav
|
|
20
|
+
aria-label="Documentation navigation"
|
|
21
|
+
className={className}
|
|
22
|
+
>
|
|
23
|
+
<ul className="flex flex-col gap-3.5 mt-5 pr-2 pb-6">
|
|
24
|
+
{ROUTES.map((item, index) => {
|
|
25
|
+
// Normalize href - hapus leading/trailing slashes
|
|
26
|
+
const normalizedHref = `/${item.href.replace(/^\/+|\/+$/g, '')}`;
|
|
27
|
+
const itemHref = `/docs${normalizedHref}`;
|
|
28
|
+
|
|
29
|
+
const modifiedItems = {
|
|
30
|
+
...item,
|
|
31
|
+
href: itemHref,
|
|
32
|
+
level: 0,
|
|
33
|
+
isSheet,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<li key={`${item.title}-${index}`}>
|
|
38
|
+
<SubLink {...modifiedItems} />
|
|
39
|
+
</li>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</ul>
|
|
43
|
+
</nav>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import docuConfig from '@/docu.json'; // Import JSON
|
|
3
|
+
import { SquarePenIcon } from 'lucide-react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
|
|
6
|
+
interface EditThisPageProps {
|
|
7
|
+
filePath: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const EditThisPage: React.FC<EditThisPageProps> = ({ filePath }) => {
|
|
11
|
+
const { repository } = docuConfig;
|
|
12
|
+
|
|
13
|
+
if (!repository?.editLink || !repository.url || !repository.editPathTemplate) return null;
|
|
14
|
+
|
|
15
|
+
const editUrl = `${repository.url}${repository.editPathTemplate.replace("{filePath}", filePath)}`;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="text-right">
|
|
19
|
+
<Link
|
|
20
|
+
href={editUrl}
|
|
21
|
+
target="_blank"
|
|
22
|
+
rel="noopener noreferrer"
|
|
23
|
+
aria-label="Edit this page on Git"
|
|
24
|
+
className="inline-flex items-center gap-2 text-sm font-medium text-muted-foreground no-underline"
|
|
25
|
+
>
|
|
26
|
+
<span className="flex justify-start items-center gap-1">Edit this page
|
|
27
|
+
<SquarePenIcon className="w-4 h-4" /></span>
|
|
28
|
+
</Link>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default EditThisPage;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { ModeToggle } from "@/components/theme-toggle";
|
|
3
|
+
import docuData from "@/docu.json";
|
|
4
|
+
import * as LucideIcons from "lucide-react";
|
|
5
|
+
|
|
6
|
+
// Define types for docu.json
|
|
7
|
+
interface SocialItem {
|
|
8
|
+
name: string;
|
|
9
|
+
url: string;
|
|
10
|
+
iconName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FooterConfig {
|
|
14
|
+
copyright: string;
|
|
15
|
+
social?: SocialItem[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Type assertion for docu.json
|
|
19
|
+
const docuConfig = docuData as {
|
|
20
|
+
footer: FooterConfig;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function Footer() {
|
|
24
|
+
const { footer } = docuConfig;
|
|
25
|
+
return (
|
|
26
|
+
<footer className="w-full py-8 border-t bg-background">
|
|
27
|
+
<div className="container flex flex-col lg:flex-row items-center justify-between text-sm">
|
|
28
|
+
<div className="flex flex-col items-center lg:items-start justify-start gap-4 w-full lg:w-3/5 text-center lg:text-left">
|
|
29
|
+
<p className="text-muted-foreground">
|
|
30
|
+
Copyright © {new Date().getFullYear()} {footer.copyright} - <MadeWith />
|
|
31
|
+
</p>
|
|
32
|
+
<div className="flex items-center justify-center lg:justify-start gap-6 mt-2 w-full">
|
|
33
|
+
<FooterButtons />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="hidden lg:flex items-center justify-end lg:w-2/5">
|
|
37
|
+
<ModeToggle />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</footer>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function FooterButtons() {
|
|
45
|
+
const footer = docuConfig?.footer;
|
|
46
|
+
|
|
47
|
+
// Jangan render apapun jika tidak ada data sosial
|
|
48
|
+
if (!footer || !Array.isArray(footer.social) || footer.social.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{footer.social.map((item) => {
|
|
55
|
+
const IconComponent =
|
|
56
|
+
(LucideIcons[item.iconName as keyof typeof LucideIcons] ??
|
|
57
|
+
LucideIcons["Globe"]) as React.FC<{ className?: string }>;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Link
|
|
61
|
+
key={item.name}
|
|
62
|
+
href={item.url}
|
|
63
|
+
target="_blank"
|
|
64
|
+
rel="noopener noreferrer"
|
|
65
|
+
aria-label={item.name}
|
|
66
|
+
>
|
|
67
|
+
<IconComponent className="w-4 h-4 text-gray-800 transition-colors dark:text-gray-400 hover:text-primary" />
|
|
68
|
+
</Link>
|
|
69
|
+
);
|
|
70
|
+
})}
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function MadeWith() {
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<span className="text-muted-foreground">Made with </span>
|
|
79
|
+
<span className="text-primary">
|
|
80
|
+
<Link href="https://www.docubook.pro" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2 text-muted-foreground">
|
|
81
|
+
DocuBook
|
|
82
|
+
</Link></span>
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Sheet,
|
|
5
|
+
SheetClose,
|
|
6
|
+
SheetContent,
|
|
7
|
+
SheetHeader,
|
|
8
|
+
SheetTrigger,
|
|
9
|
+
} from "@/components/ui/sheet";
|
|
10
|
+
import { Logo, NavMenu } from "@/components/navbar";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import { AlignLeftIcon, PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
13
|
+
import { FooterButtons } from "@/components/footer";
|
|
14
|
+
import { DialogTitle, DialogDescription } from "@/components/ui/dialog";
|
|
15
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
16
|
+
import DocsMenu from "@/components/docs-menu";
|
|
17
|
+
import { ModeToggle } from "@/components/theme-toggle";
|
|
18
|
+
|
|
19
|
+
// Toggle Button Component
|
|
20
|
+
export function ToggleButton({
|
|
21
|
+
collapsed,
|
|
22
|
+
onToggle
|
|
23
|
+
}: {
|
|
24
|
+
collapsed: boolean,
|
|
25
|
+
onToggle: () => void
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="absolute top-0 right-0 py-6 z-10 -mt-4">
|
|
29
|
+
<Button
|
|
30
|
+
size="icon"
|
|
31
|
+
variant="outline"
|
|
32
|
+
className="hover:bg-transparent hover:text-inherit border-none text-muted-foreground"
|
|
33
|
+
onClick={onToggle}
|
|
34
|
+
>
|
|
35
|
+
{collapsed ? (
|
|
36
|
+
<PanelLeftOpen size={18} />
|
|
37
|
+
) : (
|
|
38
|
+
<PanelLeftClose size={18} />
|
|
39
|
+
)}
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function Leftbar() {
|
|
46
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
47
|
+
const toggleCollapse = () => setCollapsed(prev => !prev);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<aside
|
|
51
|
+
className={`sticky lg:flex hidden top-16 h-[calc(100vh-4rem)] border-r bg-background transition-all duration-300
|
|
52
|
+
${collapsed ? "w-[24px]" : "w-[280px]"} flex flex-col pr-2`}
|
|
53
|
+
>
|
|
54
|
+
<ToggleButton collapsed={collapsed} onToggle={toggleCollapse} />
|
|
55
|
+
{/* Scrollable DocsMenu */}
|
|
56
|
+
<ScrollArea className="flex-1 px-0.5 pb-4">
|
|
57
|
+
{!collapsed && <DocsMenu />}
|
|
58
|
+
</ScrollArea>
|
|
59
|
+
</aside>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function SheetLeftbar() {
|
|
64
|
+
return (
|
|
65
|
+
<Sheet>
|
|
66
|
+
<SheetTrigger asChild>
|
|
67
|
+
<Button variant="ghost" size="icon" className="max-lg:flex hidden">
|
|
68
|
+
<AlignLeftIcon />
|
|
69
|
+
</Button>
|
|
70
|
+
</SheetTrigger>
|
|
71
|
+
<SheetContent className="flex flex-col gap-4 px-0" side="left">
|
|
72
|
+
<DialogTitle className="sr-only">Navigation Menu</DialogTitle>
|
|
73
|
+
<DialogDescription className="sr-only">
|
|
74
|
+
Main navigation menu with links to different sections
|
|
75
|
+
</DialogDescription>
|
|
76
|
+
<SheetHeader>
|
|
77
|
+
<SheetClose className="px-5" asChild>
|
|
78
|
+
<span className="px-2"><Logo /></span>
|
|
79
|
+
</SheetClose>
|
|
80
|
+
</SheetHeader>
|
|
81
|
+
<div className="flex flex-col gap-4 overflow-y-auto">
|
|
82
|
+
<div className="flex flex-col gap-2.5 mt-3 mx-2 px-5">
|
|
83
|
+
<NavMenu isSheet />
|
|
84
|
+
</div>
|
|
85
|
+
<div className="mx-2 px-5">
|
|
86
|
+
<DocsMenu isSheet />
|
|
87
|
+
</div>
|
|
88
|
+
<div className="flex w-2/4 px-5">
|
|
89
|
+
<ModeToggle />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</SheetContent>
|
|
93
|
+
</Sheet>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useState } from 'react';
|
|
4
|
+
import { ChevronRight } from 'lucide-react';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
type AccordionProps = {
|
|
8
|
+
title: string;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Accordion = ({
|
|
15
|
+
title,
|
|
16
|
+
children,
|
|
17
|
+
defaultOpen = false,
|
|
18
|
+
className,
|
|
19
|
+
}: AccordionProps) => {
|
|
20
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className={cn("border rounded-lg overflow-hidden", className)}>
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
27
|
+
className="flex items-center my-auto space-x-2 space-y-2 w-full px-4 h-12 transition-colors bg-background dark:hover:bg-muted/50 hover:bg-muted/15"
|
|
28
|
+
>
|
|
29
|
+
<ChevronRight
|
|
30
|
+
className={cn(
|
|
31
|
+
"w-4 h-4 text-muted-foreground transition-transform duration-200",
|
|
32
|
+
isOpen && "rotate-90"
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
35
|
+
<h3 className="font-medium text-base text-foreground pb-2">{title}</h3>
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
{isOpen && (
|
|
39
|
+
<div className="px-4 py-3 border-t dark:bg-muted/50 bg-muted/15">
|
|
40
|
+
{children}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default Accordion;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as Icons from "lucide-react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
|
|
5
|
+
type IconName = keyof typeof Icons;
|
|
6
|
+
type ButtonProps = {
|
|
7
|
+
icon?: keyof typeof Icons;
|
|
8
|
+
text?: string;
|
|
9
|
+
href: string;
|
|
10
|
+
target?: "_blank" | "_self" | "_parent" | "_top";
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
|
+
variation?: "primary" | "accent" | "outline";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const Button: React.FC<ButtonProps> = ({
|
|
16
|
+
icon,
|
|
17
|
+
text,
|
|
18
|
+
href,
|
|
19
|
+
target,
|
|
20
|
+
size = "md",
|
|
21
|
+
variation = "primary",
|
|
22
|
+
}) => {
|
|
23
|
+
const baseStyles = "inline-flex items-center justify-center rounded font-medium focus:outline-none transition no-underline";
|
|
24
|
+
|
|
25
|
+
const sizeStyles = {
|
|
26
|
+
sm: "px-3 py-1 my-6 text-sm",
|
|
27
|
+
md: "px-4 py-2 my-6 text-base",
|
|
28
|
+
lg: "px-5 py-3 my-6 text-lg",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const variationStyles = {
|
|
32
|
+
primary: "bg-primary text-white hover:bg-primary/90",
|
|
33
|
+
accent: "bg-accent text-white hover:bg-accent/90",
|
|
34
|
+
outline: "border border-accent text-accent hover:bg-accent/10",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const Icon = icon ? (Icons[icon] as React.FC<{ className?: string }>) : null; // Tipe eksplisit sebagai React.FC
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Link
|
|
41
|
+
href={href}
|
|
42
|
+
target={target}
|
|
43
|
+
rel={target === "_blank" ? "noopener noreferrer" : undefined}
|
|
44
|
+
className={`${baseStyles} ${sizeStyles[size]} ${variationStyles[variation]}`}
|
|
45
|
+
>
|
|
46
|
+
{text && <span>{text}</span>}
|
|
47
|
+
{Icon && <Icon className="mr-2 h-5 w-5" />}
|
|
48
|
+
</Link>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default Button;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
interface CardGroupProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
cols?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CardGroup: React.FC<CardGroupProps> = ({ children, cols = 2, className }) => {
|
|
11
|
+
const cardsArray = React.Children.toArray(children); // Pastikan children berupa array
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={clsx(
|
|
16
|
+
"grid gap-4",
|
|
17
|
+
`grid-cols-1 sm:grid-cols-${cols}`,
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
{cardsArray.map((card, index) => (
|
|
22
|
+
<div key={index}>{card}</div>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default CardGroup;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import * as Icons from "lucide-react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
|
|
6
|
+
type IconName = keyof typeof Icons;
|
|
7
|
+
|
|
8
|
+
interface CardProps {
|
|
9
|
+
title: string;
|
|
10
|
+
icon?: IconName;
|
|
11
|
+
href?: string;
|
|
12
|
+
horizontal?: boolean;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Card: React.FC<CardProps> = ({ title, icon, href, horizontal, children, className }) => {
|
|
18
|
+
const Icon = icon ? (Icons[icon] as React.FC<{ className?: string }>) : null;
|
|
19
|
+
|
|
20
|
+
const content = (
|
|
21
|
+
<div
|
|
22
|
+
className={clsx(
|
|
23
|
+
"border rounded-lg shadow-sm p-4 transition-all duration-200 bg-white dark:bg-gray-900",
|
|
24
|
+
"hover:bg-gray-50 dark:hover:bg-gray-800",
|
|
25
|
+
"flex gap-2",
|
|
26
|
+
horizontal ? "flex-row items-center gap-1" : "flex-col space-y-1",
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{Icon && <Icon className="w-5 h-5 text-primary flex-shrink-0" />}
|
|
31
|
+
<div className="flex-1 min-w-0 my-auto h-full">
|
|
32
|
+
<span className="text-base font-semibold">{title}</span>
|
|
33
|
+
<div className="text-sm text-gray-600 dark:text-gray-400 -mt-3">{children}</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return href ? <Link className="no-underline block" href={href}>{content}</Link> : content;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default Card;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
4
|
+
import { Button } from "../ui/button";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
export default function Copy({ content }: { content: string }) {
|
|
8
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
9
|
+
|
|
10
|
+
async function handleCopy() {
|
|
11
|
+
await navigator.clipboard.writeText(content);
|
|
12
|
+
setIsCopied(true);
|
|
13
|
+
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
setIsCopied(false);
|
|
16
|
+
}, 2000);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Button
|
|
21
|
+
variant="secondary"
|
|
22
|
+
className="border"
|
|
23
|
+
size="xs"
|
|
24
|
+
onClick={handleCopy}
|
|
25
|
+
>
|
|
26
|
+
{isCopied ? (
|
|
27
|
+
<CheckIcon className="w-3 h-3" />
|
|
28
|
+
) : (
|
|
29
|
+
<CopyIcon className="w-3 h-3" />
|
|
30
|
+
)}
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
}
|