v.1.13.0 add: context menu

This commit is contained in:
gitfromwildan
2025-05-29 19:16:34 +07:00
parent 36242e6942
commit d9ce3893e6
29 changed files with 340 additions and 128 deletions

View File

@@ -15,7 +15,7 @@ export default function Home() {
return (
<div className="flex flex-col items-center justify-center px-2 py-8 text-center sm:py-36">
<Link
href="/docs/getting-started/changelog"
href="/docs/changelog/version-1"
className="mb-5 sm:text-lg flex items-center gap-2 underline underline-offset-4 sm:-mt-12"
>
<div className="z-10 flex min-h-10 items-center justify-center max-[800px]:mt-10">
@@ -25,7 +25,7 @@ export default function Home() {
)}
>
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-100 hover:duration-300 hover:dark:text-neutral-200">
<span>🚀 New Version - Release v1.12.0</span>
<span>🚀 New Version - Release v1.13.0</span>
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</AnimatedShinyText>
</div>

View File

@@ -0,0 +1,133 @@
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import { ROUTES, EachRoute } from "@/lib/routes-config";
import { cn } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import * as LucideIcons from "lucide-react";
import { ChevronsUpDown, Check, type LucideIcon } from "lucide-react";
interface ContextPopoverProps {
className?: string;
}
// Get all root-level routes with context
function getContextRoutes(): EachRoute[] {
return ROUTES.filter(route => route.context);
}
// Get the first item's href from a route
function getFirstItemHref(route: EachRoute): string {
return route.items?.[0]?.href ? `${route.href}${route.items[0].href}` : route.href;
}
// Get the active context route from the current path
function getActiveContextRoute(path: string): EachRoute | undefined {
if (!path.startsWith('/docs')) return undefined;
const docPath = path.replace(/^\/docs/, '');
return getContextRoutes().find(route => docPath.startsWith(route.href));
}
// Get icon component by name
function getIcon(name: string) {
const Icon = LucideIcons[name as keyof typeof LucideIcons] as LucideIcon | undefined;
if (!Icon) return <LucideIcons.FileQuestion className="h-4 w-4" />;
return <Icon className="h-4 w-4" />;
}
export default function ContextPopover({ className }: ContextPopoverProps) {
const pathname = usePathname();
const router = useRouter();
const [activeRoute, setActiveRoute] = useState<EachRoute>();
const contextRoutes = getContextRoutes();
useEffect(() => {
if (pathname.startsWith("/docs")) {
setActiveRoute(getActiveContextRoute(pathname));
} else {
setActiveRoute(undefined);
}
}, [pathname]);
if (!pathname.startsWith("/docs") || contextRoutes.length === 0) {
return null;
}
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
className={cn(
"w-full max-w-[240px] flex items-center justify-between font-semibold text-foreground px-0 pt-8",
"hover:bg-transparent hover:text-foreground",
className
)}
>
<div className="flex items-center gap-2">
{activeRoute?.context?.icon && (
<span className="text-primary bg-primary/10 border border-primary rounded p-0.5">
{getIcon(activeRoute.context.icon)}
</span>
)}
<span className="truncate text-sm">
{activeRoute?.context?.title || activeRoute?.title || 'Select context'}
</span>
</div>
<ChevronsUpDown className="h-4 w-4 text-foreground/50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-64 p-2"
align="start"
sideOffset={6}
>
<div className="space-y-1">
{contextRoutes.map((route) => {
const isActive = activeRoute?.href === route.href;
const firstItemPath = getFirstItemHref(route);
const contextPath = `/docs${firstItemPath}`;
return (
<button
key={route.href}
onClick={() => router.push(contextPath)}
className={cn(
"relative flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm",
"text-left outline-none transition-colors",
isActive
? "bg-primary/20 text-primary"
: "text-foreground/80 hover:bg-primary/20"
)}
>
{route.context?.icon && (
<span className={cn(
"flex h-4 w-4 items-center justify-center",
isActive ? "text-primary" : "text-foreground/60"
)}>
{getIcon(route.context.icon)}
</span>
)}
<div className="flex-1 min-w-0 overflow-hidden">
<div className="truncate font-medium">
{route.context?.title || route.title}
</div>
{route.context?.description && (
<div className="text-xs text-muted-foreground truncate text-ellipsis overflow-hidden max-w-full">
{route.context.description}
</div>
)}
</div>
{isActive && (
<Check className="h-3.5 w-3.5" />
)}
</button>
);
})}
</div>
</PopoverContent>
</Popover>
);
}

