@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,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
+ }