update versi docubook

This commit is contained in:
2025-03-01 18:18:54 +07:00
parent 68383379da
commit 4521957fb9
35 changed files with 790 additions and 456 deletions

View File

@@ -25,11 +25,11 @@ const EditThisPage: React.FC<EditThisPageProps> = ({ filePath }) => {
fontWeight: 'bold',
}}
>
<span className='text-primary text-sm max-[480px]:hidden'>Edit this page on Github</span>
<span className='text-primary text-sm max-[480px]:hidden'>Edit this page</span>
<SquarePenIcon className="w-4 h-4 text-primary" />
</Link>
</div>
);
};
export default EditThisPage;
export default EditThisPage;

View File

@@ -0,0 +1,56 @@
import Link from "next/link";
import { ModeToggle } from "@/components/docs/theme-toggle";
import docuConfig from "@/docu.json";
import * as LucideIcons from "lucide-react"; // Import all icons
export function Footer() {
const { footer } = docuConfig;
const { meta } = docuConfig;
return (
<footer className="w-full py-4 border-t lg:py-8 bg-background">
<div className="container flex flex-wrap items-center justify-between text-sm">
<div className="items-start justify-center hidden gap-4 lg:flex-col lg:flex lg:w-3/5">
<h3 className="text-lg font-bold font-code">{meta.title}</h3>
<span className="w-3/4 text-base text-wrap text-muted-foreground">{meta.description}</span>
<div className="flex items-center gap-6 mt-2">
<FooterButtons />
</div>
</div>
<div className="flex flex-col items-start justify-center w-full gap-4 mt-4 xl:items-end lg:w-2/5">
<p className="text-center text-muted-foreground">
Copyright © {new Date().getFullYear()} {footer.copyright} - Made with{" "}
<Link href="https://www.docubook.pro" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2">
DocuBook
</Link>
</p>
<div className="hidden lg:flex">
<ModeToggle />
</div>
</div>
</div>
</footer>
);
}
export function FooterButtons() {
const { footer } = docuConfig;
return (
<>
{footer.social?.map((item) => {
const IconComponent = (LucideIcons[item.iconName as keyof typeof LucideIcons] ?? LucideIcons["Globe"]) as unknown as React.FC<{ className?: string }>;
return (
<Link
key={item.name}
href={item.url}
target="_blank"
rel="noopener noreferrer"
aria-label={item.name}
>
<IconComponent className="w-4 h-4 text-gray-800 transition-colors dark:text-gray-400 hover:text-primary" />
</Link>
);
})}
</>
);
}

View File