View File

@@ -1,44 +1,62 @@
"use client";
import { ROUTES } from "@/lib/routes-config";
import { ROUTES, EachRoute } from "@/lib/routes-config";
import SubLink from "./sublink";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
interface DocsMenuProps {
isSheet?: boolean;
className?: string;
}
// Get the current context from the path
function getCurrentContext(path: string): string | undefined {
if (!path.startsWith('/docs')) return undefined;
// Extract the first segment after /docs/
const match = path.match(/^\/docs\/([^\/]+)/);
return match ? match[1] : undefined;
}
// Get the route that matches the current context
function getContextRoute(contextPath: string): EachRoute | undefined {
return ROUTES.find(route => {
const normalizedHref = route.href.replace(/^\/+|\/+$/g, '');
return normalizedHref === contextPath;
});
}
export default function DocsMenu({ isSheet = false, className = "" }: DocsMenuProps) {
const pathname = usePathname();
// Skip rendering if not on a docs page
if (!pathname.startsWith("/docs")) return null;
// Get the current context
const currentContext = getCurrentContext(pathname);
// Get the route for the current context
const contextRoute = currentContext ? getContextRoute(currentContext) : undefined;
// If no context route is found, don't render anything
if (!contextRoute) return null;
return (
<nav
aria-label="Documentation navigation"
className={className}
className={cn("transition-all duration-200", className)}
>
<ul className="flex flex-col gap-3.5 mt-5 pr-2 pb-6">
{ROUTES.map((item, index) => {
// Normalize href - hapus leading/trailing slashes
const normalizedHref = `/${item.href.replace(/^\/+|\/+$/g, '')}`;
const itemHref = `/docs${normalizedHref}`;
const modifiedItems = {
...item,
href: itemHref,
level: 0,
isSheet,
};
return (
<li key={`${item.title}-${index}`}>
<SubLink {...modifiedItems} />
<ul className="flex flex-col gap-1.5 py-4">
{/* Display only the items from the current context */}
<li key={contextRoute.title}>
<SubLink
{...contextRoute}
href={`/docs${contextRoute.href}`}
level={0}
isSheet={isSheet}
/>
</li>
);
})}
</ul>
</nav>
);

View File

@@ -14,6 +14,7 @@ import { DialogTitle, DialogDescription } from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import DocsMenu from "@/components/docs-menu";
import { ModeToggle } from "@/components/theme-toggle";
import ContextPopover from "@/components/context-popover";
// Toggle Button Component
export function ToggleButton({
@@ -51,9 +52,14 @@ export function Leftbar() {
${collapsed ? "w-[24px]" : "w-[280px]"} flex flex-col pr-2`}
>
<ToggleButton collapsed={collapsed} onToggle={toggleCollapse} />
{/* Scrollable DocsMenu */}
{/* Scrollable Content */}
<ScrollArea className="flex-1 px-0.5 pb-4">
{!collapsed && <DocsMenu />}
{!collapsed && (
<div className="space-y-2">
<ContextPopover />
<DocsMenu />
</div>
)}
</ScrollArea>
</aside>
);
@@ -81,7 +87,8 @@ export function SheetLeftbar() {
<div className="flex flex-col gap-2.5 mt-3 mx-2 px-5">
<NavMenu isSheet />
</div>
<div className="mx-2 px-5">
<div className="mx-2 px-5 space-y-2">
<ContextPopover />
<DocsMenu isSheet />
</div>
<div className="flex w-2/4 px-5">

View File

@@ -8,6 +8,23 @@ date: 24-05-2025
> This changelog contains a list of all the changes made to the DocuBook template. It will be updated with each new release and will include information about new features, bug fixes, and other improvements.
<div className="sr-only">
### v 1.13.0
</div>
<Release version="1.13.0" date="2025-05-29" title="Context Menu for organize file and folder">
<Changes type="added">
- New ContextMenu component for organizing file and folder
- Nested docs folder and file support with context menu
</Changes>
<Changes type="improved">
- improve routes-config with context menu
- improve docu.json with context menu
- improve leftbar with context menu
- improve docs-menu with context menu
</Changes>
</Release>
<div className="sr-only">
### v 1.12.0
</div>

View File

@@ -41,9 +41,9 @@ The Card component is a component used to create cards that can be used to displ
<Tabs defaultValue="link" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="link">Card with Link & Icon</TabsTrigger>
<TabsTrigger value="horizontal">Card Horizontal</TabsTrigger>
<TabsTrigger value="simple">Card Simple</TabsTrigger>
<TabsTrigger value="link">Link & Icon</TabsTrigger>
<TabsTrigger value="horizontal">Horizontal</TabsTrigger>
<TabsTrigger value="simple">Simple</TabsTrigger>
</TabsList>
<TabsContent value="link">
```markdown

View File

@@ -1,9 +0,0 @@
---
title: Components
description: This section provides an overview of the custom components available in DocuBook.
date: 29-11-2024
---
Explore the custom components we've defined for easy integration and development within your projects. Each component is designed to enhance your workflow and streamline your development process.
<Outlet path="getting-started/components" />

View File

@@ -90,5 +90,3 @@ For both options, ensure that you add the variable to `tailwind.config.ts`:
}
}
```
For theme and colors, refer to the [Theme section](/docs/getting-started/themes)

View File

@@ -15,7 +15,7 @@ To get started, you can clone the DocuBook repository directly from GitHub.
Begin by cloning the DocuBook repository from GitHub:
```bash
git clone --branch starter https://gitlab.com/mywildancloud/docubook.git
git clone --branch main https://github.com/DocuBook/docubook.git
```
</StepperItem>

View File

@@ -13,7 +13,7 @@ DocuBook is proudly **open-source**! 🎉 We believe in creating an accessible,
<Note title="Contribute">
Interested in helping us improve? Check out our [GitHub
repository](https://github.com/gitfromwildan/docubook) to get started! From
repository](https://github.com/DocuBook/docubook) to get started! From
feature suggestions to bug fixes, all contributions are welcome.
</Note>

View File

@@ -4,11 +4,11 @@ description: Get up and running with DocuBook in minutes with this comprehensive
date: 20-05-2025
---
Welcome to DocuBook! This guide will help you set up your documentation site quickly and efficiently.
Welcome to DocuBook! This guide will help you set up and customize your documentation site efficiently.
## Prerequisites
Before we begin, make sure you have the following installed:
Before we begin, ensure you have the following installed:
- [Git](https://git-scm.com/)
- [Node.js 18+](https://nodejs.org/) or [Bun 1.0+](https://bun.sh/)
@@ -16,112 +16,130 @@ Before we begin, make sure you have the following installed:
## Installation
<Note type="note" title="Note">
Follow the instructions on the [installation page](/docs/getting-started/installation) to install the required dependencies and set up your project.
<Note type="note" title="Initial Setup">
Follow the [installation guide](/docs/getting-started/installation) to set up your project dependencies and configuration.
</Note>
## Project Setup
### Customize Branding
### Configuration
<Stepper>
<StepperItem title="Step 1: Add Favicon">
Place your favicon at `public/favicon.ico`
<StepperItem title="Add Favicon">
Place your favicon at `public/favicon.ico` for browser tab display.
</StepperItem>
<StepperItem title="Step 2: Add Logo">
Place your logo at `public/images/docu.svg` (SVG format recommended)
<StepperItem title="Add Logo">
Add your logo at `public/images/docu.svg` (SVG format recommended for scalability).
</StepperItem>
<StepperItem title="Step 3: Update Site Info">
Edit `docu.json` to update site name, description, and other settings
<StepperItem title="Update Site Information">
Customize your site's metadata in `docu.json`:
- Site title and description
- Navigation structure
- Default theme settings
</StepperItem>
</Stepper>
## Creating Content
## Content Management
### File Structure
DocuBook uses the following structure:
DocuBook organizes content in a hierarchical structure:
````plaintext
```plaintext
contents/
docs/
getting-started/
quick-start-guide/ <-- You are here
docs/ # Main documentation directory
getting-started/ # Section for getting started guides
quick-start-guide/ # Current guide
index.mdx # Main content file
guides/ # Additional documentation sections
components/ # Component-specific documentation
index.mdx
guides/
components/
index.mdx
````
```
### Adding New Pages
### Creating New Content
<Stepper>
<StepperItem title="Step 1: Create a New Folder">
Create a new folder in `contents/docs/getting-started/` for your content section
<StepperItem title="1. Create Content Directory">
Organize your documentation by creating a new directory:
```bash
mkdir -p contents/docs/getting-started/your-page-title
mkdir -p contents/docs/your-section/your-topic
```
Example for an API reference:
```bash
mkdir -p contents/docs/api/authentication
```
</StepperItem>
<StepperItem title="Step 2: Add MDX File">
Create an `index.mdx` file with frontmatter:
<StepperItem title="2. Create MDX Content">
Add an `index.mdx` file with frontmatter metadata:
````markdown
---
title: Your Page Title
description: Brief description of your page
date: 20-05-2025
title: Authentication
description: Learn how to implement user authentication
date: 2025-05-29
---
# Your Content Here
Your comprehensive guide to implementing authentication in your application.
Start writing your documentation using Markdown or MDX components.
## Getting Started
Start by setting up your authentication provider...
````
</StepperItem>
<StepperItem title="Step 3: Add to Navigation">
Update the navigation in `docu.json`:
```json:docu.json showLineNumbers {8}
<StepperItem title="3. Update Navigation">
Add your new section to the navigation in `docu.json`. Here's how to add the authentication section we created earlier:
```json:docu.json showLineNumbers {4-16}
{
"routes": [
// ... existing routes ...
{
"title": "Getting Started",
"href": "/getting-started",
"title": "API",
"href": "/api",
"noLink": true,
"context": {
"icon": "Code2",
"description": "API Reference and Integration",
"title": "API"
},
"items": [
{ "title": "Your Page Title", "href": "/your-page-title" } // Add your page here separated by comma `,`
{ "title": "Authentication", "href": "/authentication" }
]
}
]
}
```
This will add a new "API" section with an "Authentication" page under it. The `context` object defines how this section appears in the navigation, including its icon and description.
</StepperItem>
</Stepper>
## Development
## Development Workflow
Start the development server:
### Local Development
Start the development server with live reload:
```bash
# Using npm
npm run dev
# or
# Or using Bun
bun dev
```
Visit [http://localhost:3000](http://localhost:3000) to see your site.
Access your site at [http://localhost:3000](http://localhost:3000)
## Building for Production
### Building for Production
When you're ready to deploy:
When ready to deploy:
```bash
# Build the production version
npm run build
# Start the production server
npm start
```
## Need Help?
<Note type="info">
Check out the [Components Guide](/docs/getting-started/components) to learn about all available components.
</Note>
<Note type="warning">
Make sure to commit your changes to version control before deploying.
</Note>

View File

@@ -61,14 +61,28 @@
"title": "Getting Started",
"href": "/getting-started",
"noLink": true,
"context": {
"icon": "Book",
"description": "Set up your Documentation",
"title": "Guides"
},
"items": [
{ "title": "Introduction", "href": "/introduction" },
{ "title": "Installation", "href": "/installation" },
{ "title": "Quick Start Guide", "href": "/quick-start-guide" },
{ "title": "Project Structure", "href": "/project-structure" },
{ "title": "Customize", "href": "/customize" }
]
},
{
"title": "Components",
"href": "/components",
"noLink": true,
"context": {
"icon": "Layers",
"description": "Write with Markdown",
"title": "Markdown"
},
"items": [
{ "title": "Accordion", "href": "/accordion" },
{ "title": "Button", "href": "/button" },
@@ -88,8 +102,17 @@
{ "title": "Custom", "href": "/custom" }
]
},
{ "title": "Customize", "href": "/customize" },
{ "title": "Changelog", "href": "/changelog" }
{
"title": "Changelog",
"href": "/changelog",
"noLink": true,
"context": {
"icon": "History",
"description": "Updates and changes",
"title": "Changelog"
},
"items": [
{ "title": "Version 1.0+", "href": "/version-1" }
]
}
]

View File

@@ -1,9 +1,16 @@
import docuConfig from "@/docu.json"; // Import JSON file
export type ContextInfo = {
icon: string;
description: string;
title?: string;
};
export type EachRoute = {
title: string;
href: string;
noLink?: boolean; // Sekarang mendukung boolean
noLink?: boolean;
context?: ContextInfo;
items?: EachRoute[];
};

View File

@@ -1,6 +1,6 @@
{
"name": "docubook",
"version": "1.12.0",
"version": "1.13.0",
"private": true,
"scripts": {
"dev": "next dev",