@@ -6,12 +6,13 @@ import {
SheetTrigger,
} from "@/components/ui/sheet";
import { Logo, NavMenu } from "./navbar";
import { Button } from "./ui/button";
import { Button } from "../ui/button";
import { AlignLeftIcon } from "lucide-react";
import { FooterButtons } from "./footer";
import { DialogTitle } from "./ui/dialog";
import { DialogTitle } from "../ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import DocsMenu from "./docs-menu";
import { ModeToggle } from "./theme-toggle";
export function Leftbar() {
return (
@@ -45,9 +46,12 @@ export function SheetLeftbar() {
<div className="mx-2 px-5">
<DocsMenu isSheet />
</div>
<div className="p-6 pb-4 flex gap-2.5">
<div className="px-6 py-2 flex justify-start items-center gap-6">
<FooterButtons />
</div>
<div className="flex w-2/4 px-5">
<ModeToggle />
</div>
</div>
</SheetContent>
</Sheet>

View File

@@ -1,8 +1,6 @@
import { ModeToggle } from "@/components/theme-toggle";
import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
import Image from "next/image";
import { buttonVariants } from "./ui/button";
import Search from "./search";
import Anchor from "./anchor";
import { SheetLeftbar } from "./leftbar";
@@ -10,43 +8,24 @@ import { SheetClose } from "@/components/ui/sheet";
import docuConfig from "@/docu.json"; // Import JSON
export function Navbar() {
const { social } = docuConfig; // Extract navbar and social from JSON
return (
<nav className="w-full border-b h-16 sticky top-0 z-50 bg-background">
<nav className="sticky top-0 z-50 w-full h-16 border-b bg-background">
<div className="sm:container mx-auto w-[95vw] h-full flex items-center justify-between md:gap-2">
<div className="flex items-center gap-5">
<SheetLeftbar />
<div className="flex items-center gap-6">
<div className="sm:flex hidden">
<div className="hidden sm:flex">
<Logo />
</div>
<div className="lg:flex hidden items-center gap-4 text-sm font-medium text-muted-foreground">
<div className="items-center hidden gap-4 text-sm font-medium lg:flex text-muted-foreground">
<NavMenu />
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
<Search />
<div className="flex ml-2.5 sm:ml-0 gap-2">
{social.map((item) => {
const Icon = require("lucide-react")[item.iconName]; // Dynamically load icon
return (
<Link
key={item.name}
href={item.url}
target="_blank"
className={buttonVariants({ variant: "ghost", size: "icon" })}
>
<Icon className="h-[1.1rem] w-[1.1rem]" />
</Link>
);
})}
<ModeToggle />
</div>
</div>
</div>
</div>
</nav>
@@ -59,7 +38,7 @@ export function Logo() {
return (
<Link href="/" className="flex items-center gap-2.5">
<Image src={navbar.logo.src} alt={navbar.logo.alt} width="24" height="24" />
<h2 className="text-md font-bold font-code">{navbar.logoText}</h2>
<h2 className="font-bold font-code text-md">{navbar.logoText}</h2>
</Link>
);
}
@@ -69,12 +48,12 @@ export function NavMenu({ isSheet = false }) {
return (
<>
{navbar.menu.map((item) => {
{navbar?.menu?.map((item) => {
const isExternal = item.href.startsWith("http");
const Comp = (
<Anchor
key={item.title + item.href}
key={`${item.title}-${item.href}`}
activeClassName="!text-primary md:font-semibold font-medium"
absolute
className="flex items-center gap-1 dark:text-stone-300/85 text-stone-800"
@@ -83,7 +62,7 @@ export function NavMenu({ isSheet = false }) {
rel={isExternal ? "noopener noreferrer" : undefined}
>
{item.title}
{isExternal && <ArrowUpRight className="h-4 w-4 text-muted-foreground" />}
{isExternal && <ArrowUpRight className="w-4 h-4 text-muted-foreground" />}
</Anchor>
);
return isSheet ? (

View File

@@ -1,7 +1,7 @@
import { getPreviousNext } from "@/lib/markdown";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import Link from "next/link";
import { buttonVariants } from "./ui/button";
import { buttonVariants } from "../ui/button";
export default function Pagination({ pathname }: { pathname: string }) {
const res = getPreviousNext(pathname);
@@ -46,4 +46,4 @@ export default function Pagination({ pathname }: { pathname: string }) {
</div>
</div>
);
}
}

View File

@@ -2,7 +2,7 @@
import { ArrowUpIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Button } from "./ui/button";
import { Button } from "../ui/button";
import { cn } from "@/lib/utils";
export function ScrollToTop() {

View File

@@ -96,14 +96,14 @@ export default function Search() {
}}
>
<DialogTrigger asChild>
<div className="relative flex-1 cursor-pointer sm:w-60">
<div className="relative flex-1 cursor-pointer max-w-[160px]">
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-500 dark:text-stone-400" />
<Input
className="md:w-full rounded-md dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
placeholder="Search"
type="search"
/>
<div className="sm:flex hidden absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-black dark:border dark:border-white/20 bg-stone-200/50 border border-black/40 p-1 rounded-sm">
<div className="flex absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-black dark:border dark:border-white/20 bg-stone-200/50 border border-black/40 p-1 rounded-sm">
<CommandIcon className="w-3 h-3" />
<span>K</span>
</div>

View File

@@ -0,0 +1,71 @@
"use client";
import * as React from "react";
import { Moon, Sun, Monitor } from "lucide-react";
import { useTheme } from "next-themes";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export function ModeToggle() {
const { theme, setTheme } = useTheme();
const [selectedTheme, setSelectedTheme] = React.useState<string>("system");
// Pastikan toggle tetap di posisi yang benar setelah reload
React.useEffect(() => {
if (theme) {
setSelectedTheme(theme);
} else {
setSelectedTheme("system"); // Default ke system jika undefined
}
}, [theme]);
return (
<ToggleGroup
type="single"
value={selectedTheme}
onValueChange={(value) => {
if (value) {
setTheme(value);
setSelectedTheme(value);
}
}}
className="flex items-center gap-1 rounded-full border border-gray-300 dark:border-gray-700 p-1 transition-all"
>
<ToggleGroupItem
value="light"
size="sm"
aria-label="Light Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "light"
? "bg-blue-500 text-white"
: "bg-transparent"
}`}
>
<Sun className={`h-4 w-4 ${selectedTheme === "light" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
</ToggleGroupItem>
<ToggleGroupItem
value="system"
size="sm"
aria-label="System Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "system"
? "bg-blue-500 text-white"
: "bg-transparent"
}`}
>
<Monitor className={`h-4 w-4 ${selectedTheme === "system" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
</ToggleGroupItem>
<ToggleGroupItem
value="dark"
size="sm"
aria-label="Dark Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "dark"
? "bg-blue-500 text-white"
: "bg-transparent"
}`}
>
<Moon className={`h-4 w-4 ${selectedTheme === "dark" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
</ToggleGroupItem>
</ToggleGroup>
);
}

View File

@@ -1,57 +0,0 @@
import Link from "next/link";
import { buttonVariants } from "./ui/button";
import docuConfig from "@/docu.json"; // Import JSON
export function Footer() {
const { footer } = docuConfig; // Extract footer from JSON
return (
<footer className="border-t w-full h-16">
<div className="container flex items-center sm:justify-between justify-center sm:gap-0 gap-4 h-full text-muted-foreground text-sm flex-wrap sm:py-0 py-3 max-sm:px-4">
{/* Footer Text */}
<div className="flex items-center gap-3">
<p className="text-center">
Copyright © {new Date().getFullYear()} {footer.copyright} - Crafted with love using{" "}
<Link
href="https://www.docubook.pro"
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2"
>
DocuBook
</Link>
</p>
</div>
{/* Footer Buttons */}
<div className="gap-4 items-center hidden md:flex">
<FooterButtons />
</div>
</div>
</footer>
);
}
export function FooterButtons() {
const { footer } = docuConfig; // Extract footer from JSON
return (
<>
{footer.buttons.map((button, index) => {
const Icon = require("lucide-react")[button.iconName]; // Dynamically load icon
return (
<Link
key={index}
href={button.url}
target="_blank"
rel="noopener noreferrer"
className={buttonVariants({ variant: "outline", size: "sm" })}
>
<Icon className="h-4 w-4 mr-2 dark:text-primary dark:hover:text-accent-foreground" />
{button.text}
</Link>
);
})}
</>
);
}

View File

@@ -0,0 +1,33 @@
"use client";
import { useState } from "react";
import { TerminalSquareIcon, ClipboardIcon, CheckIcon } from "lucide-react";
export function CopyCommand() {
const [copied, setCopied] = useState(false);
const command = "npx @docubook/create@latest";
const copyToClipboard = () => {
navigator.clipboard.writeText(command);
setCopied(true);
setTimeout(() => setCopied(false), 5000);
};
return (
<div className="relative flex flex-row items-center justify-center sm:gap-2 gap-0.5 text-muted-foreground text-md mt-10 mb-12 font-code text-base font-medium group">
<TerminalSquareIcon className="w-5 h-5 mr-1 mt-0.5" />
<span className="select-all">{command}</span>
<button
onClick={copyToClipboard}
className="p-1 ml-1 transition-opacity rounded-md opacity-0 group-hover:opacity-100 hover:bg-gray-200 dark:hover:bg-gray-700"
>
{copied ? (
<CheckIcon className="w-4 h-4 text-green-500" />
) : (
<ClipboardIcon className="w-4 h-4" />
)}
</button>
</div>
);
}
export default CopyCommand;

View File

@@ -0,0 +1,91 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Play } from "lucide-react";
const scriptOutput = [
"DocuBook CLI Installer",
"✔ Enter your project directory name: docubook",
"? Choose a package manager:",
"> npm",
" pnpm",
" yarn",
" bun",
"",
":: Cloning starter from GitLab...",
"✔ Docubook project successfully created!",
"Skipping rename postcss.config.js because Bun is not installed.",
"",
"[ DocuBook Version 1.8.0 ]",
"",
"Starting the installation process...",
"Installation | ████████████████████████████████ | 100% 100/100",
"",
"Dependencies installed successfully using npm!",
"",
"┌────────────────────────────────────┐",
"│ Next Steps: │",
"│ │",
"│ 1. Navigate to project directory: │",
"│ cd docubook │",
"│ │",
"│ 2. Install dependencies: │",
"│ npm install │",
"│ │",
"│ 3. Start the development server: │",
"│ npm run dev │",
"└────────────────────────────────────┘"
];
export function RuntimeSimulator() {
const [logs, setLogs] = useState<string[]>([]);
const [running, setRunning] = useState(false);
const terminalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (running) {
setLogs([]);
scriptOutput.forEach((line, index) => {
setTimeout(() => {
setLogs((prev) => [...prev, line]);
}, index * 500);
});
}
}, [running]);
useEffect(() => {
terminalRef.current?.scrollTo({ top: terminalRef.current.scrollHeight, behavior: "smooth" });
}, [logs]);
return (
<div className="bg-gray-900 text-green-400 font-mono rounded-lg overflow-hidden w-full h-auto max-w-2xl shadow-lg">
<div className="bg-gray-800 text-gray-300 px-4 py-2 flex items-center space-x-2">
<div className="flex space-x-1">
<span className="w-3 h-3 bg-red-500 rounded-full"></span>
<span className="w-3 h-3 bg-yellow-500 rounded-full"></span>
<span className="w-3 h-3 bg-green-500 rounded-full"></span>
</div>
<span className="ml-3">docubook@localhost</span>
</div>
<div ref={terminalRef} className="p-4 md:h-[400px] h-72 overflow-y-auto text-left">
{logs.map((log, index) => (
<pre key={index} className="whitespace-pre-wrap">{log}</pre>
))}
</div>
<div className="bg-gray-800 p-2 flex items-center space-x-2">
<input
type="text"
value="npx @docubook/create@latest"
readOnly
className="bg-gray-700 text-gray-300 px-2 py-1 rounded w-full text-left"
/>
<Button onClick={() => setRunning(true)} className="bg-green-600 hover:bg-green-500 px-4 py-1">
<Play className="w-5 h-5" />
</Button>
</div>
</div>
);
}
export default RuntimeSimulator;

View File

@@ -1,48 +0,0 @@
import {
AnimatedSpan,
Terminal,
TypingAnimation,
} from "@/components/ui/terminal";
export function NpxTerminal() {
return (
<Terminal>
<TypingAnimation className="text-left pl-6 dark:text-blue-300 text-blue-600">&gt; npx @docubook/create@latest</TypingAnimation>
<AnimatedSpan delay={1500} className="text-muted-foreground text-left pl-6">
<span>Need to install the following packages:</span>
</AnimatedSpan>
<AnimatedSpan delay={2000} className="text-muted-foreground text-left pl-6">
<span>@docubook/create@1.4.0</span>
</AnimatedSpan>
<AnimatedSpan delay={2500} className="text-muted-foreground text-left pl-6">
<span>Ok to proceed? (y)</span>
</AnimatedSpan>
<AnimatedSpan delay={3000} className="dark:text-blue-300 text-blue-600 text-left pl-6">
<span> ? Enter a name for your project directory: (docubook)</span>
</AnimatedSpan>
<AnimatedSpan delay={3500} className="text-muted-foreground text-left pl-6">
<span>Creating a new Docubook project in /path/your/docubook from the starter branch...</span>
</AnimatedSpan>
<AnimatedSpan delay={4000} className="text-muted-foreground text-left pl-6">
<span> Docubook project successfully created in /path/your/docubook!</span>
</AnimatedSpan>
<AnimatedSpan delay={6000} className="text-foreground text-left pl-6">
<span>Next Step</span>
<span className="pl-2 dark:text-blue-300 text-blue-600">1. Navigate to your project directory: cd docubook</span>
<span className="pl-2 dark:text-blue-300 text-blue-600">2. Install dependencies: npm install</span>
<span className="pl-2 dark:text-blue-300 text-blue-600">3. Start the development server: npm run dev</span>
</AnimatedSpan>
<TypingAnimation delay={6500} className="text-muted-foreground text-left pl-6">
Open the apps via browser http://localhost:3000.
</TypingAnimation>
</Terminal>
);
}

View File

@@ -1,40 +0,0 @@
"use client";
import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Sun className="h-[1.1rem] w-[1.1rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.1rem] w-[1.1rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -0,0 +1,61 @@
"use client"
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem }

46
components/ui/toggle.tsx Normal file
View File

@@ -0,0 +1,46 @@
"use client"
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-2 min-w-9",
sm: "h-8 px-1.5 min-w-8",
xs: "h-7 px-1 min-w-7",
lg: "h-10 px-2.5 min-w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }