Compare commits

...

11 Commits

Author SHA1 Message Date
Dwindi Ramadhana
259496bc86 Initial commit of WooNooW Docs 2026-02-01 00:10:31 +07:00
Wildan Nursahidan
217310888c Merge pull request #14 from DocuBook/dev-36d3a93
chore: Sync package version v2.0.0-beta.1
2026-01-18 20:47:45 +07:00
Bot DocuBook
039d8d5a50 chore: Sync package version v2.0.0-beta.1 2026-01-18 13:42:31 +00:00
Wildan Nursahidan
6ea39676c8 Merge pull request #13
chore: Sync package version v1.16.1

- Introduce language-specific icons in code blocks using react-icons
- Implement custom handleCodeTitles rehype plugin to support data-title attribute
- Refactor PreMdx component to display language icons and titles
- Add comprehensive styling for code blocks including header, actions, and syntax highlighting
- Update dependencies to include react-icons and remove unused install package
- Bump version to 1.16.1 in both root and dist package.json files
2025-08-12 21:36:49 +07:00
Bot DocuBook
4a6d0da72a chore: Sync package version v1.16.1 2025-08-12 14:32:00 +00:00
Wildan Nursahidan
5060fd2154 Merge pull request #12 from DocuBook/dev-9dd141d
chore: Sync package version v1.16.0
feat(accordion): add accordion group context and update styling
- Introduce `AccordionGroupContext` to support grouped accordions
- Update styling to differentiate standalone and grouped accordions
- Add background hover states for improved interactivity
- Register `AccordionGroup` in markdown components
2025-08-10 19:07:31 +07:00
Bot DocuBook
2bca9eb28e chore: Sync package version v1.16.0 2025-08-10 12:04:53 +00:00
Wildan Nursahidan
e192899446 Merge pull request #11
feat(accordion): add icon support and update styling
- Added optional `icon` prop to Accordion component using lucide-react icons
- Updated default image placeholder path in markdown examples and snippets
- Removed unused className prop and adjusted styling classes
- Fixed markdown image paths in documentation files
- Updated docu.json navigation structure and corrected icon name typo
2025-08-10 13:46:20 +07:00
Bot DocuBook
9b6ef22262 chore: Sync package version v1.15.3 2025-08-10 06:41:17 +00:00
Wildan Nursahidan
fc46b72224 Merge pull request #10
chore: Sync package version v1.15.1 

- clean project scaffolding
- add schema json
- validate docu.schema.json
- guide to writing markdown format examples
- how to writing changelog by release note components
2025-08-10 01:08:22 +07:00
Bot DocuBook
e13ba8c9e9 chore: Sync package version v1.15.1 2025-08-09 18:04:38 +00:00
85 changed files with 2792 additions and 3488 deletions

View File

@@ -2,7 +2,7 @@
"DocuImage": { "DocuImage": {
"prefix": "image", "prefix": "image",
"body": [ "body": [
"![${1:Alt text for the image}](${2:https://via.placeholder.com/150})" "![${1:Alt text for the image}](${2:/images/example-img.png})"
], ],
"description": "Snippet untuk menampilkan image komponen." "description": "Snippet untuk menampilkan image komponen."
}, },

View File

@@ -12,13 +12,19 @@ import MobToc from "@/components/mob-toc";
const { meta } = docuConfig; const { meta } = docuConfig;
type PageProps = { type PageProps = {
params: { params: Promise<{
slug: string[]; slug: string[];
}; }>;
}; };
// Function to generate metadata dynamically // Function to generate metadata dynamically
export async function generateMetadata({ params: { slug = [] } }: PageProps) { export async function generateMetadata(props: PageProps) {
const params = await props.params;
const {
slug = []
} = params;
const pathName = slug.join("/"); const pathName = slug.join("/");
const res = await getDocsForSlug(pathName); const res = await getDocsForSlug(pathName);
@@ -62,7 +68,13 @@ export async function generateMetadata({ params: { slug = [] } }: PageProps) {
}; };
} }
export default async function DocsPage({ params: { slug = [] } }: PageProps) { export default async function DocsPage(props: PageProps) {
const params = await props.params;
const {
slug = []
} = params;
const pathName = slug.join("/"); const pathName = slug.join("/");
const res = await getDocsForSlug(pathName); const res = await getDocsForSlug(pathName);

View File

@@ -6,6 +6,9 @@ import { GeistMono } from "geist/font/mono";
import { Footer } from "@/components/footer"; import { Footer } from "@/components/footer";
import docuConfig from "@/docu.json"; import docuConfig from "@/docu.json";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import "@docsearch/css";
import "@/styles/algolia.css";
import "@/styles/syntax.css";
import "@/styles/globals.css"; import "@/styles/globals.css";
const { meta } = docuConfig; const { meta } = docuConfig;

View File

@@ -25,21 +25,21 @@ 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"> <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.15.1</span> <span>🚀 Release v2.0.0-beta.1</span>
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" /> <ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</AnimatedShinyText> </AnimatedShinyText>
</div> </div>
</div> </div>
</Link> </Link>
<div className="w-full max-w-[800px] pb-8"> <div className="w-full max-w-[800px] pb-8">
<h1 className="mb-4 text-2xl font-bold sm:text-5xl">DocuBook Starter Templates</h1> <h1 className="mb-4 text-2xl font-bold sm:text-5xl">DocuBook Starter Templates</h1>
<p className="mb-8 sm:text-xl text-muted-foreground"> <p className="mb-8 sm:text-xl text-muted-foreground">
Get started by editing app/page.tsx . Save and see your changes instantly.{' '} Get started by editing app/page.tsx . Save and see your changes instantly.{' '}
<Link className="text-primary underline" href="https://www.docubook.pro/docs/getting-started/introduction" target="_blank"> <Link className="text-primary underline" href="https://www.docubook.pro/docs/getting-started/introduction" target="_blank">
Read Documentations Read Documentations
</Link> </Link>
</p> </p>
</div> </div>
<div className="flex flex-row items-center gap-6 mb-10"> <div className="flex flex-row items-center gap-6 mb-10">
<Link <Link
href={`/docs${page_routes[0].href}`} href={`/docs${page_routes[0].href}`}

View File

@@ -50,6 +50,7 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSearchedInput(""); setSearchedInput("");
} }
}, [isOpen]); }, [isOpen]);
@@ -71,9 +72,9 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
return advanceSearch(trimmedInput) as unknown as SearchResult[]; return advanceSearch(trimmedInput) as unknown as SearchResult[];
}, [searchedInput]); }, [searchedInput]);
useEffect(() => { // useEffect(() => {
setSelectedIndex(0); // setSelectedIndex(0);
}, [filteredResults]); // }, [filteredResults]);
useEffect(() => { useEffect(() => {
const handleNavigation = (event: KeyboardEvent) => { const handleNavigation = (event: KeyboardEvent) => {
@@ -117,7 +118,10 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
<input <input
value={searchedInput} value={searchedInput}
onChange={(e) => setSearchedInput(e.target.value)} onChange={(e) => {
setSearchedInput(e.target.value);
setSelectedIndex(0);
}}
placeholder="Type something to search..." placeholder="Type something to search..."
autoFocus autoFocus
className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full" className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full"
@@ -138,38 +142,38 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
const isActive = index === selectedIndex; const isActive = index === selectedIndex;
return ( return (
<DialogClose key={item.href} asChild> <DialogClose key={item.href} asChild>
<Anchor <Anchor
ref={(el) => { ref={(el) => {
itemRefs.current[index] = el as HTMLDivElement | null; itemRefs.current[index] = el as HTMLDivElement | null;
}} }}
className={cn(
"dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5",
isActive && "bg-primary/20 dark:bg-primary/30",
paddingClass
)}
href={`/docs${item.href}`}
tabIndex={-1}
>
<div
className={cn( className={cn(
"dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5", "flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
isActive && "bg-primary/20 dark:bg-primary/30", level > 1 && "border-l pl-4"
paddingClass
)} )}
href={`/docs${item.href}`} >
tabIndex={-1} <div className="flex items-center">
> <FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
<div <span>{item.title}</span>
className={cn(
"flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
level > 1 && "border-l pl-4"
)}
>
<div className="flex items-center">
<FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
<span>{item.title}</span>
</div>
{isActive && (
<div className="hidden md:flex items-center text-xs text-muted-foreground">
<span>Return</span>
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
</div>
)}
</div> </div>
</Anchor> {isActive && (
</DialogClose> <div className="hidden md:flex items-center text-xs text-muted-foreground">
<span>Return</span>
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
</div>
)}
</div>
</Anchor>
</DialogClose>
); );
})} })}
</div> </div>
@@ -177,14 +181,14 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
<DialogFooter className="md:flex md:justify-start hidden h-14 px-6 bg-transparent border-t text-[14px] outline-none"> <DialogFooter className="md:flex md:justify-start hidden h-14 px-6 bg-transparent border-t text-[14px] outline-none">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2"> <span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
<ArrowUpIcon className="w-3 h-3"/> <ArrowUpIcon className="w-3 h-3" />
</span> </span>
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2"> <span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
<ArrowDownIcon className="w-3 h-3"/> <ArrowDownIcon className="w-3 h-3" />
</span> </span>
<p className="text-muted-foreground">to navigate</p> <p className="text-muted-foreground">to navigate</p>
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2"> <span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
<CornerDownLeftIcon className="w-3 h-3"/> <CornerDownLeftIcon className="w-3 h-3" />
</span> </span>
<p className="text-muted-foreground">to select</p> <p className="text-muted-foreground">to select</p>
<span className="dark:bg-accent/15 bg-slate-200 border rounded px-2 py-1"> <span className="dark:bg-accent/15 bg-slate-200 border rounded px-2 py-1">

View File

@@ -45,6 +45,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
useEffect(() => { useEffect(() => {
if (pathname.startsWith("/docs")) { if (pathname.startsWith("/docs")) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setActiveRoute(getActiveContextRoute(pathname)); setActiveRoute(getActiveContextRoute(pathname));
} else { } else {
setActiveRoute(undefined); setActiveRoute(undefined);
@@ -61,7 +62,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
<Button <Button
variant="ghost" variant="ghost"
className={cn( className={cn(
"w-full max-w-[240px] flex items-center justify-between font-semibold text-foreground px-0 pt-8", "w-full max-w-[240px] cursor-pointer flex items-center justify-between font-semibold text-foreground px-0 pt-8",
"hover:bg-transparent hover:text-foreground", "hover:bg-transparent hover:text-foreground",
className className
)} )}

View File

@@ -0,0 +1,4 @@
import { createContext } from 'react';
// Create a context to check if a component is inside an accordion group
export const AccordionGroupContext = createContext<{ inGroup: boolean } | null>(null);

View File

@@ -1,8 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types"; import { type ThemeProviderProps } from "next-themes";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return <NextThemesProvider {...props}>{children}</NextThemesProvider>;

View File

@@ -10,53 +10,27 @@ interface DocsMenuProps {
className?: string; 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) { export default function DocsMenu({ isSheet = false, className = "" }: DocsMenuProps) {
const pathname = usePathname(); const pathname = usePathname();
// Skip rendering if not on a docs page
if (!pathname.startsWith("/docs")) return null; 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 ( return (
<nav <nav
aria-label="Documentation navigation" aria-label="Documentation navigation"
className={cn("transition-all duration-200", className)} className={cn("transition-all duration-200", className)}
> >
<ul className="flex flex-col gap-1.5 py-4"> <ul className="flex flex-col gap-1.5 py-4">
{/* Display only the items from the current context */} {ROUTES.map((route, index) => (
<li key={contextRoute.title}> <li key={route.title + index}>
<SubLink <SubLink
{...contextRoute} {...route}
href={`/docs${contextRoute.href}`} href={`/docs${route.href}`}
level={0} level={0}
isSheet={isSheet} isSheet={isSheet}
/> />
</li> </li>
))}
</ul> </ul>
</nav> </nav>
); );

View File

@@ -29,7 +29,7 @@ export function ToggleButton({
<Button <Button
size="icon" size="icon"
variant="outline" variant="outline"
className="hover:bg-transparent hover:text-inherit border-none text-muted-foreground" className="cursor-pointer hover:bg-transparent hover:text-inherit border-none text-muted-foreground"
onClick={onToggle} onClick={onToggle}
> >
{collapsed ? ( {collapsed ? (

View File

@@ -0,0 +1,31 @@
"use client"
import React, { ReactNode } from "react";
import clsx from "clsx";
import { AccordionGroupContext } from "@/components/contexts/AccordionContext";
interface AccordionGroupProps {
children: ReactNode;
className?: string;
}
const AccordionGroup: React.FC<AccordionGroupProps> = ({ children, className }) => {
return (
// Wrap all children with the AccordionGroupContext.Provider
// so that any nested accordions know they are inside a group.
// This enables group-specific behavior in child components.
<AccordionGroupContext.Provider value={{ inGroup: true }}>
<div
className={clsx(
"border rounded-lg overflow-hidden",
className
)}
>
{children}
</div>
</AccordionGroupContext.Provider>
);
};
export default AccordionGroup;

View File

@@ -1,42 +1,57 @@
"use client"; "use client";
import { ReactNode, useState } from 'react'; import { ReactNode, useState, useContext } from 'react';
import { ChevronRight } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
import * as Icons from "lucide-react";
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { AccordionGroupContext } from '@/components/contexts/AccordionContext';
type AccordionProps = { type AccordionProps = {
title: string; title: string;
children?: ReactNode; children?: ReactNode;
defaultOpen?: boolean; defaultOpen?: boolean;
className?: string; icon?: keyof typeof Icons;
}; };
const Accordion = ({ const Accordion: React.FC<AccordionProps> = ({
title, title,
children, children,
defaultOpen = false, defaultOpen = false,
className, icon,
}: AccordionProps) => { }: AccordionProps) => {
const groupContext = useContext(AccordionGroupContext);
const isInGroup = groupContext?.inGroup === true;
const [isOpen, setIsOpen] = useState(defaultOpen); const [isOpen, setIsOpen] = useState(defaultOpen);
const Icon = icon ? (Icons[icon] as React.FC<{ className?: string }>) : null;
// The main wrapper div for the accordion.
// All styling logic for the accordion container is handled here.
return ( return (
<div className={cn("border rounded-lg overflow-hidden", className)}> <div
className={cn(
// Style for STANDALONE: full card with border & shadow
!isInGroup && "border rounded-lg shadow-sm",
// Style for IN GROUP: only a bottom border separator
isInGroup && "border-b last:border-b-0 border-border"
)}
>
<button <button
type="button" type="button"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
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" className="flex items-center gap-2 w-full px-4 h-12 transition-colors bg-muted/40 dark:bg-muted/20 hover:bg-muted/70 dark:hover:bg-muted/70"
> >
<ChevronRight <ChevronRight
className={cn( className={cn(
"w-4 h-4 text-muted-foreground transition-transform duration-200", "w-4 h-4 text-muted-foreground transition-transform duration-200 flex-shrink-0",
isOpen && "rotate-90" isOpen && "rotate-90"
)} )}
/> />
<h3 className="font-medium text-base text-foreground pb-2">{title}</h3> {Icon && <Icon className="text-foreground w-4 h-4 flex-shrink-0" />}
<h3 className="font-medium text-base text-foreground !m-0 leading-none">{title}</h3>
</button> </button>
{isOpen && ( {isOpen && (
<div className="px-4 py-3 border-t dark:bg-muted/50 bg-muted/15"> <div className="px-4 py-3 dark:bg-muted/10 bg-muted/15">
{children} {children}
</div> </div>
)} )}

View File

@@ -8,13 +8,21 @@ interface CardGroupProps {
} }
const CardGroup: React.FC<CardGroupProps> = ({ children, cols = 2, className }) => { const CardGroup: React.FC<CardGroupProps> = ({ children, cols = 2, className }) => {
const cardsArray = React.Children.toArray(children); // Pastikan children berupa array const cardsArray = React.Children.toArray(children);
// Static grid column classes for Tailwind v4 compatibility
const gridColsClass = {
1: "grid-cols-1",
2: "grid-cols-1 sm:grid-cols-2",
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
}[cols] || "grid-cols-1 sm:grid-cols-2";
return ( return (
<div <div
className={clsx( className={clsx(
"grid gap-4 text-foreground", "grid gap-4 text-foreground",
`grid-cols-1 sm:grid-cols-${cols}`, gridColsClass,
className className
)} )}
> >

View File

@@ -100,8 +100,8 @@ export const Files = ({ children }: { children: ReactNode }) => {
return ( return (
<div <div
className=" className="
rounded-xl border border-muted/50 rounded-xl border border-muted/20
bg-card/50 backdrop-blur-sm bg-card/20 backdrop-blur-sm
shadow-sm overflow-hidden shadow-sm overflow-hidden
transition-all duration-200 transition-all duration-200
hover:shadow-md hover:border-muted/60 hover:shadow-md hover:border-muted/60

View File

@@ -4,13 +4,17 @@ import NextImage from "next/image";
type Height = ComponentProps<typeof NextImage>["height"]; type Height = ComponentProps<typeof NextImage>["height"];
type Width = ComponentProps<typeof NextImage>["width"]; type Width = ComponentProps<typeof NextImage>["width"];
type ImageProps = Omit<ComponentProps<"img">, "src"> & {
src?: ComponentProps<typeof NextImage>["src"];
};
export default function Image({ export default function Image({
src, src,
alt = "alt", alt = "alt",
width = 800, width = 800,
height = 350, height = 350,
...props ...props
}: ComponentProps<"img">) { }: ImageProps) {
if (!src) return null; if (!src) return null;
return ( return (
<NextImage <NextImage

View File

@@ -1,18 +1,108 @@
import { ComponentProps } from "react"; import { type ComponentProps, type JSX } from "react";
import Copy from "./CopyMdx"; import Copy from "./CopyMdx";
import {
SiJavascript,
SiTypescript,
SiReact,
SiPython,
SiGo,
SiPhp,
SiRuby,
SiSwift,
SiKotlin,
SiHtml5,
SiCss3,
SiSass,
SiPostgresql,
SiGraphql,
SiYaml,
SiToml,
SiDocker,
SiNginx,
SiGit,
SiGnubash,
SiMarkdown,
} from "react-icons/si";
import { FaJava, FaCode } from "react-icons/fa";
import { TbJson } from "react-icons/tb";
type PreProps = ComponentProps<"pre"> & {
raw?: string;
"data-title"?: string;
};
// Component to display an icon based on the programming language
const LanguageIcon = ({ lang }: { lang: string }) => {
const iconProps = { className: "w-4 h-4" };
const languageToIconMap: Record<string, JSX.Element> = {
gitignore: <SiGit {...iconProps} />,
docker: <SiDocker {...iconProps} />,
dockerfile: <SiDocker {...iconProps} />,
nginx: <SiNginx {...iconProps} />,
sql: <SiPostgresql {...iconProps} />,
graphql: <SiGraphql {...iconProps} />,
yaml: <SiYaml {...iconProps} />,
yml: <SiYaml {...iconProps} />,
toml: <SiToml {...iconProps} />,
json: <TbJson {...iconProps} />,
md: <SiMarkdown {...iconProps} />,
markdown: <SiMarkdown {...iconProps} />,
bash: <SiGnubash {...iconProps} />,
sh: <SiGnubash {...iconProps} />,
shell: <SiGnubash {...iconProps} />,
swift: <SiSwift {...iconProps} />,
kotlin: <SiKotlin {...iconProps} />,
kt: <SiKotlin {...iconProps} />,
kts: <SiKotlin {...iconProps} />,
rb: <SiRuby {...iconProps} />,
ruby: <SiRuby {...iconProps} />,
php: <SiPhp {...iconProps} />,
go: <SiGo {...iconProps} />,
py: <SiPython {...iconProps} />,
python: <SiPython {...iconProps} />,
java: <FaJava {...iconProps} />,
tsx: <SiReact {...iconProps} />,
typescript: <SiTypescript {...iconProps} />,
ts: <SiTypescript {...iconProps} />,
jsx: <SiReact {...iconProps} />,
js: <SiJavascript {...iconProps} />,
javascript: <SiJavascript {...iconProps} />,
html: <SiHtml5 {...iconProps} />,
css: <SiCss3 {...iconProps} />,
scss: <SiSass {...iconProps} />,
sass: <SiSass {...iconProps} />,
};
return languageToIconMap[lang] || <FaCode {...iconProps} />;
};
// Function to extract the language from className
function getLanguage(className: string = ""): string {
const match = className.match(/language-(\w+)/);
return match ? match[1] : "default";
}
export default function Pre({ children, raw, ...rest }: PreProps) {
const { "data-title": title, className, ...restProps } = rest;
const language = getLanguage(className);
const hasTitle = !!title;
export default function Pre({
children,
raw,
...rest
}: ComponentProps<"pre"> & { raw?: string }) {
return ( return (
<div className="my-5 relative"> <div className="code-block-container">
<div className="absolute top-3 right-2.5 z-10 sm:block hidden"> <div className="code-block-actions">
<Copy content={raw!} /> {raw && <Copy content={raw} />}
</div> </div>
<div className="relative"> {hasTitle && (
<pre {...rest}>{children}</pre> <div className="code-block-header">
<div className="flex items-center gap-2">
<LanguageIcon lang={language} />
<span>{title}</span>
</div>
</div>
)}
<div className="code-block-body">
<pre className={className} {...restProps}>
{children}
</pre>
</div> </div>
</div> </div>
); );

View File

@@ -14,7 +14,7 @@ interface MobTocProps {
tocs: TocItem[]; tocs: TocItem[];
} }
const useClickOutside = (ref: React.RefObject<HTMLElement>, callback: () => void) => { const useClickOutside = (ref: React.RefObject<HTMLElement | null>, callback: () => void) => {
const handleClick = React.useCallback((event: MouseEvent) => { const handleClick = React.useCallback((event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) { if (ref.current && !ref.current.contains(event.target as Node)) {
callback(); callback();

View File

@@ -32,6 +32,7 @@ export function ScrollToTop({
useEffect(() => { useEffect(() => {
// Initial check // Initial check
// eslint-disable-next-line react-hooks/set-state-in-effect
checkScroll(); checkScroll();
// Set up scroll listener with debounce for better performance // Set up scroll listener with debounce for better performance

View File

@@ -27,7 +27,7 @@ export default function SubLink({
parentHref = "", parentHref = "",
}: SubLinkProps) { }: SubLinkProps) {
const path = usePathname(); const path = usePathname();
const [isOpen, setIsOpen] = useState(level === 0); const [isOpen, setIsOpen] = useState(false);
// Full path including parent's href // Full path including parent's href
const fullHref = `${parentHref}${href}`; const fullHref = `${parentHref}${href}`;
@@ -44,6 +44,7 @@ export default function SubLink({
// Auto-expand if current path is a child of this item // Auto-expand if current path is a child of this item
useEffect(() => { useEffect(() => {
if (items && (path.startsWith(fullHref) && path !== fullHref)) { if (items && (path.startsWith(fullHref) && path !== fullHref)) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsOpen(true); setIsOpen(true);
} }
}, [path, fullHref, items]); }, [path, fullHref, items]);
@@ -85,7 +86,11 @@ export default function SubLink({
<div className={cn("flex flex-col gap-1 w-full")}> <div className={cn("flex flex-col gap-1 w-full")}>
<Collapsible open={isOpen} onOpenChange={setIsOpen}> <Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger <CollapsibleTrigger
className="w-full pr-5 text-left" className={cn(
"w-full pr-5 text-left rounded-md transition-colors",
isOpen && "bg-muted/30 pb-2 pt-2", // Background when open
hasActiveChild && "bg-primary/5" // Accent tint when child is active
)}
aria-expanded={isOpen} aria-expanded={isOpen}
aria-controls={`collapsible-${fullHref.replace(/[^a-zA-Z0-9]/g, '-')}`} aria-controls={`collapsible-${fullHref.replace(/[^a-zA-Z0-9]/g, '-')}`}
> >
@@ -103,13 +108,13 @@ export default function SubLink({
<CollapsibleContent <CollapsibleContent
id={`collapsible-${fullHref.replace(/[^a-zA-Z0-9]/g, '-')}`} id={`collapsible-${fullHref.replace(/[^a-zA-Z0-9]/g, '-')}`}
className={cn( className={cn(
"overflow-hidden transition-all duration-200 ease-in-out", "pl-3 overflow-hidden transition-all duration-200 ease-in-out",
isOpen ? "animate-collapsible-down" : "animate-collapsible-up" isOpen ? "animate-collapsible-down" : "animate-collapsible-up"
)} )}
> >
<div <div
className={cn( className={cn(
"flex flex-col items-start sm:text-sm text-foreground/80 ml-0.5 mt-2.5 gap-3 hover:[&_a]:text-foreground transition-colors", "flex flex-col items-start sm:text-sm text-foreground/80 ml-0.5 mt-2.5 gap-3 transition-colors",
level > 0 && "pl-4 border-l border-border ml-1.5" level > 0 && "pl-4 border-l border-border ml-1.5"
)} )}
> >

View File

@@ -1,68 +1,69 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { Moon, Sun, Monitor } from "lucide-react"; import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export function ModeToggle() { export function ModeToggle() {
const { theme, setTheme } = useTheme(); const { theme, setTheme, resolvedTheme } = useTheme();
const [selectedTheme, setSelectedTheme] = React.useState<string>("system"); const [mounted, setMounted] = React.useState(false);
// Pastikan toggle tetap di posisi yang benar setelah reload // Untuk menghindari hydration mismatch
React.useEffect(() => { React.useEffect(() => {
if (theme) { setMounted(true);
setSelectedTheme(theme); }, []);
// Jika belum mounted, jangan render apapun untuk menghindari mismatch
if (!mounted) {
return (
<div className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1">
<div className="rounded-full p-1 w-8 h-8" />
<div className="rounded-full p-1 w-8 h-8" />
</div>
);
}
// Tentukan theme yang aktif: gunakan resolvedTheme untuk menampilkan ikon yang sesuai
// jika theme === "system", resolvedTheme akan menjadi "light" atau "dark" sesuai device
const activeTheme = theme === "system" || !theme ? resolvedTheme : theme;
const handleToggle = () => {
// Toggle antara light dan dark
// Jika sekarang light, ganti ke dark, dan sebaliknya
if (activeTheme === "light") {
setTheme("dark");
} else { } else {
setSelectedTheme("system"); // Default ke system jika undefined setTheme("light");
} }
}, [theme]); };
return ( return (
<ToggleGroup <ToggleGroup
type="single" type="single"
value={selectedTheme} value={activeTheme}
onValueChange={(value) => { onValueChange={handleToggle}
if (value) {
setTheme(value);
setSelectedTheme(value);
}
}}
className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1 transition-all" className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1 transition-all"
> >
<ToggleGroupItem <ToggleGroupItem
value="light" value="light"
size="sm" size="sm"
aria-label="Light Mode" aria-label="Light Mode"
className={`rounded-full p-1 transition-all ${ className={`rounded-full p-1 transition-all ${activeTheme === "light"
selectedTheme === "light" ? "bg-primary text-primary-foreground"
? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted/50"
: "bg-transparent hover:bg-muted/50" }`}
}`}
> >
<Sun className="h-4 w-4" /> <Sun className="h-4 w-4" />
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem
value="system"
size="sm"
aria-label="System Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "system"
? "bg-primary text-primary-foreground"
: "bg-transparent hover:bg-muted/50"
}`}
>
<Monitor className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
value="dark" value="dark"
size="sm" size="sm"
aria-label="Dark Mode" aria-label="Dark Mode"
className={`rounded-full p-1 transition-all ${ className={`rounded-full p-1 transition-all ${activeTheme === "dark"
selectedTheme === "dark" ? "bg-primary text-primary-foreground"
? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted/50"
: "bg-transparent hover:bg-muted/50" }`}
}`}
> >
<Moon className="h-4 w-4" /> <Moon className="h-4 w-4" />
</ToggleGroupItem> </ToggleGroupItem>

View File

@@ -1,324 +0,0 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { renderToString } from "react-dom/server";
interface Icon {
x: number;
y: number;
z: number;
scale: number;
opacity: number;
id: number;
}
interface IconCloudProps {
icons?: React.ReactNode[];
images?: string[];
}
function easeOutCubic(t: number): number {
return 1 - Math.pow(1 - t, 3);
}
export function IconCloud({ icons, images }: IconCloudProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [iconPositions, setIconPositions] = useState<Icon[]>([]);
const [rotation] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [lastMousePos, setLastMousePos] = useState({ x: 0, y: 0 });
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
const [targetRotation, setTargetRotation] = useState<{
x: number;
y: number;
startX: number;
startY: number;
distance: number;
startTime: number;
duration: number;
} | null>(null);
const animationFrameRef = useRef<number>();
const rotationRef = useRef(rotation);
const iconCanvasesRef = useRef<HTMLCanvasElement[]>([]);
const imagesLoadedRef = useRef<boolean[]>([]);
// Create icon canvases once when icons/images change
useEffect(() => {
if (!icons && !images) return;
const items = icons || images || [];
imagesLoadedRef.current = new Array(items.length).fill(false);
const newIconCanvases = items.map((item, index) => {
const offscreen = document.createElement("canvas");
offscreen.width = 40;
offscreen.height = 40;
const offCtx = offscreen.getContext("2d");
if (offCtx) {
if (images) {
// Handle image URLs directly
const img = new Image();
img.crossOrigin = "anonymous";
img.src = items[index] as string;
img.onload = () => {
offCtx.clearRect(0, 0, offscreen.width, offscreen.height);
// Create circular clipping path
offCtx.beginPath();
offCtx.arc(20, 20, 20, 0, Math.PI * 2);
offCtx.closePath();
offCtx.clip();
// Draw the image
offCtx.drawImage(img, 0, 0, 40, 40);
imagesLoadedRef.current[index] = true;
};
} else {
// Handle SVG icons
offCtx.scale(0.4, 0.4);
const svgString = renderToString(item as React.ReactElement);
const img = new Image();
img.src = "data:image/svg+xml;base64," + btoa(svgString);
img.onload = () => {
offCtx.clearRect(0, 0, offscreen.width, offscreen.height);
offCtx.drawImage(img, 0, 0);
imagesLoadedRef.current[index] = true;
};
}
}
return offscreen;
});
iconCanvasesRef.current = newIconCanvases;
}, [icons, images]);
// Generate initial icon positions on a sphere
useEffect(() => {
const items = icons || images || [];
const newIcons: Icon[] = [];
const numIcons = items.length || 20;
// Fibonacci sphere parameters
const offset = 2 / numIcons;
const increment = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < numIcons; i++) {
const y = i * offset - 1 + offset / 2;
const r = Math.sqrt(1 - y * y);
const phi = i * increment;
const x = Math.cos(phi) * r;
const z = Math.sin(phi) * r;
newIcons.push({
x: x * 100,
y: y * 100,
z: z * 100,
scale: 1,
opacity: 1,
id: i,
});
}
setIconPositions(newIcons);
}, [icons, images]);
// Handle mouse events
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
const rect = canvasRef.current?.getBoundingClientRect();
if (!rect || !canvasRef.current) return;
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ctx = canvasRef.current.getContext("2d");
if (!ctx) return;
iconPositions.forEach((icon) => {
const cosX = Math.cos(rotationRef.current.x);
const sinX = Math.sin(rotationRef.current.x);
const cosY = Math.cos(rotationRef.current.y);
const sinY = Math.sin(rotationRef.current.y);
const rotatedX = icon.x * cosY - icon.z * sinY;
const rotatedZ = icon.x * sinY + icon.z * cosY;
const rotatedY = icon.y * cosX + rotatedZ * sinX;
const screenX = canvasRef.current!.width / 2 + rotatedX;
const screenY = canvasRef.current!.height / 2 + rotatedY;
const scale = (rotatedZ + 200) / 300;
const radius = 20 * scale;
const dx = x - screenX;
const dy = y - screenY;
if (dx * dx + dy * dy < radius * radius) {
const targetX = -Math.atan2(
icon.y,
Math.sqrt(icon.x * icon.x + icon.z * icon.z),
);
const targetY = Math.atan2(icon.x, icon.z);
const currentX = rotationRef.current.x;
const currentY = rotationRef.current.y;
const distance = Math.sqrt(
Math.pow(targetX - currentX, 2) + Math.pow(targetY - currentY, 2),
);
const duration = Math.min(2000, Math.max(800, distance * 1000));
setTargetRotation({
x: targetX,
y: targetY,
startX: currentX,
startY: currentY,
distance,
startTime: performance.now(),
duration,
});
return;
}
});
setIsDragging(true);
setLastMousePos({ x: e.clientX, y: e.clientY });
};
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
const rect = canvasRef.current?.getBoundingClientRect();
if (rect) {
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
setMousePos({ x, y });
}
if (isDragging) {
const deltaX = e.clientX - lastMousePos.x;
const deltaY = e.clientY - lastMousePos.y;
rotationRef.current = {
x: rotationRef.current.x + deltaY * 0.002,
y: rotationRef.current.y + deltaX * 0.002,
};
setLastMousePos({ x: e.clientX, y: e.clientY });
}
};
const handleMouseUp = () => {
setIsDragging(false);
};
// Animation and rendering
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext("2d");
if (!canvas || !ctx) return;
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
const dx = mousePos.x - centerX;
const dy = mousePos.y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
const speed = 0.003 + (distance / maxDistance) * 0.01;
if (targetRotation) {
const elapsed = performance.now() - targetRotation.startTime;
const progress = Math.min(1, elapsed / targetRotation.duration);
const easedProgress = easeOutCubic(progress);
rotationRef.current = {
x:
targetRotation.startX +
(targetRotation.x - targetRotation.startX) * easedProgress,
y:
targetRotation.startY +
(targetRotation.y - targetRotation.startY) * easedProgress,
};
if (progress >= 1) {
setTargetRotation(null);
}
} else if (!isDragging) {
rotationRef.current = {
x: rotationRef.current.x + (dy / canvas.height) * speed,
y: rotationRef.current.y + (dx / canvas.width) * speed,
};
}
iconPositions.forEach((icon, index) => {
const cosX = Math.cos(rotationRef.current.x);
const sinX = Math.sin(rotationRef.current.x);
const cosY = Math.cos(rotationRef.current.y);
const sinY = Math.sin(rotationRef.current.y);
const rotatedX = icon.x * cosY - icon.z * sinY;
const rotatedZ = icon.x * sinY + icon.z * cosY;
const rotatedY = icon.y * cosX + rotatedZ * sinX;
const scale = (rotatedZ + 200) / 300;
const opacity = Math.max(0.2, Math.min(1, (rotatedZ + 150) / 200));
ctx.save();
ctx.translate(
canvas.width / 2 + rotatedX,
canvas.height / 2 + rotatedY,
);
ctx.scale(scale, scale);
ctx.globalAlpha = opacity;
if (icons || images) {
// Only try to render icons/images if they exist
if (
iconCanvasesRef.current[index] &&
imagesLoadedRef.current[index]
) {
ctx.drawImage(iconCanvasesRef.current[index], -20, -20, 40, 40);
}
} else {
// Show numbered circles if no icons/images are provided
ctx.beginPath();
ctx.arc(0, 0, 20, 0, Math.PI * 2);
ctx.fillStyle = "#4444ff";
ctx.fill();
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "16px Arial";
ctx.fillText(`${icon.id + 1}`, 0, 0);
}
ctx.restore();
});
animationFrameRef.current = requestAnimationFrame(animate);
};
animate();
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [icons, images, iconPositions, isDragging, mousePos, targetRotation]);
return (
<canvas
ref={canvasRef}
width={400}
height={400}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
className="rounded-full"
aria-label="Interactive 3D Icon Cloud"
role="img"
/>
);
}

View File

@@ -0,0 +1,87 @@
---
title: Licensing API
description: Endpoints for activating, validating, and managing licenses
date: 2024-01-31
---
## Overview
The Licensing API allows external applications to interact with the WooNooW licensing system.
**Base URL**: `https://your-domain.com/wp-json/woonoow/v1`
---
## Public Endpoints
### Activate License
Activates a license key for a specific domain.
```http
POST /licenses/activate
```
#### Activation Parameters
| Body Params | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `license_key` | `string` | **Yes** | The license key to activate |
| `domain` | `string` | **Yes** | The domain where the software is installed |
| `activation_mode` | `string` | No | Set to `oauth` to trigger OAuth flow |
#### Responses
```json
{
"success": true,
"activation_id": 123,
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"status": "active"
}
```
If OAuth is required:
```json
{
"success": false,
"oauth_required": true,
"oauth_redirect": "https://vendor.com/my-account/license-connect/...",
"state": "abc12345"
}
```
---
### Validate License
Checks if a license key is valid and active for the current domain.
```http
POST /licenses/validate
```
#### Validation Parameters
| Body Params | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `license_key` | `string` | **Yes** | The license key to validate |
| `domain` | `string` | **Yes** | The domain to check against |
---
### Deactivate License
Deactivates a license for the current domain.
```http
POST /licenses/deactivate
```
#### Deactivation Parameters
| Body Params | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `license_key` | `string` | **Yes** | The license key to deactivate |
| `domain` | `string` | **Yes** | The domain to remove |

View File

@@ -0,0 +1,5 @@
{
"pages": [
"licensing"
]
}

View File

@@ -0,0 +1,16 @@
---
title: Changelog
description: Latest updates and changes to WooNooW
date: 2024-01-31
---
## Initial Release
<Release version="1.0.0" date="2024-01-31" title="Initial Public Release">
<Changes type="added">
- Core plugin functionality for WooCommerce enhancement.
- Licensing module with OAuth activation flow.
- Subscription management and payment gateway integration.
- Extensive hook system for developers.
</Changes>
</Release>

View File

@@ -1,687 +0,0 @@
---
title: Release Version 1.0+
description: List of latest changes and updates on DocuBook
date: 02-08-2025
---
<Note type="note" title="Version History">
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.
</Note>
<div className="sr-only">
### v 1.15.1
</div>
<Release version="1.15.1" date="2025-08-06" title="Algolia DocSearch for better search result">
<Changes type="added">
- new DocSearch.tsx components
- add props type algolia
- add searchprops
- add algolia.css
</Changes>
</Release>
<Note type="warning" title="environment">
To use Algolia DocSearch, you need to configure the following environment variables:
```plaintext
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_APP_ID="your_app_id"
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_API_KEY="your_api_key"
NEXT_PUBLIC_ALGOLIA_DOCSEARCH_INDEX_NAME="your_index_name"
```
in the navbar component, add a prop to the class
```
Change <Search /> to <Search type="algolia" />
```
</Note>
<div className="sr-only">
### v 1.14.2
</div>
<Release version="1.14.2" date="2025-08-05" title="Refactor & Fix: Decouple Search Component and Resolve Type Errors">
<Changes type="added">
- Refactor Search component into three distinct components: Search, SearchTrigger, and SearchModal for better maintainability and scalability.
- New SearchTrigger components
- New SearchModal components
</Changes>
<Changes type="fixed">
- Resolve TypeScript error for missing 'noLink' property on the 'Page' type after refactoring.
- Fix TypeScript error for missing 'context' property by providing a complete inline type annotation in the SearchModal component.
</Changes>
</Release>
<div className="sr-only">
### v 1.14.0
</div>
<Release version="1.14.0" date="2025-08-02" title="Dev: Add workflow to run Sync NPM package">
<Changes type="added">
- Add workflows to run Sync NPM package
- Add sync-from-npm.yml
- Add PR to organization github.com/DocuBook/docubook
</Changes>
</Release>
<div className="sr-only">
### v 1.13.9
</div>
<Release version="1.13.9" date="2025-07-27" title="Code Cleanup & Stability Improvements">
<Changes type="added">
- Added proper type definitions for unist-util-visit
- Added .eslintrc.json configuration file
- Added proper type checking for node values in markdown processing
</Changes>
<Changes type="improved">
- Enhanced type safety in lib/markdown.ts with proper TypeScript interfaces
- Converted interface extensions to type aliases in UI components for consistency
- Improved code organization across multiple files
</Changes>
<Changes type="fixed">
- Fixed potential null reference in markdown.ts when processing code blocks
- Updated import for MDXRemote in mdx-provider.tsx
- Optimized event listener cleanup in toc-observer.tsx
</Changes>
<Changes type="removed">
- Removed unused state and variables in toc-observer.tsx
- Removed unused imports for cleaner codebase
</Changes>
</Release>
<div className="sr-only">
### v 1.13.6
</div>
<Release version="1.13.6" date="2025-06-01" title="Improve sheet leftbar and search icon">
<Changes type="improved">
- Improve sheet leftbar and search icon
- color scheme for sheet leftbar
- color scheme for search icon
</Changes>
</Release>
<div className="sr-only">
### v 1.13.5
</div>
<Release version="1.13.5" date="2025-05-31" title="Add Theme schema for consistent theme">
<Changes type="added">
- Add Theme schema
- Add Freshlime theme
- Add Coffee theme
- Add llms context for generated theme with AI
</Changes>
<Changes type="improved">
- Markdown support for theme-colors
- consistent theme-colors page
- all components now consistent with theme-colors
- syntax with theme-colors
</Changes>
<Changes type="fixed">
- fix bug FileTree component
- fix issue markdown with theme-colors
</Changes>
</Release>
<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
- improve search dialog limit result to 6 post per suggestion
- improve search result typing 3 characters to show suggestion
</Changes>
</Release>
<div className="sr-only">
### v 1.12.0
</div>
<Release version="1.12.0" date="2025-05-28" title="New File Tree Component and enhancements for existing components or features">
<Changes type="added">
- New FileTree component for displaying hierarchical file structures
- Support for nested folders and files with expand/collapse functionality
- Hover effects showing file extensions
- Dark mode support with modern styling
- Keyboard navigation and accessibility features
- add toc-observer data attribute to detect toc section
- cli to copy from path npm registry
</Changes>
<Changes type="improved">
- search dialog hover effect return key
- search icon showing on mobile screens
</Changes>
<Changes type="fixed">
- fix search dialog on mobile screens
- fix release note component eslint error on mdx when rendering
- fix mob-toc callback function
- fix toc height issue when toc section is longer than screen height
</Changes>
<Changes type="removed">
- remove prompts depedencies
- remove degit depedencies
- remove prompts functions
- remove degit functions
- remove prompts and degit from package.json
- remove clone repository using git
</Changes>
</Release>
<Note type="note" title="Note">
on this version `1.12.0`, we remove clone repository using git and replace it with cli to copy from path npm registry
</Note>
<div className="sr-only">
### v 1.11.0
</div>
<Release version="1.11.0" date="2025-05-25" title="New Release Note components support multiple products or multiple changelogs">
<Changes type="added">
- New ReleaseNote component for structured changelog display
- Added support for categorized changes (added, fixed, improved, deprecated, removed)
- Integrated Lucide icons for better visual hierarchy
- Support for multiple release notes
</Changes>
<Changes type="improved">
- Enhanced documentation with comprehensive usage examples
- Better component organization and styling
- Semantic versioning support
- Nested release notes support
</Changes>
<Changes type="removed">
- Removed old changelog page in favor of the new ReleaseMdx component
- Removed changelog.md
- Removed changelog/page.tsx
- Removed changelog.ts
- Removed components/changelog
</Changes>
</Release>
<div className="sr-only">
### v 1.10.1
</div>
<Release version="1.10.1" date="2025-05-24" title="Accessibility Improvements and Bug Fixes">
<Changes type="fixed">
- Added missing DialogDescription components for better accessibility
- Fixed image aspect ratio issues in navbar logo
- Resolved console warnings for missing image sizes
- Improved keyboard navigation in search component
- Fixed mobile layout for search result items
</Changes>
<Changes type="improved">
- Added proper ARIA labels for screen readers
- Enhanced focus management in dialogs
- Optimized image loading with proper sizing attributes
- Better mobile experience with responsive design fixes
</Changes>
<Changes type="removed">
- Remove blog page
- Remove blog functions on markdown
</Changes>
</Release>
<div className="sr-only">
### v 1.10.0
</div>
<Release version="1.10.0" date="2025-05-21" title="Sidebar Improvements and Mobile TOC Enhancements">
<Changes type="added">
- New reusable ToggleButton component with animation
- Mobile-friendly Table of Contents (TOC) component
- Click-outside handler for better mobile navigation
- Smooth scroll behavior for TOC navigation
- Active section highlighting in TOC
</Changes>
<Changes type="improved">
- Sidebar now has a collapsible design
- Enhanced mobile responsiveness for TOC
- Better visual hierarchy in sidebar navigation
- Smoother animations for sidebar toggle
- Optimized TOC performance with intersection observer
- Improved accessibility with proper ARIA labels
- Better spacing and alignment in mobile view
</Changes>
<Changes type="fixed">
- Fixed sidebar toggle button positioning
- Resolved TOC highlighting issues during scroll
- Fixed z-index conflicts in mobile view
- Addressed minor UI glitches in dark mode
- Fixed TOC not updating on route changes
- Resolved scroll jank on mobile devices
- Fixed incorrect active state in navigation
</Changes>
<Changes type="deprecated">
- No longer support changelog.md
- No longer support changelog/page.tsx (will be removed in future update)
</Changes>
</Release>
<div className="sr-only">
### v 1.9.0
</div>
<Release version="1.9.0" date="2025-05-19" title="New Keyboard component to show keyboard shortcut on docs page">
<Changes type="added">
- New Keyboard component with props show, type, children
- Snippet keyboard component
</Changes>
<Changes type="improved">
- Support custom content
- Support platform type (mac or window)
- Support automatic rendering of platform-specific key symbols
- Rename lowercase to camelCase for markdown component
</Changes>
</Release>
<div className="sr-only">
### v 1.8.5
</div>
<Release version="1.8.5" date="2025-05-10" title="Add sponsor card on single docs page">
<Changes type="added">
- Expandables Leftbar
- Sponsor badges or ads
- Boolean show/hide 'edit on github'
- With the same code run anywhere (bun or nodejs)
- Add frontmatter (metadata) to playground editor
</Changes>
<Changes type="improved">
- Adjustment docu.json
- Adjustment navbar, footer and components
</Changes>
<Changes type="fixed">
- Bun compatibility: rename .js to common js
- CLI manage packageManager on package.json
- Inconsistent design moved to better UI/UX
- Error handle render footer.social
</Changes>
<Changes type="removed">
- Remove confusing and verbose CLI on installer
</Changes>
</Release>
<div className="sr-only">
### v 1.8.0
</div>
<Release version="1.8.0" date="2025-03-01" title="Now looks more modern and clean which is a big change in layout and design">
<Changes type="added">
- Social footer
- Toggle group
- Site description in footer
- Site title in footer
</Changes>
<Changes type="improved">
- Header design changes
- Footer design changes
- New functions in theme provider
- Object changes in docu.json
</Changes>
<Changes type="fixed">
- Updates to path structure components
- Groups to organize components
</Changes>
</Release>
<div className="sr-only">
### v 1.7.0
</div>
<Release version="1.7.0" date="2025-02-23" title="Remove the old function in the search dialog and replace it with a new and more optimal feature">
<Changes type="added">
- Up and down navigation in search dialog
- Enter (return) to select in search dialog
- Escape to close the dialog
</Changes>
<Changes type="improved">
- Maintenance for anchor components
- Anchor.tsx adjustments for all elements that use it
</Changes>
<Changes type="removed">
- Remove suboptimal search features
</Changes>
</Release>
<div className="sr-only">
### v 1.6.0
</div>
<Release version="1.6.0" date="2025-02-21" title="New Feature Card Groups with arrays for more Flexible Content">
<Changes type="added">
- Card Groups Components
- Props: href to url link
- Props: horizontal boolean
</Changes>
<Changes type="improved">
- Card props styling
- Compatibility for Cards components
- Support for children props in card content
</Changes>
<Changes type="removed">
- Remove unused props cards components
</Changes>
</Release>
<div className="sr-only">
### v 1.5.0
</div>
<Release version="1.5.0" date="2025-02-18" title="Minor Update - improved features and responsiveness on all devices">
<Changes type="added">
- New dialog footer on searchbox above medium screens
- Icon X for close dialog on searchbox (ESC key on medium screen)
</Changes>
<Changes type="improved">
- Responsive Leftbar components on large screens
- Menu Trigger on medium screens
- Responsive Navbar components on medium screens
- Better UX for searchbox dialog
- Tooltips components can be written together with regular paragraphs
</Changes>
<Changes type="fixed">
- Responsive issues
- Compatibility for Bun
- Changes postcss.config.js to .cjs for Bun
- All CLI installer and updater not working
- Adjustments for package managers (npm, pnpm, bun, yarn)
</Changes>
</Release>
<div className="sr-only">
### v 1.4.2
</div>
<Release version="1.4.2" date="2025-02-16" title="Complex Content for Accordion Component props children">
<Changes type="added">
- New Props with children in accordion
- Compatibility for markdown in accordion
- Nested components inside an accordion
- New icon on note components
- Add CLI npx @docubook/create@latest
- Add CLI npx @docubook/update@latest
</Changes>
<Changes type="improved">
- Better UI design for accordion
- Styling Note components on markdown
- Change accordion output on playground
- Change accordion output on snippet
</Changes>
<Changes type="removed">
- Remove deprecated props on accordion
- Remove CLI npx update_docu
- Remove CLI npx create_docu
</Changes>
</Release>
<div className="sr-only">
### v 1.4.0
</div>
<Release version="1.4.0" date="2025-02-11" title="Floating Button Version with Dynamic Tag version on Changelog page">
<Changes type="added">
- New components / changelog floating-version.tsx
- Button popover to open version-toc below large screens
- Dynamic tag by section ID #version
- Dynamic url tag #version
- Dynamic version indicator on floating version when scrolling section by ID
</Changes>
<Changes type="improved">
- Change icon version history
- Responsive version-toc
- Improvement components to changelog page
</Changes>
</Release>
<div className="sr-only">
### v 1.3.8
</div>
<Release version="1.3.8" date="2025-02-08" title="Responsive Table of Content">
<Changes type="added">
- Components terminal MagicUI
- Components card Shadcn
- New mob-toc for a better experience on mobile devices
- New Components scroll to top button
- Scroll to top: blog-post
- Scroll to top: docs-post
</Changes>
<Changes type="improved">
- lib/markdown for generated dynamic toc on markdown
- Responsive Table of Content below large screens
- Improve docs page
</Changes>
</Release>
<div className="sr-only">
### v 1.3.6
</div>
<Release version="1.3.6" date="2025-02-01" title="Appears more modern editor for Docu Play">
<Changes type="added">
- Line Number for editor
- editor.css
</Changes>
<Changes type="improved">
- Better Design for Editor
- Similar to Github Editor
- Moved Handler Element (copy, download, reset and fullscreen) on Header
</Changes>
</Release>
<div className="sr-only">
### v 1.3.5
</div>
<Release version="1.3.5" date="2025-01-30" title="It's Easy to Write Markdown with Playground">
<Changes type="added">
- New Playground Page
- New Playground Layout
- Toolbar for Markdown Components
- Fullscreen Mode to Focus Editing Your Content
- Copy to Clipboard Your Content
- Download Your Content as index.mdx
- Reset Your Content without refresh the Browser
- Only Large Screen for Better Experience
</Changes>
</Release>
<div className="sr-only">
### v 1.3.1
</div>
<Release version="1.3.1" date="2025-01-20" title="Snippet Feature to Easily Write Markdown and Call DocuBook Components">
<Changes type="added">
- New Feature Snippet for Markdown Components
- Support Snippet for Visual Studio Code
</Changes>
<Changes type="removed">
- Remove props icon and props description for accordion components
</Changes>
</Release>
<div className="sr-only">
### v 1.3.0
</div>
<Release version="1.3.0" date="2024-12-31" title="Release Note Feature to Make it Easier to Write Changelogs">
<Changes type="added">
- New Release Note Feature
- New Layout for Changelog page
- New Changelog page
- Add Release Note Component
- Easily write release notes directly from the CHANGELOG.md file
- TOC for versioning
- Write with the markdown tag
- Add lib / changelog.ts
</Changes>
<Changes type="improved">
- Improvement Responsive feature image for Version Entry
- Improvement Layout for changelog page
- Improvement Padding on mobile devices
- Only use containers of md size
- Improvement syntax.css for ul>li classes
</Changes>
<Changes type="fixed">
- Fix og:image not showing on Page.tsx
- Fix text-indent on class li
</Changes>
<Changes type="removed">
- Remove excessive padding
- Remove Logo on Footer
</Changes>
</Release>
<div className="sr-only">
### v 1.2.0
</div>
<Release version="1.2.0" date="2024-12-22" title="New Accordion Component: Support content plain text, html and all markdown component">
<Changes type="added">
- Add New Accordion Component
</Changes>
<Changes type="improved">
- Props Improvement
- Support Dynamic Content for Accordion
</Changes>
</Release>
<div className="sr-only">
### v 1.1.0
</div>
<Release version="1.1.0" date="2024-12-15" title="Minor Update: Easily manage set up with docu.json">
<Changes type="added">
- Add docu.json file
- Add openGraph (title, description, image)
- Add Dynamic metadata
- Generate metadata as openGraph
- OpenGraph support for .mdx
</Changes>
<Changes type="improved">
- Routes-config from json
- Frontmatter improvement
- Edit the content of footer.tsx simply via the docu.json file
- Edit the content of navbar.tsx simply via the docu.json file
</Changes>
</Release>
<div className="sr-only">
### v 1.0.7
</div>
<Release version="1.0.7" date="2024-12-14" title="Easily updates your DocuBook Version with CLI npx update_docu">
<Changes type="added">
- CLI npx update_docu (update features into docubook existing directory)
- Playground (easily to written content)
- New Button component
- Navbar external link conditions
- CLI npx create_docu
</Changes>
<Changes type="improved">
- Searchbar Improvement
- Navigation Improvement
- Edit on Github Improvement
</Changes>
<Changes type="removed">
- Remove CLI npx create-docu (on this version not usage dash `-`)
</Changes>
</Release>
<div className="sr-only">
### v 1.0.6
</div>
<Release version="1.0.6" date="2024-11-24" title="New Components, Fix and Improvement">
<Changes type="added">
- New Card component
- New Tooltips component
</Changes>
<Changes type="fixed">
- Change root folder
</Changes>
<Changes type="improved">
- Logo on navbar & footer
- Easily change logo
</Changes>
</Release>
<div className="sr-only">
### v 1.0.5
</div>
<Release version="1.0.5" date="2024-11-16" title="Add New Features and Improvement for this version">
<Changes type="added">
- New Youtube component
- Edit this page - easily manage directory content via the github repo
- Support installation via CLI command npx create-docu
</Changes>
<Changes type="improved">
- Keyboard shortcut command + k or ctrl + k to open search dialog
</Changes>
</Release>
<div className="sr-only">
### v 1.0.0
</div>
<Release version="1.0.0" date="2024-11-10" title="Initial release of DocuBook to create interactive nested docs with MDX">
<Changes type="added">
- Initial release of DocuBook
- Basic documentation structure
- Markdown support with MDX
- Responsive design
- Search functionality
- Dark mode support
</Changes>
</Release>

View File

@@ -1,86 +0,0 @@
---
title: Accordion
description: A component used to create collapsible content that can be hidden and shown again.
date: 22-12-2024
---
## Preview
### Basic Usage
<Accordion title="Click to expand" defaultOpen={true}>
This is a simple accordion component that can be toggled by clicking the header. The content can include any valid React nodes, including text, components, and markdown.
</Accordion>
### With Code Block
<Accordion title="Code Example" className="mt-4">
```javascript:utils.js showLineNumbers
// Example of using the Accordion component
import Accordion from '@/components/markdown/AccordionMdx';
function Example() {
return (
<Accordion title="Click to see code">
<pre>
{`function greet() {\n console.log('Hello, world!');\n}`}
</pre>
</Accordion>
);
}
```
</Accordion>
### With Markdown Content
<Accordion title="Markdown Example" className="mt-4">
## This is a markdown heading
- List item 1
- List item 2
- List item 3
You can include **bold** and *italic* text, [links](#), and other markdown elements.
</Accordion>
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | string | - | **Required**. The text displayed in the accordion header. |
| `children` | ReactNode | null | The content to be displayed when the accordion is expanded. Can be plain text, markdown, or React components. |
| `defaultOpen` | boolean | false | When true, the accordion will be expanded by default. |
| `className` | string | undefined | Additional CSS classes to apply to the accordion container. |
## Output Markdown
<Tabs defaultValue="markdown" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="markdown">Markdown</TabsTrigger>
<TabsTrigger value="codeblock">Code Block</TabsTrigger>
</TabsList>
<TabsContent value="markdown">
```plaintext
<Accordion title="Markdown">
this is an example of plain text content from the accordion component and below is markdown ;
1. number one
2. number two
3. number three
</Accordion>
```
</TabsContent>
<TabsContent value="codeblock">
````plaintext
<Accordion title="Code Block" defaultOpen={true}>
```javascript:main.js showLineNumbers {3-4}
function isRocketAboutToCrash() {
// Check if the rocket is stable
if (!isStable()) {
NoCrash(); // Prevent the crash
}
}
```
</Accordion>
````
</TabsContent>
</Tabs>

View File

@@ -1,40 +0,0 @@
---
title: Button
description: A component used to create buttons that can be used to trigger actions or navigate to other pages.
date: 14-12-2024
---
## Preview
<Button
text="Learn More"
href="https://learn.example.com"
icon="MoveUpRight"
size="md"
target="_blank"
variation="primary"
/>
## Props
| Prop | Type | Default | Description |
| ----------- | -------- | ----------- | -------------------------------------------- |
| `text` | string | undefined | The button text |
| `href` | string | **required**| The URL to navigate to |
| `icon` | string | undefined | Lucide icon name (e.g. "MoveUpRight") |
| `size` | string | "md" | Button size: `"sm"`, `"md"`, or `"lg"` |
| `target` | string | undefined | Link target (e.g. "_blank") |
| `variation` | string | "primary" | Button style: `"primary"`, `"accent"`, or `"outline"` |
## Output Markdown
```markdown
<Button
text="Learn More"
href="https://learn.example.com"
icon="MoveUpRight"
size="md"
target="_blank"
variation="primary"
/>
```

View File

@@ -1,47 +0,0 @@
---
title: Card Group
description: A component used to create card groups that can be used to display multiple cards in a compact and organized way.
date: 20-02-2025
---
## Preview
<CardGroup cols={2}>
<Card title="Heading 1" icon="Heading1">
This is an example of card content with columns.
</Card>
<Card title="Heading 2" icon="Heading2">
This is an example of card content with columns.
</Card>
<Card title="Grid Card" icon="Grid" horizontal>
This is a horizontal card layout.
</Card>
<Card title="Horizontal Card" icon="Layout" horizontal>
This is a horizontal card layout.
</Card>
</CardGroup>
## Props
| Prop | Type | Default | Description |
| ------------- | -------- | ------- | ------------------------------------------------------- |
| `cols` | number | {2} | By default 2 The number of columns per row |
## Output Markdown
```markdown
<CardGroup cols={2}>
<Card title="Heading 1" icon="Heading1">
This is an example of card content with columns.
</Card>
<Card title="Heading 2" icon="Heading2">
This is an example of card content with columns.
</Card>
<Card title="Grid Card" icon="Grid" horizontal>
This is a horizontal card layout.
</Card>
<Card title="Horizontal Card" icon="Layout" horizontal>
This is a horizontal card layout.
</Card>
</CardGroup>
```

View File

@@ -1,68 +0,0 @@
---
title: Cards
description: A component used to create cards that can be used to display content in a compact and organized way.
date: 20-02-2025
---
## Example
### Card with Link and icon
<Card title="Click on me" icon="Link" href="/docs/components/card-group">
This is how you use a card with an icon and a link. Clicking on this card
brings you to the Card Group page.
</Card>
### Card Horizontal
<Card title="Horizontal Card" icon="Layout" horizontal>
This is a horizontal card layout.
</Card>
### Card Simple
<Card title="Simple Card">
This is a simple card without an icon or link.
</Card>
## Props
| Prop | Type | Default | Description |
| ------------- | -------- | ------- | ------------------------------------------------------- |
| `title` | string | null | The value of card title. |
| `icon` | string | null | The value of card icon render from lucide. |
| `href` | string | null | The value of card link url. |
| `horizontal` | boolean | undefined | horizontal layout for card. |
## Output Markdown
<Tabs defaultValue="link" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="link">Link & Icon</TabsTrigger>
<TabsTrigger value="horizontal">Horizontal</TabsTrigger>
<TabsTrigger value="simple">Simple</TabsTrigger>
</TabsList>
<TabsContent value="link">
```markdown
<Card title="Click on me" icon="Link" href="/docs/getting-started/components/button">
This is how you use a card with an icon and a link. Clicking on this card
brings you to the Card Group page.
</Card>
```
</TabsContent>
<TabsContent value="horizontal">
```markdown
<Card title="Horizontal Card" icon="Layout" horizontal>
This is a horizontal card layout.
</Card>
```
</TabsContent>
<TabsContent value="simple">
```markdown
<Card title="Simple Card">
This is a simple card without an icon or link.
</Card>
```
</TabsContent>
</Tabs>

View File

@@ -1,39 +0,0 @@
---
title: Code Block
description: A component used to display code snippets with optional line numbering and line highlighting.
date: 14-12-2024
---
## Preview
```javascript:main.js showLineNumbers {3-4}
function isRocketAboutToCrash() {
// Check if the rocket is stable
if (!isStable()) {
NoCrash(); // Prevent the crash
}
}
```
In this example, line numbers are displayed for lines 1 to 4. You can specify which lines to highlight using the format `{2,3-5}`.
## Output Markdown
You can directly use the following syntax to create a code block with line numbers and highlight specific lines:
````plaintext
```javascript:main.js showLineNumbers {3-4}
function isRocketAboutToCrash() {
// Check if the rocket is stable
if (!isStable()) {
NoCrash(); // Prevent the crash
}
}
```
````
## Features
- **Line Numbers**: Enable line numbers by adding `showLineNumbers` after the opening backticks.
- **Highlight Lines**: Specify lines to highlight using curly braces (e.g., `{2,3-5}`).
- **Syntax Highlighting**: Use the appropriate language for syntax highlighting.

View File

@@ -1,38 +0,0 @@
---
title: Custom Components
description: How to create custom components for Markdown.
date: 14-12-2024
---
To add custom components in DocuBook, follow these steps:
1. **Create Your Component**: First, create your custom component in the `@components/markdown` folder. For example, you might create a file named `Outlet.tsx`.
2. **Import Your Component**: Next, open the `@lib/markdown.ts` file. This is where you'll register your custom component for use in Markdown.
3. **Add Your Component to the Components Object**: In the `@lib/markdown.ts` file, import your custom component and add it to the `components` object. Heres how to do it:
```ts
import Outlet from "@/components/markdown/outlet";
// Add custom components
const components = {
Outlet,
};
```
4. **Using Your Custom Component in Markdown**: After registering your component, you can now use it anywhere in your Markdown content. For instance, if your `Outlet` component is designed to display additional information, you can use it as follows:
### Markdown Example
```markdown
<Outlet>
This is some custom content rendered by the Outlet component!
</Outlet>
```
### Rendered Output
This will render the content inside the `Outlet` component, allowing you to create reusable and dynamic Markdown content.
By following these steps, you can extend the capabilities of your Markdown documentation and create a more engaging user experience.

View File

@@ -1,109 +0,0 @@
---
title: File Tree Component
description: A customizable file tree component for displaying hierarchical file structures in your documentation.
date: 28-05-2025
---
The File Tree component allows you to display hierarchical file structures in your documentation with collapsible folders and files.
## Basic Usage
```
<Files>
<Folder name="src">
<File name="App.tsx" />
<File name="index.tsx" />
<Folder name="components">
<File name="Button.tsx" />
<File name="Card.tsx" />
</Folder>
<Folder name="pages">
<File name="Home.tsx" />
<File name="About.tsx" />
</Folder>
</Folder>
</Files>
```
Render As:
<Files>
<Folder name="src">
<File name="App.tsx" />
<File name="index.tsx" />
<Folder name="components">
<File name="Button.tsx" />
<File name="Card.tsx" />
</Folder>
<Folder name="pages">
<File name="Home.tsx" />
<File name="About.tsx" />
</Folder>
</Folder>
</Files>
## Props
### Files
The root component that wraps the entire file tree.
### Folder
| Prop | Type | Required | Description |
|----------|----------|----------|---------------------------------------|
| `name` | string | Yes | The name of the folder |
| `children` | ReactNode | No | Child elements (File or Folder) |
### File
| Prop | Type | Required | Description |
|------|--------|----------|----------------------------|
| `name` | string | Yes | The name of the file |
## Examples
### Nested Folder Structure
```
<Files>
<Folder name="project-root">
<File name="package.json" />
<File name="tsconfig.json" />
<Folder name="src">
<File name="index.ts" />
<Folder name="components">
<File name="Button.tsx" />
<File name="Card.tsx" />
</Folder>
</Folder>
</Folder>
</Files>
```
### Minimal Example
```
<Files>
<Folder name="components">
<File name="Button.tsx" />
<File name="Input.tsx" />
</Folder>
</Files>
```
## Best Practices
1. Keep the nesting level reasonable (recommended max 3-4 levels deep)
2. Use clear and descriptive names for files and folders
3. Consider the user experience when displaying large file structures
4. Use consistent naming conventions throughout your file tree
## Accessibility
The File Tree component includes built-in accessibility features:
- Keyboard navigation support
- ARIA attributes for screen readers
- Focus management for interactive elements
- High contrast mode support

View File

@@ -1,37 +0,0 @@
---
title: Image
description: A component used to display images in Markdown.
date: 14-12-2024
---
In DocuBook, all images written in Markdown are automatically converted to their respective Next.js components. This allows for better optimization and performance in your application.
## Images
Similarly, images in Markdown are transformed into the Next.js `Image` component. This allows for automatic image optimization, such as lazy loading and resizing, which enhances performance and user experience. Heres an example:
### Markdown
```markdown
![Alt text for the image](https://via.placeholder.com/150)
```
### Rendered Output
The above Markdown is converted to:
```jsx
<Image
src="https://via.placeholder.com/150"
alt="Alt text for the image"
width={800}
height={350}
/>
```
## Benefits
- **Performance Optimization**: Automatic conversion to Next.js components ensures optimized loading of images.
- **Responsive Images**: Next.js `Image` component handles responsive images, providing the best quality for various device sizes.
By utilizing these features, you can ensure that your documentation is not only visually appealing but also performs efficiently.

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="components" />

View File

@@ -1,117 +0,0 @@
---
title: Keyboard
description: Display keyboard keys with platform-specific styling for Windows and macOS.
date : 19-05-2025
---
The `Keyboard` component automatically renders platform-appropriate key symbols for macOS and Windows. It's perfect for documenting keyboard shortcuts in your application.
## Usage
### Basic Usage
Simply use the `Kbd` component with a `show` prop:
```tsx
<Kbd show="cmd" type="mac" /> + <Kbd show="c" />
```
Renders as:
<Kbd show="cmd" type="mac" /> + <Kbd show="c" />
### Automatic Symbol Rendering
The component automatically renders appropriate symbols based on the platform:
```tsx
{/* Windows style (default) */}
<Kbd show="ctrl" /> + <Kbd show="v" />
{/* Mac style */}
<Kbd show="cmd" type="mac" /> + <Kbd show="v" type="mac" />
```
Renders as:
- Windows: <Kbd show="ctrl" type="window" /> + <Kbd show="v" type="window" />
- Mac: <Kbd show="cmd" type="mac" /> + <Kbd show="v" type="mac" />
### Custom Content
For custom key labels, provide children:
```tsx
<Kbd show="custom">Custom</Kbd>
```
Renders as: <Kbd show="custom">Custom</Kbd>
## Props
| Prop | Type | Default | Description |
|-----------|---------------------|------------|-------------|
| `show` | string | (required) | The key identifier (e.g., 'cmd', 'ctrl', 'a') |
| `type` | string | `window` | for device type `mac` or `window` | Platform style or custom content |
| `children`| ReactNode | - | Custom content to display (overrides automatic rendering) |
## Supported Keys
The component includes special handling for common keys:
| Key Name | Windows | macOS |
|-------------|---------|-------|
| command/cmd | `Win` | `⌘` |
| option/alt | `Alt` | `⌥` |
| shift | `Shift` | `⇧` |
| ctrl/control| `Ctrl` | `⌃` |
| tab | `Tab` | `⇥` |
| enter/return| `Enter` | `⏎` |
| delete | `Del` | `⌫` |
| escape/esc | `Esc` | `⎋` |
| up/down/left/right | `↑` `↓` `←` `→` | `↑` `↓` `←` `→` |
| space | `Space` | `␣` |
## Examples
### Common Shortcuts
```tsx
{/* Copy shortcut */}
<Kbd show="ctrl" /> + <Kbd show="c" />
{/* Paste shortcut */}
<Kbd show="cmd" type="mac" /> + <Kbd show="v" type="mac" />
{/* Save shortcut */}
<Kbd show="ctrl" /> + <Kbd show="s" />
```
### Custom Key Combinations
```tsx
{/* Custom application shortcut */}
<Kbd show="cmd" type="mac" /> + <Kbd show="option" type="mac" /> + <Kbd show="a" type="mac"/>
```
Render as: <Kbd show="cmd" type="mac" /> + <Kbd show="option" type="mac" /> + <Kbd show="a" type="mac"/>
### Arrow Key
```tsx
<Kbd show="up" /> <Kbd show="down" /> <Kbd show="left" /> <Kbd show="right" />
```
Render as: <Kbd show="up" /> <Kbd show="down" /> <Kbd show="left" /> <Kbd show="right" />
## Best Practices
1. **Be Consistent**: Stick to one platform style within the same context
2. **Use Type Wisely**:
- Use `type="mac"` for Mac-specific documentation
- Use `type="window"` (default) for Windows/Linux
3. **Accessibility**: The component uses semantic `<kbd>` HTML for better accessibility
## Notes
- The component automatically capitalizes single letters (e.g., 'a' becomes 'A')
- Unrecognized keys are displayed as-is
- Dark mode is automatically supported through Tailwind's dark mode classes

View File

@@ -1,34 +0,0 @@
---
title: Link
description: A component used to create links that can be used to navigate to other pages.
date: 14-12-2024
---
In DocuBook, all links written in Markdown are automatically converted to their respective Next.js components. This allows for better optimization and performance in your application.
## Links
When you create a link in your Markdown, it is converted to the Next.js `Link` component. This enables client-side navigation and improves loading times. Heres an example of how a Markdown link is transformed:
### Markdown
```markdown
[Visit OpenAI](https://www.openai.com)
```
### Rendered Output
The above Markdown is converted to:
```jsx
<Link href="https://www.openai.com" target="_blank" rel="noopener noreferrer">
Visit OpenAI
</Link>
```
## Benefits
- **Performance Optimization**: Automatic conversion to Next.js components ensures optimized loading of links.
- **Improved User Experience**: Client-side navigation with Next.js `Link` improves the browsing experience.
By utilizing these features, you can ensure that your documentation is not only visually appealing but also performs efficiently.

View File

@@ -1,44 +0,0 @@
---
title: Note
description: A component used to display different types of messages such as general notes, warnings, or success notifications.
date: 14-12-2024
---
## Preview
<Note type="note" title="Note">
This is a general note to convey information to the user.
</Note>
<Note type="danger" title="Danger">
This is a danger alert to notify the user of a critical issue.
</Note>
<Note type="warning" title="Warning">
This is a warning alert for issues that require attention.
</Note>
<Note type="success" title="Success">
This is a success message to inform the user of successful actions.
</Note>
## Props
| Prop | Type | Default | Description |
| ------- | ---------------------------------------------- | ------- | ---------------------------------------- |
| `title` | string | "Note" | Sets the title of the note. |
| `type` | "note" "danger" "success" "warning" | undefined | Determines the visual style of the note. |
## Output Markdown
```markdown
<Note type="note" title="Note">
This is a general note to convey information to the user.
</Note>
<Note type="danger" title="Danger">
This is a danger alert to notify the user of a critical issue.
</Note>
<Note type="warning" title="Warning">
This is a warning alert for issues that require attention.
</Note>
<Note type="success" title="Success">
This is a success message to inform the user of successful actions.
</Note>
```

View File

@@ -1,130 +0,0 @@
---
title: Release Note
description: The Release Note component makes it easy for you to write updates for each version of your application.
date: 31-12-2024
---
The Release Note component makes it easy for you to write and display changelogs in a structured and organized way. This component consists of two main parts: `Release` and `Changes` which can be used to display version, date, release title, and a list of changes categorized by type.
## Basic Usage
Here is a basic example of using the Release Note component:
```markdown
<Release version="1.10.1" date="2025-05-24" title="Accessibility Improvements and Bug Fixes">
<Changes type="added">
- New feature to improve accessibility
- Keyboard navigation support for dialog components
</Changes>
<Changes type="fixed">
- Bug fix for mobile menu
- Fixed loading issues on documentation pages
</Changes>
</Release>
```
<Accordion title="Render As" defaultOpen={true}>
<Release version="1.10.1" date="2025-05-24" title="Accessibility Improvements and Bug Fixes">
<Changes type="added">
- New feature to improve accessibility
- Keyboard navigation support for dialog components
</Changes>
<Changes type="fixed">
- Bug fix for mobile menu
- Fixed loading issues on documentation pages
</Changes>
</Release>
</Accordion>
## Release Component
The `Release` component is used to display key information about a release version, such as version number, release date, and title.
### Release Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `version` | string | ✅ | Version number to display (without "v" prefix) |
| `title` | string | ✅ | Title or name of the release |
| `date` | string | ❌ | Release date in a valid format (example: "2025-05-24") |
| `children` | ReactNode | ✅ | Child content, typically `Changes` components |
```markdown
<Release
version="1.10.1"
date="2025-05-24"
title="Accessibility Improvements and Bug Fixes"
>
{/* Changes content here */}
</Release>
```
## Changes Component
The `Changes` component is used to group changes by category with appropriate icons and colors.
### Changes Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `type` | string | ✅ | Type of change: 'added', 'fixed', 'improved', 'deprecated', or 'removed' |
| `children` | ReactNode | ✅ | List of changes, can be text with Markdown formatting |
### Changes Note
| Category | Description |
|----------|-------------|
| `added` | New features or functionality added |
| `fixed` | Bugs or issues that have been fixed |
| `improved` | Enhancements or optimizations to existing features |
| `deprecated` | Features that are not recommended and may be removed in future |
| `removed` | Features that have been completely removed |
### Changes Example
```jsx
<Changes type="added">
- New feature to improve accessibility
- Keyboard navigation support for dialog components
</Changes>
<Changes type="fixed">
- Bug fix for mobile menu
- Fixed loading issues on documentation pages
</Changes>
```
## Complete Implementation
Here is a complete example of using the Release Note component in an MDX file:
````plaintext
<!-- hidden heading for TOC -->
<Release version="1.10.1" date="2025-05-24" title="Accessibility Improvements and Bug Fixes">
<Changes type="added">
- Keyboard navigation for all interactive components
- Screen reader support for table components
- Dark mode feature with system preference detection
</Changes>
<Changes type="fixed">
- Fixed mobile menu bug that wouldn't close when navigating to another page
- Fixed loading issues on documentation pages
- Fixed display issues in Safari browser
</Changes>
<Changes type="improved">
- Improved page loading performance
- Optimized JavaScript bundle size
- Enhanced responsive design across all viewports
</Changes>
</Release>
````
## Usage Tips
1. **Date Format**: Use a consistent date format for all releases.
2. **Version Ordering**: Arrange versions in reverse chronological order (newest version at the top).
3. **List Items**: You can use standard Markdown list format (`-` or `*`) or write text directly, the component will handle the formatting.
4. **TOC**: Use hidden headings to ensure each version is detected in the Table of Contents. Use `<div className="sr-only">### v 1.10.1</div>`

View File

@@ -1,45 +0,0 @@
---
title: Stepper
description: A component used to display step-by-step instructions directly within the markdown render.
date: 14-12-2024
---
In this guide, we utilize a custom `Stepper` component, specifically designed for DocuBook, which enables users to display step-by-step instructions directly within the markdown render.
## Preview
<Stepper>
<StepperItem title="Step 1: Clone the DocuBook Repository">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum,
felis sed efficitur tincidunt, justo nulla viverra enim, et maximus nunc
dolor in lorem.
</StepperItem>
<StepperItem title="Step 2: Access the Project Directory">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin non neque ut
eros auctor accumsan. Mauris a nisl vitae magna ultricies aliquam.
</StepperItem>
<StepperItem title="Step 3: Install Required Dependencies">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ut
ipsum nec nulla ultricies porttitor et non justo.
</StepperItem>
</Stepper>
## Output Markdown
```markdown
<Stepper>
<StepperItem title="Step 1: Clone the DocuBook Repository">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum,
felis sed efficitur tincidunt, justo nulla viverra enim, et maximus nunc
dolor in lorem.
</StepperItem>
<StepperItem title="Step 2: Access the Project Directory">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin non neque ut
eros auctor accumsan. Mauris a nisl vitae magna ultricies aliquam.
</StepperItem>
<StepperItem title="Step 3: Install Required Dependencies">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ut
ipsum nec nulla ultricies porttitor et non justo.
</StepperItem>
</Stepper>
```

View File

@@ -1,70 +0,0 @@
---
title: Tabs
description: Organize content into multiple sections with switchable tabs.
date: 14-12-2024
---
The `Tabs` component allows you to organize content into multiple sections, enabling users to switch between them easily. This is particularly useful for displaying related content in a compact manner.
## Preview
<Tabs defaultValue="java" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="java">Java</TabsTrigger>
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
</TabsList>
<TabsContent value="java">
```java
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
</TabsContent>
<TabsContent value="typescript">
```typescript
// helloWorld.ts
function helloWorld(): void {
console.log("Hello, World!");
}
helloWorld();
```
</TabsContent>
</Tabs>
## Props
| Prop | Type | Default | Description |
| -------------- | -------- | ------- | ------------------------------------------------------ |
| `defaultValue` | string | null | The value of the tab that is selected by default. |
| `className` | string | null | Additional CSS classes for styling the Tabs component. |
## Output Markdown
````plaintext
<Tabs defaultValue="java" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="java">Java</TabsTrigger>
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
</TabsList>
<TabsContent value="java">
```java
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```</TabsContent>
<TabsContent value="typescript">
```typescript
// helloWorld.ts
function helloWorld(): void {
console.log("Hello, World!");
}
helloWorld();
```</TabsContent>
</Tabs>
````

View File

@@ -1,22 +0,0 @@
---
title: Tooltips
description: A component used to display additional information when hovering over a word or phrase.
date: 19-02-2025
---
I have implemented the `tooltips` component into markdown which allows you to add additional information to a word or phrase when hovering. This feature is useful for providing definitions, explanations, or any other additional information that can enhance the user experience.
## Usage
You can use tooltips in your Markdown content to provide additional information when hovering over a word or phrase.
### Preview
What do you know about <Tooltip text="DocuBook" tip="npx @docubook/create@latest" /> ? Create interactive nested documentations using MDX.
### Output Markdown
The above Markdown is converted to:
```markdown:index.mdx
What do you know about <Tooltip text="DocuBook" tip="npx @docubook/create@latest" /> ? Create interactive nested documentations using MDX.
```

View File

@@ -1,23 +0,0 @@
---
title: Youtube
description: A component used to embed YouTube videos directly into your documentation.
date: 14-12-2024
---
I have implemented a `YouTube` component for your documentation. This component allows you to easily embed YouTube videos directly into your documentation by simply inputting the video's ID.
## Preview
<Youtube videoId="8sM0Di7Ew9Q" />
## Output Markdown
```markdown
<Youtube videoId="8sM0Di7Ew9Q" />
```
## Usage
<Note type="note" title="Usage">
for example the youtube URL show this https://www.youtube.com/watch?v=8sM0Di7Ew9Q the ID `8sM0Di7Ew9Q`
</Note>

View File

@@ -0,0 +1,135 @@
---
title: Appearance Settings
description: Customize the look and feel of your WooNooW store
date: 2024-01-31
---
## Accessing Appearance Settings
Go to **WooNooW → Appearance** in the WordPress admin.
---
## General Settings
### Logo
Upload your store logo for display in the header.
- **Recommended size**: 200x60 pixels (width x height)
- **Formats**: PNG (transparent background recommended), SVG, JPG
- **Mobile**: Automatically resized for smaller screens
### SPA Page
Select which page hosts the WooNooW SPA. Default is "Store".
> **Note**: This page should contain the `[woonoow_spa]` shortcode.
### SPA Mode
Choose how WooNooW handles your store pages:
| Mode | Description |
|------|-------------|
| **Full** | All WooCommerce pages redirect to SPA |
| **Disabled** | Native WooCommerce templates are used |
---
## Colors
### Primary Color
The main brand color used for:
- Buttons
- Links
- Active states
- Primary actions
**Default**: `#6366f1` (Indigo)
### Secondary Color
Secondary UI elements:
- Less prominent buttons
- Borders
- Subtle backgrounds
**Default**: `#64748b` (Slate)
### Accent Color
Highlight color for:
- Sale badges
- Notifications
- Call-to-action elements
**Default**: `#f59e0b` (Amber)
---
## Typography
### Body Font
Font used for general text content.
**Options**: System fonts and Google Fonts
- Inter
- Open Sans
- Roboto
- Lato
- Poppins
- And more...
### Heading Font
Font used for titles and headings.
**Options**: Same as body fonts, plus:
- Cormorant Garamond (Serif option)
- Playfair Display
- Merriweather
### Font Sizes
Font sizes are responsive and adjust automatically based on screen size.
---
## Layout
### Container Width
Maximum width of the content area.
| Option | Width |
|--------|-------|
| Narrow | 1024px |
| Default | 1280px |
| Wide | 1536px |
| Full | 100% |
### Header Style
Configure the header appearance:
- **Fixed**: Stays at top when scrolling
- **Static**: Scrolls with page
### Product Grid
Columns in the shop page grid:
- Mobile: 1-2 columns
- Tablet: 2-3 columns
- Desktop: 3-4 columns
---
## Saving Changes
1. Make your changes
2. Click **Save Changes** button
3. Refresh your store page to see updates
> **Tip**: Open your store in another tab to preview changes quickly.

View File

@@ -0,0 +1,141 @@
---
title: SPA Mode
description: Understanding and configuring WooNooW's SPA mode
date: 2024-01-31
---
## What is SPA Mode?
SPA Mode controls how WooNooW handles your WooCommerce pages. It determines whether visitors experience the modern SPA interface or traditional WooCommerce templates.
---
## Available Modes
### Full Mode (Recommended)
**All WooCommerce pages redirect to the SPA.**
When a visitor navigates to:
- `/shop` → Redirects to `/store/shop`
- `/product/example` → Redirects to `/store/product/example`
- `/cart` → Redirects to `/store/cart`
- `/checkout` → Redirects to `/store/checkout`
- `/my-account` → Redirects to `/store/my-account`
**Benefits**:
- Instant page transitions
- Modern, consistent UI
- Better mobile experience
- Smooth animations
**Best for**:
- New stores
- Stores wanting a modern look
- Mobile-focused businesses
### Disabled Mode
**WooCommerce uses its native templates.**
WooCommerce pages work normally with your theme's templates. WooNooW admin features still work, but the customer-facing SPA is turned off.
**Benefits**:
- Keep existing theme customizations
- Compatibility with WooCommerce template overrides
- Traditional page-by-page navigation
**Best for**:
- Stores with heavy theme customizations
- Testing before full rollout
- Troubleshooting issues
---
## Switching Modes
### How to Switch
1. Go to **WooNooW → Appearance → General**
2. Find **SPA Mode** setting
3. Select your preferred mode
4. Click **Save Changes**
### What Happens When Switching
**Switching to Full**:
- WooCommerce pages start redirecting
- SPA loads for shop experience
- No data is changed
**Switching to Disabled**:
- Redirects stop immediately
- WooCommerce templates take over
- No data is changed
> **Note**: All your products, orders, and settings remain unchanged when switching modes.
---
## URL Structure
### Full Mode URLs
```
https://yourstore.com/store/ → Home/Shop
https://yourstore.com/store/shop → Shop page
https://yourstore.com/store/product/slug → Product page
https://yourstore.com/store/cart → Cart
https://yourstore.com/store/checkout → Checkout
https://yourstore.com/store/my-account → Account
```
### Disabled Mode URLs
Standard WooCommerce URLs:
```
https://yourstore.com/shop/ → Shop page
https://yourstore.com/product/slug → Product page
https://yourstore.com/cart/ → Cart
https://yourstore.com/checkout/ → Checkout
https://yourstore.com/my-account/ → Account
```
---
## SEO Considerations
### Full Mode SEO
- WooCommerce URLs (`/product/slug`) remain in sitemaps
- When users click from search results, they're redirected to SPA
- Meta tags are generated dynamically for social sharing
- 302 (temporary) redirects preserve link equity
### Disabled Mode SEO
- Standard WooCommerce SEO applies
- No redirects needed
- Works with Yoast SEO, RankMath, etc.
---
## Troubleshooting
### Redirects Not Working
1. **Flush Permalinks**: Go to Settings → Permalinks → Save Changes
2. **Check Store Page**: Ensure the Store page exists and has `[woonoow_spa]`
3. **Clear Cache**: Purge all caching layers
### Blank Pages After Enabling
1. Verify SPA Mode is set to "Full"
2. Clear browser cache
3. Check for JavaScript errors in browser console
### Want to Test Before Enabling
1. Keep mode as "Disabled"
2. Visit `/store/` directly to preview SPA
3. Switch to "Full" when satisfied

View File

@@ -0,0 +1,79 @@
---
title: Bridge Pattern
description: Integrating third-party plugins with WooNooW
date: 2024-01-31
---
# Addon Bridge Pattern
## Philosophy
**WooNooW Core = Zero Addon Dependencies**
We don't integrate specific plugins into WooNooW core. Instead, we provide:
1. **Hook system** for addons to extend functionality
2. **Bridge snippets** for compatibility with existing plugins
3. **Addon development guide** for building proper WooNooW addons
---
## The Problem
Example: **Rajaongkir** (Indonesian Shipping Plugin).
It removes standard fields and adds custom dropdowns, storing data in WooCommerce sessions.
It doesn't work with WooNooW OrderForm out of the box because the OrderForm uses standard fields and API-based validation.
---
## Solution: Bridge Snippet
### Option A: Standalone Bridge Plugin
Create a tiny bridge plugin that makes the third-party plugin work with WooNooW.
```php
/**
* Plugin Name: WooNooW Rajaongkir Bridge
* Description: Makes Rajaongkir plugin work with WooNooW OrderForm
* Version: 1.0.0
*/
// Hook into WooNooW's shipping calculation
add_filter('woonoow_before_shipping_calculate', function($shipping_data) {
if ($shipping_data['country'] === 'ID' && !empty($shipping_data['city'])) {
// Search API and set session data
$api = Cekongkir_API::get_instance();
$results = $api->search_destination_api($shipping_data['city']);
if (!empty($results[0])) {
WC()->session->set('selected_destination_id', $results[0]['id']);
}
}
return $shipping_data;
});
```
### Option B: Frontend Injection
Inject script to handle UI changes.
```typescript
import { addonLoader, addFilter } from '@woonoow/hooks';
addonLoader.register({
id: 'rajaongkir-bridge',
init: () => {
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
if (formData.shipping?.country !== 'ID') return content;
return (
<>
{content}
<div className="custom-field">
{/* Custom Destination Select */}
</div>
</>
);
});
}
});
```

View File

@@ -0,0 +1,50 @@
---
title: Module Integration
description: Integrating Addons usage with Module Registry
date: 2024-01-31
---
# Addon-Module Integration
## Vision
**Module Registry as the Single Source of Truth.**
Functionality extensions—whether built-in Modules or external Addons—should live in the same UI.
## Registration
Addons register themselves via the `woonoow/addon_registry` filter.
```php
add_filter('woonoow/addon_registry', function($addons) {
$addons['my-shipping-addon'] = [
'id' => 'my-shipping-addon',
'name' => 'My Shipping',
'description' => 'Custom shipping integration',
'version' => '1.0.0',
'author' => 'My Company',
'category' => 'shipping',
'icon' => 'truck',
'settings_url' => '/settings/shipping/my-addon',
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
];
return $addons;
});
```
This ensures your addon appears in the **WooNooW Modules** list with a consistent UI, toggle switch, and settings link.
## Settings Pages
Addons can register their own SPA routes for settings pages.
```php
add_filter('woonoow/spa_routes', function($routes) {
$routes[] = [
'path' => '/settings/shipping/my-addon',
'component_url' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
'title' => 'My Addon Settings',
];
return $routes;
});
```

View File

@@ -0,0 +1,79 @@
---
title: React Integration
description: Using React within WooNooW Addons
date: 2024-01-31
---
# Addon React Integration
## The Challenge
External addons cannot bundle React because WooNooW already ships with a React runtime. Bundling it again would cause conflicts and bloat.
## Solution: Exposed Runtime
WooNooW exposes its React instance and Component library on the `window` object.
### Core Setup (How it works internally)
```typescript
// WooNooW Core
window.WooNooW = {
React: React,
ReactDOM: ReactDOM,
components: {
Button: Button,
Input: Input,
Select: Select,
},
hooks: { addFilter, addAction }
};
```
### Addon implementation
#### Level 1: Vanilla JS (No Build)
Good for simple injections.
```javascript
(function() {
window.addEventListener('woonoow:loaded', function() {
window.WooNooW.addFilter('woonoow_order_form_after_shipping', function(container) {
// Manual DOM manipulation
return container;
});
});
})();
```
#### Level 2: React with Build (Recommended)
Use `vite` or `webpack` and configure React as an **external**.
**vite.config.js**
```javascript
export default {
build: {
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'window.WooNooW.React',
'react-dom': 'window.WooNooW.ReactDOM'
}
}
}
}
};
```
**Index.tsx**
```typescript
const { React, hooks, components } = window.WooNooW;
const { Button } = components;
function MyAddonComponent() {
return <Button>Click Me</Button>;
}
```

View File

@@ -0,0 +1,143 @@
---
title: Checkout
description: Streamlined purchasing experience in WooNooW
date: 2024-01-31
---
## Overview
The checkout process includes:
1. **Cart Review** - Verify items before checkout
2. **Customer Information** - Billing and shipping details
3. **Payment Method** - Select how to pay
4. **Order Confirmation** - Complete the purchase
---
## Checkout Flow
### Step 1: Cart
Before checkout, customers review their cart:
- Product list with images
- Quantity adjustments
- Remove items
- Apply coupon codes
- See subtotal, shipping, and total
### Step 2: Customer Details
Customers provide:
- **Email address**
- **Billing information**
- **Shipping address** (if different from billing)
> **Note**: Logged-in customers have their details pre-filled.
### Step 3: Shipping Method
If physical products are in the cart:
- Available shipping methods are shown
- Shipping cost is calculated
- Customer selects preferred method
### Step 4: Payment
Customers choose their payment method:
- Credit/Debit Card (Stripe, PayPal, etc.)
- Bank Transfer
- Cash on Delivery
- Other configured gateways
### Step 5: Place Order
After reviewing everything:
- Click "Place Order"
- Payment is processed
- Confirmation page is shown
- Email receipt is sent
---
## Features
### Guest Checkout
Allow customers to checkout without creating an account.
Configure in **WooCommerce → Settings → Accounts & Privacy**.
### Coupon Codes
Customers can apply discount codes:
1. Enter code in the coupon field
2. Click "Apply"
3. Discount is reflected in total
### Order Notes
Optional field for customers to add special instructions.
---
## Payment Gateways
### Supported Gateways
WooNooW supports all WooCommerce payment gateways:
| Gateway | Type |
|---------|------|
| Bank Transfer (BACS) | Manual |
| Check Payments | Manual |
| Cash on Delivery | Manual |
| PayPal | Card / PayPal |
| Stripe | Card |
| Square | Card |
### Configuring Gateways
1. Go to **WooNooW → Settings → Payments**
2. Enable desired payment methods
3. Configure API keys and settings
4. Test with sandbox/test mode first
---
## After Checkout
### Order Confirmation Page
Shows:
- Order number
- Order summary
- Next steps
### Confirmation Email
Automatically sent to customer with:
- Order details
- Payment confirmation
- Shipping information (if applicable)
---
## Troubleshooting
### "Place Order" Button Not Working
1. Check all required fields are filled
2. Verify payment gateway is properly configured
3. Check browser console for JavaScript errors
### Payment Declined
1. Customer should verify card details
2. Check payment gateway dashboard for error details
3. Ensure correct API keys are configured
### Shipping Not Showing
1. Verify shipping zones are configured in WooCommerce
2. Check if products have weight/dimensions set
3. Confirm customer's address is in a configured zone

View File

@@ -0,0 +1,98 @@
---
title: Shop Page
description: Browsing and filtering your product catalog
date: 2024-01-31
---
## Overview
The WooNooW shop page provides:
- **Product Grid** - Visual display of products
- **Search** - Find products by name
- **Filters** - Category and sorting options
- **Pagination** - Navigate through products
---
## Features
### Product Cards
Each product displays:
- Product image
- Product name
- Price (with sale price if applicable)
- Add to Cart button
- Wishlist button (if enabled)
### Search
Type in the search box to filter products by name. Search is instant and updates the grid as you type.
### Category Filter
Filter products by category using the dropdown. Shows:
- All Categories
- Individual categories with product count
### Sorting
Sort products by:
- Default sorting
- Popularity
- Average rating
- Latest
- Price: Low to High
- Price: High to Low
---
## Customization
### Grid Layout
Configure the product grid in **WooNooW → Appearance**:
| Device | Options |
|--------|---------|
| Mobile | 1-2 columns |
| Tablet | 2-4 columns |
| Desktop | 2-6 columns |
### Product Card Style
Product cards can display:
- **Image** - Product featured image
- **Title** - Product name
- **Price** - Current price and sale price
- **Rating** - Star rating (if reviews enabled)
- **Add to Cart** - Quick add button
---
## Navigation
### Clicking a Product
Clicking a product card navigates to the full product page where customers can:
- View all images
- Select variations
- Read description
- Add to cart
### Back to Shop
From any product page, use the breadcrumb or browser back button to return to the shop.
---
## Performance
### Lazy Loading
Product images load as they come into view, improving initial page load time.
### Infinite Scroll vs Pagination
Currently uses pagination. Infinite scroll may be added in future versions.

View File

@@ -0,0 +1,26 @@
---
title: Shortcodes
description: Available shortcodes in WooNooW
date: 2024-01-31
---
## The Primary Shortcode
WooNooW operates as a Single Page Application (SPA). To render the entire store application, you only need one shortcode:
### `[woonoow_spa]`
This shortcode initializes the SPA router and renders the appropriate view based on the URL (Shop, Product, Cart, Checkout, Account).
**Usage:**
Place this shortcode on your designated "Store" page.
```text
[woonoow_spa]
```
---
## Legacy Shortcodes
*Note: Previous versions utilize individual shortcodes (`[woonoow_shop]`, etc.). These are now consolidated into the single SPA entry point for better performance and routing state management.*

View File

@@ -1,92 +0,0 @@
---
title: Customize
description: This guide provides instructions on customizing our application.
date: 29-11-2024
---
## Markdown Options
To customize Markdown parsing, navigate to `@lib/markdown.ts` and locate the `parseMdx` function. You can add your desired plugins in the `mdxOptions`. Heres an example:
```ts:lib/markdown.ts
async function parseMdx<Frontmatter>(rawMdx: string) {
return await compileMDX<Frontmatter>({
source: rawMdx,
options: {
parseFrontmatter: true,
mdxOptions: {
// Add your plugins here
rehypePlugins: [Shiki],
remarkPlugins: [remarkGfm],
},
},
components,
});
}
```
## Fonts
Currently, this project uses the `Geist` font. If you want to use other fonts, you can change the configuration in the main layout as shown below:
### Google Fonts
To use a Google font, import your desired font from `next/font/google`, initialize it with options, and apply the variable to the `body`:
```tsx:app/layout.tsx
import { Space_Grotesk } from "next/font/google";
const fontSans = Space_Grotesk({
display: "swap",
variable: "--font-regular",
weight: "400",
subsets: ["latin"],
});
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={`${fontSans.variable} font-regular`}>
{children}
</body>
</html>
);
}
```
### Local Fonts
To use a local font, you need to use the local font loader from Next.js. Pass the options and apply them to the `body`:
```tsx:app/layout.tsx
import localFont from "next/font/local";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-regular",
weight: "100 900",
});
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={`${geistSans.variable} font-regular`}>
{children}
</body>
</html>
);
}
```
For both options, ensure that you add the variable to `tailwind.config.ts`:
```ts:tailwind.config.ts
{
// ...
extend: {
fontFamily: {
regular: ["var(--font-regular)"],
},
}
}
```

View File

@@ -0,0 +1,63 @@
---
title : Development
description : for Development server and production
date : 10-12-2024
---
## Heading 2
this is regular text written in markdown format with `inline code`, **bold**, and *italic*
### Heading 3
example of ordered list format :
- list one
- sub list
- list two
- list three
#### Heading 4
Below is an example of how to write a code block :
````plaintext
```javascript:main.js showLineNumbers {3-4}
function isRocketAboutToCrash() {
// Check if the rocket is stable
if (!isStable()) {
NoCrash(); // Prevent the crash
}
}
```
````
example note :
```plaintext
<Note type="note" title="Note">
This is a general note to convey information to the user.
</Note>
```
displaying an image in markdown format :
```plaintext
![Alt text for the image](/images/example-img.png)
```
render as :
![Alt text for the image](/images/example-img.png)
For a complete guide on using markdown content in DocuBook, please refer to the [Components](https://docubook.pro/docs/components) page.
<Note type="warning" title="Warning">
every page that is indexed in a folder will have an `index.mdx` file with metadata :
```plaintext
---
title : Introduction
description : overview or synopsis of a project
date : 10-12-2024
image : example-img.png
---
```
</Note>

View File

@@ -1,9 +0,0 @@
---
title: Getting Started
description: Set up Your Documentations with DocuBook
date: 29-05-2025
---
Welcome to DocuBook! This guide will help you set up and customize your documentation site quickly. Whether you're creating API references, user guides, or technical documentation, these components will help you build a professional and consistent documentation experience. Follow the steps below to get started with DocuBook and make the most of its powerful features.
<Outlet path="getting-started" />

View File

@@ -1,84 +1,94 @@
--- ---
title: Installation title: Installation Guide
description: Installation guide for our application. description: Installing WooNooW on your WordPress site
date: 17-02-2025 date: 2024-01-31
--- ---
Setting up DocuBook is straightforward, with options to clone the repository or use the convenient `npx` command. DocuBook requires [Node.js](https://nodejs.org/) version 18 or higher for optimal performance. ## Requirements
## Clone Repository Before installing, ensure your site meets these requirements:
To get started, you can clone the DocuBook repository directly from GitHub. | Requirement | Minimum | Recommended |
|-------------|---------|-------------|
| WordPress | 6.0+ | Latest |
| WooCommerce | 7.0+ | Latest |
| PHP | 7.4+ | 8.1+ |
| MySQL | 5.7+ | 8.0+ |
<Stepper> ## Installation Methods
<StepperItem title="Step 1: Clone the DocuBook Repository">
Begin by cloning the DocuBook repository from GitHub:
```bash ### Method 1: WordPress Admin (Recommended)
git clone --branch main https://github.com/DocuBook/docubook.git
```
</StepperItem> 1. Go to **Plugins → Add New**
2. Click **Upload Plugin**
3. Select the `woonoow.zip` file
4. Click **Install Now**
5. Click **Activate**
<StepperItem title="Step 2: Access the Project Directory"> ### Method 2: FTP Upload
After cloning, navigate into the project directory to start setting up:
```bash 1. Extract `woonoow.zip` to get the `woonoow` folder
cd docubook 2. Upload to `/wp-content/plugins/`
``` 3. Go to **Plugins → Installed Plugins**
4. Find WooNooW and click **Activate**
</StepperItem> ## Post-Installation
<StepperItem title="Step 3: Install Required Dependencies"> After activation, WooNooW automatically:
Install all necessary project dependencies with npm:
```bash ### 1. Creates Store Page
npm install A new "Store" page is created with the SPA shortcode. This is your main storefront.
```
</StepperItem> ### 2. Registers Rewrite Rules
URL routes like `/store/shop` and `/store/product/...` are registered.
<StepperItem title="Step 4: Launch the Development Server"> > **Note**: If you see 404 errors, go to **Settings → Permalinks** and click **Save Changes** to flush rewrite rules.
Finally, start the development server to view the project locally:
```bash ### 3. Sets Default Configuration
npm run dev Basic appearance settings are configured with sensible defaults.
```
</StepperItem> ## Verification Checklist
</Stepper>
## Quick Setup with CLI After installation, verify everything works:
For a faster setup, use the `cli` command to create a new DocuBook project in one step: - [ ] Plugin activated without errors
- [ ] WooNooW menu appears in admin sidebar
- [ ] Store page exists (check **Pages**)
- [ ] `/store` URL loads the SPA
- [ ] Products display on shop page
<Tabs defaultValue="npm" className="pt-5 pb-1"> ## WooCommerce Compatibility
<TabsList>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
</TabsList>
<TabsContent value="npm">
```shell
npx @docubook/create@latest
```
</TabsContent>
<TabsContent value="pnpm">
```shell
pnpm dlx @docubook/create@latest
```
</TabsContent>
<TabsContent value="bun">
```shell
bunx @docubook/create@latest
```
</TabsContent>
<TabsContent value="yarn">
```shell
yarn dlx @docubook/create@latest
```
</TabsContent>
</Tabs>
With this setup, youll have a ready-to-use DocuBook instance to start building your documentation. WooNooW works alongside WooCommerce:
| WooCommerce Page | WooNooW Behavior (Full Mode) |
|------------------|------------------------------|
| `/shop` | Redirects to `/store/shop` |
| `/product/...` | Redirects to `/store/product/...` |
| `/cart` | Redirects to `/store/cart` |
| `/checkout` | Redirects to `/store/checkout` |
| `/my-account` | Redirects to `/store/my-account` |
When SPA Mode is **Disabled**, WooCommerce pages work normally.
## Updating
To update WooNooW:
1. Download the latest version
2. Go to **Plugins → Installed Plugins**
3. Deactivate WooNooW (optional but recommended)
4. Delete the old version
5. Install and activate the new version
Your settings are preserved in the database.
## Uninstalling
To completely remove WooNooW:
1. Deactivate the plugin (restores WooCommerce page content)
2. Delete the plugin
3. (Optional) Delete WooNooW options from database
> **Note**: Deactivating restores original WooCommerce shortcodes to Cart, Checkout, and My Account pages.

View File

@@ -1,50 +1,39 @@
--- ---
title: Introduction title: Introduction
description: This section provides an overview of DocuBook. description: Overview of the WooNooW Plugin ecosystem
date: 29-11-2024 date: 2024-01-31
image: example-img.png
--- ---
Welcome to **DocuBook**! This template provides a modern, flexible, and user-friendly foundation for creating engaging documentation. Whether you're building a knowledge base, project docs, or a personal blog, DocuBook makes it easy to set up and scale. ## What is WooNooW?
## Open Source Philosophy **WooNooW** is a comprehensive WooCommerce enhancement suite designed to power up your online store. It provides a robust set of tools for license management, subscription handling, and custom checkout flows.
DocuBook is proudly **open-source**! 🎉 We believe in creating an accessible, collaborative platform that thrives on community contributions.
<Note title="Contribute">
Interested in helping us improve? Check out our [GitHub
repository](https://github.com/DocuBook/docubook) to get started! From
feature suggestions to bug fixes, all contributions are welcome.
</Note>
## Project Overview
**DocuBook** is more than just a documentation template. It's a **complete toolkit** designed for modern web development. Key features include:
- **Markdown & MDX Support:** Easily write documentation in Markdown, with the option to include interactive components via MDX.
- **Customizable Themes:** Designed with a minimalist ShadCN-inspired theme thats easy to style.
- **SEO-Optimized:** Each page is SEO-ready, ensuring search engines can find and rank your content.
- **Interactive Code Blocks:** Beautifully styled, language-specific code blocks for an enhanced reading experience.
### Key Features ### Key Features
| **Feature** | **Description** | * **License Management**: Sell and manage digital software licenses with ease.
| ------------------------------- | ----------------------------------------------------- | * **OAuth Activation**: Secure, user-verified license activation flow.
| MDX Support | Write interactive documentation with MDX. | * **Subscription Utilities**: Enhanced subscription notifications and manual renewal controls.
| Nested Pages | Organize content in a nested, hierarchical structure. | * **Developer API**: Full REST API for extending functionality.
| Pagination | Split content across multiple pages. |
| Syntax Highlighting | Highlight code for better readability. |
| Code Line Highlighting & Titles | Highlight specific lines with descriptive titles. |
| Interactive Code Blocks | Language-specific and interactive code display. |
| Custom Markdown Components | Embed custom, reusable components in your docs. |
| Static Site Generation | Generate a static, high-performance site. |
| SEO-Optimized | Structured for optimal search engine indexing. |
## Technology & Libraries ---
DocuBook leverages cutting-edge technologies to ensure performance and flexibility: ## Why use WooNooW?
- **Next.js 14** - The powerful React framework optimized for production. If you are a plugin developer or a digital store owner using WooCommerce, WooNooW simplifies the complex parts of your business logic.
- **Tailwind CSS** - Utility-first styling for quick, clean designs.
- **Shadcn-UI** - Elegant, accessible components for a polished look. <CardGroup>
- **next-mdx-remote** - Enables MDX support for dynamic, interactive Markdown content. <Card title="For Developers" icon="Terminal">
Built with extensibility in mind. Use our hooks, filters, and REST API to build custom integrations.
</Card>
<Card title="For Store Owners" icon="Box">
Manage your digital products, verified customers, and subscriptions all in one place.
</Card>
</CardGroup>
## Getting Help
Need assistance? Check out our support channels:
* [Official Support](https://woonoow.com/support)
* [Community Forums](https://community.woonoow.com)
* [Developer Docs](https://docs.woonoow.com)

View File

@@ -0,0 +1,7 @@
{
"pages": [
"introduction",
"quick-start-guide",
"development"
]
}

View File

@@ -1,87 +0,0 @@
---
title: Project Structure
description: This section provides an overview of Project Structure.
date: 29-11-2024
---
## app
This folder contains the main application code for Next.js, managing layouts, routing, and specific content pages. It is organized to support both the `docs` sections, with dedicated pages and layouts for each section.
```
app // Main Next.js application folder
├── page.tsx // Hero page - the entry point of the app showcasing key content
├── layout.tsx // Main layout file, applies global navigation and footer
└── docs
├── layout.tsx // Documentation layout with sidebar, providing easy navigation across doc pages
└── [[...slug]] // Catch-all route to handle nested documentation pages, allowing flexible doc structure
```
## components
This folder contains all reusable components used throughout the project. Its structured to categorize components, making it easy to locate and manage UI elements, Markdown customizations, and navigation.
```
components // Reusable components
├── ui // ShadCN components, includes standardized UI elements like buttons, forms, and modals
├── markdown // Custom Markdown components for rendering rich Markdown content
│ ├── CodeBlock.tsx // Component for rendering syntax-highlighted code blocks
│ ├── Image.tsx // Component for handling responsive images in Markdown
│ └── Table.tsx // Component to render tables with consistent styling
└── navbar.tsx // Main navigation component for the site header, managing links and responsive behavior
```
## styles
This folder contains global and component-specific CSS files, allowing for project-wide styling consistency and customizations.
```
styles // CSS files
├── globals.css // Global CSS file, includes Tailwind base, utilities, and custom global styles
├── syntax.css // Syntax highlighting styles, providing consistent code block appearance across the site
└── overrides.css // Additional custom styles that override specific component or plugin defaults
```
## contents
This folder stores all Markdown content for the documentation and blog sections, with clear organization by content type. Each Markdown file represents a single piece of content, with frontmatter used for metadata.
```
contents // Markdown content for blogs and documentation
├── docs // Documentation content, structured to support nested sections and pages
│ ├── getting-started // Main Folder at the Contents
│ └─── installation // Subfolder for tutorial-style content
| └── index.mdx // Step-by-step tutorial on a specific topic
```
## public
This folder holds all static assets, such as images, videos, fonts, and icons. These files are directly accessible via URL, so its important to avoid sensitive or private content here.
```
public // Publicly accessible assets
├── images // Image assets, used across various parts of the app
│ ├── og-image.png // Default opengraph image for thumbnail
│ └── docu.svg // Your site Logo
├── favicon.ico // Favicon for the app
└── videos // Video files for media content, if any
```
## lib
This folder contains helper functions and utilities used across the application, such as Markdown parsing and routing logic. These utilities help keep the codebase clean and organized by separating out common functionality.
```
lib // Utility or helper functions
├── changelog.ts // Change log for the app, used to display recent changes and updates
├── markdown.ts // Markdown parsing logic, converts Markdown to HTML and adds custom components
├── routes-config.ts // Routing configuration for docs, maps URLs to content files for dynamic routing
└── utils.tsx // General utility functions used across the app, such as data formatting and validation helpers
```
## Additional
- **`package.json`**: Contains metadata about the project, dependencies, and scripts for building and running the application.
- **`tailwind.config.ts`**: Configures Tailwind CSS, allowing customization of theme colors, fonts, and responsive breakpoints specific to this project.
This structure organizes your project in a way that supports scalability and maintainability, making it easier to navigate and work on different sections of the application.

View File

@@ -1,145 +1,63 @@
--- ---
title: Quick Start Guide title : Quick Start Guide
description: Get up and running with DocuBook in minutes with this comprehensive guide description : a quick way to understand how to use it
date: 20-05-2025 date : 10-12-2024
--- ---
Welcome to DocuBook! This guide will help you set up and customize your documentation site efficiently. ## Heading 2
## Prerequisites this is regular text written in markdown format with `inline code`, **bold**, and *italic*
Before we begin, ensure you have the following installed: ### Heading 3
- [Git](https://git-scm.com/) example of ordered list format :
- [Node.js 18+](https://nodejs.org/) or [Bun 1.0+](https://bun.sh/)
- A package manager (npm, yarn, or pnpm)
## Installation - list one
- sub list
- list two
- list three
<Note type="note" title="Initial Setup"> #### Heading 4
Follow the [installation guide](/docs/getting-started/installation) to set up your project dependencies and configuration.
Below is an example of how to write a code block :
````plaintext
```javascript:main.js showLineNumbers {3-4}
function isRocketAboutToCrash() {
// Check if the rocket is stable
if (!isStable()) {
NoCrash(); // Prevent the crash
}
}
```
````
example note :
```plaintext
<Note type="note" title="Note">
This is a general note to convey information to the user.
</Note> </Note>
```
## Project Setup displaying an image in markdown format :
### Configuration
<Stepper>
<StepperItem title="Add Favicon">
Place your favicon at `public/favicon.ico` for browser tab display.
</StepperItem>
<StepperItem title="Add Logo">
Add your logo at `public/images/docu.svg` (SVG format recommended for scalability).
</StepperItem>
<StepperItem title="Update Site Information">
Customize your site's metadata in `docu.json`:
- Site title and description
- Navigation structure
- Default theme settings
</StepperItem>
</Stepper>
## Content Management
### File Structure
DocuBook organizes content in a hierarchical structure:
```plaintext ```plaintext
contents/ ![Alt text for the image](/images/example-img.png)
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
``` ```
### Creating New Content render as :
![Alt text for the image](/images/example-img.png)
<Stepper> For a complete guide on using markdown content in DocuBook, please refer to the [Components](https://docubook.pro/docs/components) page.
<StepperItem title="1. Create Content Directory">
Organize your documentation by creating a new directory:
```bash
mkdir -p contents/docs/your-section/your-topic
```
Example for an API reference: <Note type="warning" title="Warning">
```bash every page that is indexed in a folder will have an `index.mdx` file with metadata :
mkdir -p contents/docs/api/authentication ```plaintext
``` ---
</StepperItem> title : Introduction
description : overview or synopsis of a project
<StepperItem title="2. Create MDX Content"> date : 10-12-2024
Add an `index.mdx` file with frontmatter metadata: image : example-img.png
````markdown ---
--- ```
title: Authentication </Note>
description: Learn how to implement user authentication
date: 2025-05-29
---
Your comprehensive guide to implementing authentication in your application.
## Getting Started
Start by setting up your authentication provider...
````
</StepperItem>
<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": "API",
"href": "/api",
"noLink": true,
"context": {
"icon": "Code2",
"description": "API Reference and Integration",
"title": "API"
},
"items": [
{ "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 Workflow
### Local Development
Start the development server with live reload:
```bash
# Using npm
npm run dev
# Or using Bun
bun dev
```
Access your site at [http://localhost:3000](http://localhost:3000)
### Building for Production
When ready to deploy:
```bash
# Build the production version
npm run build
# Start the production server
npm start
```

View File

@@ -1,165 +0,0 @@
---
title: Coffee
description: A warm, minimalistic design inspired by early computing. This theme is a replica of the "Coffee" theme from the `shiki-twoslash` package.
date: 2025-05-30
---
## Styles
<Note type="note" title="note">
You can override the default styles by adding your own CSS variables in the `globals.css` and `syntax.css` files.
</Note>
<Tabs defaultValue="globals" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="globals">globals.css</TabsTrigger>
<TabsTrigger value="syntax">syntax.css</TabsTrigger>
</TabsList>
<TabsContent value="globals">
```css:globals.css
/* Rich Coffee Theme */
@layer base {
:root {
--background: 35 40% 96%;
--foreground: 25 25% 10%;
--card: 0 0% 100%;
--card-foreground: 25 25% 10%;
--popover: 0 0% 100%;
--popover-foreground: 25 25% 10%;
--primary: 25 60% 40%;
--primary-foreground: 0 0% 100%;
--secondary: 35 30% 90%;
--secondary-foreground: 25 25% 10%;
--muted: 35 20% 94%;
--muted-foreground: 25 15% 35%;
--accent: 30 50% 38%;
--accent-foreground: 0 0% 100%;
--destructive: 5 85% 45%;
--destructive-foreground: 0 0% 100%;
--border: 30 15% 85%;
--input: 30 15% 88%;
--ring: 25 60% 40%;
--radius: 0.5rem;
--chart-1: 25 60% 45%;
--chart-2: 30 55% 45%;
--chart-3: 35 50% 42%;
--chart-4: 20 45% 38%;
--chart-5: 40 45% 40%;
--line-number-color: rgba(0, 0, 0, 0.08);
}
.dark {
--background: 30 10% 6%;
--foreground: 35 20% 94%;
--card: 30 10% 8%;
--card-foreground: 35 20% 94%;
--popover: 30 10% 8%;
--popover-foreground: 35 20% 94%;
--primary: 30 45% 52%;
--primary-foreground: 30 10% 6%;
--secondary: 30 12% 14%;
--secondary-foreground: 35 20% 94%;
--muted: 30 10% 16%;
--muted-foreground: 30 15% 60%;
--accent: 28 40% 48%;
--accent-foreground: 0 0% 100%;
--destructive: 5 80% 55%;
--destructive-foreground: 0 0% 100%;
--border: 30 10% 20%;
--input: 30 10% 20%;
--ring: 30 45% 52%;
--chart-1: 30 45% 52%;
--chart-2: 28 40% 50%;
--chart-3: 32 45% 50%;
--chart-4: 25 35% 46%;
--chart-5: 35 40% 48%;
--line-number-color: rgba(255, 255, 255, 0.12);
}
}
```
</TabsContent>
<TabsContent value="syntax">
```css:syntax.css
/* Rich Coffee Theme - Syntax Highlighting */
/* Light Mode */
.keyword {
color: hsl(25 60% 35%); /* Rich coffee brown */
}
.function {
color: hsl(30 55% 38%); /* Warm coffee brown */
}
.punctuation {
color: hsl(30 15% 55%); /* Muted brown */
}
.comment {
color: hsl(25 15% 45%); /* Muted brown */
font-style: italic;
opacity: 0.9;
}
.string,
.constant,
.annotation,
.boolean,
.number {
color: hsl(32 50% 40%); /* Warm brown with hint of orange */
}
.tag {
color: hsl(25 65% 30%); /* Dark rich coffee */
}
.attr-name {
color: hsl(28 55% 38%); /* Warm brown */
}
.attr-value {
color: hsl(35 55% 35%); /* Slightly orange brown */
}
/* Dark Mode */
.dark .keyword {
color: hsl(30 50% 65%); /* Light warm brown */
}
.dark .function {
color: hsl(28 55% 68%); /* Light warm brown */
}
.dark .string,
.dark .constant,
.dark .annotation,
.dark .boolean,
.dark .number {
color: hsl(35 50% 72%); /* Light warm brown with hint of orange */
}
.dark .tag {
color: hsl(30 50% 75%); /* Light warm brown */
}
.dark .attr-name {
color: hsl(28 45% 70%); /* Light warm brown */
}
.dark .attr-value {
color: hsl(35 50% 70%); /* Light warm brown with hint of orange */
}
.dark .comment {
color: hsl(30 15% 65%); /* Light brown-gray */
opacity: 0.8;
}
.dark .punctuation {
color: hsl(30 15% 70%); /* Light brown-gray */
opacity: 0.9;
}
```
</TabsContent>
</Tabs>

View File

@@ -1,160 +0,0 @@
---
title: Default Theme
description: This page explains the Modern Blue theme colors used in DocuBook, which is a default theme provided by DocuBook.
date: 2025-05-20
---
## Styles
<Note type="note" title="note">
You can override the default styles by adding your own CSS variables in the `globals.css` and `syntax.css` files.
</Note>
<Tabs defaultValue="globals" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="globals">globals.css</TabsTrigger>
<TabsTrigger value="syntax">syntax.css</TabsTrigger>
</TabsList>
<TabsContent value="globals">
```css:globals.css
/* Modern Blue Theme */
@layer base {
:root {
--background: 210 40% 98%;
--foreground: 220 30% 15%;
--card: 0 0% 100%;
--card-foreground: 220 30% 15%;
--popover: 0 0% 100%;
--popover-foreground: 220 30% 15%;
--primary: 210 81% 56%; /* #2281E3 */
--primary-foreground: 0 0% 100%;
--secondary: 210 30% 90%;
--secondary-foreground: 220 30% 15%;
--muted: 210 20% 92%;
--muted-foreground: 220 15% 50%;
--accent: 200 100% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 100%;
--border: 210 20% 85%;
--input: 210 20% 85%;
--ring: 210 81% 56%;
--radius: 0.5rem;
--chart-1: 210 81% 56%;
--chart-2: 200 100% 40%;
--chart-3: 220 76% 60%;
--chart-4: 190 90% 50%;
--chart-5: 230 86% 45%;
--line-number-color: rgba(0, 0, 0, 0.05);
}
.dark {
--background: 220 25% 10%;
--foreground: 210 30% 96%;
--card: 220 25% 15%;
--card-foreground: 210 30% 96%;
--popover: 220 25% 15%;
--popover-foreground: 210 30% 96%;
--primary: 210 100% 65%;
--primary-foreground: 220 25% 10%;
--secondary: 215 25% 20%;
--secondary-foreground: 210 30% 96%;
--muted: 215 20% 25%;
--muted-foreground: 215 20% 65%;
--accent: 200 100% 60%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 70%;
--destructive-foreground: 0 0% 100%;
--border: 215 20% 25%;
--input: 215 20% 25%;
--ring: 210 100% 65%;
--chart-1: 210 100% 65%;
--chart-2: 200 100% 60%;
--chart-3: 220 90% 70%;
--chart-4: 190 100% 65%;
--chart-5: 230 90% 60%;
--line-number-color: rgba(255, 255, 255, 0.1);
}
}
```
</TabsContent>
<TabsContent value="syntax">
```css:syntax.css
/* Modern Blue Theme */
/* Light Mode */
.keyword {
color: #1d4ed8; /* Darker blue for better contrast */
}
.function {
color: #0369a1; /* Deep blue */
}
.punctuation {
color: #4b5563; /* Slate gray */
}
.comment {
color: #6b7280; /* Muted gray */
font-style: italic;
}
.string,
.constant,
.annotation,
.boolean,
.number {
color: #0d9488; /* Teal for better distinction */
}
.tag {
color: #1d4ed8; /* Matching keyword color */
}
.attr-name {
color: #0284c7; /* Sky blue */
}
.attr-value {
color: #2563eb; /* Primary blue */
}
/* Dark Mode */
.dark .keyword {
color: #60a5fa; /* Soft blue */
}
.dark .function {
color: #38bdf8; /* Sky blue */
}
.dark .string,
.dark .constant,
.dark .annotation,
.dark .boolean,
.dark .number {
color: #2dd4bf; /* Teal */
}
.dark .tag {
color: #60a5fa; /* Matching keyword color */
}
.dark .attr-name {
color: #7dd3fc; /* Lighter blue */
}
.dark .attr-value {
color: #3b82f6; /* Brighter blue */
}
.dark .comment {
color: #9ca3af; /* Lighter gray for dark mode */
}
.dark .punctuation {
color: #9ca3af; /* Lighter gray for dark mode */
}
```
</TabsContent>
</Tabs>

View File

@@ -1,161 +0,0 @@
---
title: Fresh Lime
description: A fresh and vibrant freshlime-themed color scheme that's easy on the eyes while maintaining excellent readability.
date: 2025-05-30
---
## Styles
<Note type="note" title="note">
You can override the default styles by adding your own CSS variables in the `globals.css` and `syntax.css` files.
</Note>
<Tabs defaultValue="globals" className="pt-5 pb-1">
<TabsList>
<TabsTrigger value="globals">globals.css</TabsTrigger>
<TabsTrigger value="syntax">syntax.css</TabsTrigger>
</TabsList>
<TabsContent value="globals">
```css:globals.css
/* Fresh Lime Theme */
@layer base {
:root {
--background: 85 45% 98%;
--foreground: 85 30% 10%;
--card: 0 0% 100%;
--card-foreground: 85 30% 10%;
--popover: 0 0% 100%;
--popover-foreground: 85 30% 10%;
--primary: 85 70% 45%;
--primary-foreground: 0 0% 100%;
--secondary: 85 40% 90%;
--secondary-foreground: 85 30% 10%;
--muted: 85 30% 92%;
--muted-foreground: 85 15% 45%;
--accent: 85 60% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 100%;
--border: 85 25% 88%;
--input: 85 25% 88%;
--ring: 85 70% 45%;
--radius: 0.5rem;
--chart-1: 85 70% 45%;
--chart-2: 85 60% 40%;
--chart-3: 85 80% 40%;
--chart-4: 85 85% 35%;
--chart-5: 85 90% 30%;
--line-number-color: rgba(0, 0, 0, 0.05);
}
.dark {
--background: 85 20% 8%;
--foreground: 85 30% 96%;
--card: 85 20% 10%;
--card-foreground: 85 30% 96%;
--popover: 85 20% 10%;
--popover-foreground: 85 30% 96%;
--primary: 85 75% 55%;
--primary-foreground: 85 20% 8%;
--secondary: 85 25% 18%;
--secondary-foreground: 85 30% 96%;
--muted: 85 20% 20%;
--muted-foreground: 85 20% 70%;
--accent: 85 70% 50%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 65%;
--destructive-foreground: 0 0% 100%;
--border: 85 25% 22%;
--input: 85 25% 22%;
--ring: 85 75% 55%;
--chart-1: 85 75% 55%;
--chart-2: 85 70% 50%;
--chart-3: 85 80% 45%;
--chart-4: 85 85% 40%;
--chart-5: 85 90% 35%;
--line-number-color: rgba(255, 255, 255, 0.1);
}
}
```
</TabsContent>
<TabsContent value="syntax">
```css:syntax.css
/* Fresh Lime Theme - Syntax Highlighting */
/* Light Mode */
.keyword {
color: hsl(85 70% 30%); /* Dark lime green */
}
.function {
color: hsl(85 65% 35%); /* Slightly lighter lime */
}
.punctuation {
color: hsl(85 15% 50%); /* Muted green-gray */
}
.comment {
color: hsl(85 15% 50%); /* Muted green-gray */
font-style: italic;
}
.string,
.constant,
.annotation,
.boolean,
.number {
color: hsl(85 50% 35%); /* Medium lime green */
}
.tag {
color: hsl(85 70% 25%); /* Darker lime */
}
.attr-name {
color: hsl(85 60% 35%); /* Lime green */
}
.attr-value {
color: hsl(85 70% 30%); /* Dark lime green */
}
/* Dark Mode */
.dark .keyword {
color: hsl(85 75% 65%); /* Bright lime */
}
.dark .function {
color: hsl(85 75% 60%); /* Slightly less bright lime */
}
.dark .string,
.dark .constant,
.dark .annotation,
.dark .boolean,
.dark .number {
color: hsl(85 60% 70%); /* Light lime */
}
.dark .tag {
color: hsl(85 70% 70%); /* Bright lime */
}
.dark .attr-name {
color: hsl(85 65% 70%); /* Light lime */
}
.dark .attr-value {
color: hsl(85 75% 65%); /* Bright lime */
}
.dark .comment {
color: hsl(85 15% 60%); /* Light gray-green */
}
.dark .punctuation {
color: hsl(85 20% 70%); /* Light gray-green */
}
```
</TabsContent>
</Tabs>

View File

@@ -1,9 +0,0 @@
---
title : Theme Colors
description : Color system used in DocuBook for styling UI elements
date : 2025-05-20
---
DocuBook comes with a carefully designed color system that ensures consistency and accessibility across your documentation. The theme includes both light and dark modes, automatically adapting to the user's system preferences.
<Outlet path="getting-started/theme-colors" />

View File

@@ -1,77 +0,0 @@
---
title: Llms.txt - Generate Custom Theme
description: Complete guide for creating and using AI-generated custom themes based on LLMS context.
date: 2025-05-31
---
## Introduction
This document explains how to create and implement custom color themes using AI with LLMS context. The context includes a consistent color palette for both light and dark modes, along with syntax highlighting colors for code.
## File Structure
<Files>
<Folder name="styles">
<File name="globals.css" />
<File name="syntax.css" />
</Folder>
</Files>
## Generate New Theme
<Stepper>
<StepperItem title="Step 1: Access LLM for Color Generation">
Ensure you have access to a language model (LLM) that supports color palette generation
</StepperItem>
<StepperItem title="Step 2: Prepare Color Context">
Share the content of the [llms.txt](/llms.txt) file with the AI to provide the current color scheme context.
</StepperItem>
<StepperItem title="Step 3: Define Color Requirements">
Determine your preferred color scheme or theme requirements
</StepperItem>
<StepperItem title="Step 4: Generate New Theme">
Use the following prompt to request a new theme from the AI:
```markdown
Create a new color theme based on the provided context with the following requirements:
1. Different primary color #2281E3
2. Maintain good contrast for accessibility
3. Include variants for both light and dark modes
4. Keep the same structure as the provided context
```
<Note type="danger" title="Colors example">
changes the value of Hex color `#2281E3` into your brand colors.
</Note>
</StepperItem>
</Stepper>
## Implementation
After receiving the new color palette:
1. Copy the generated CSS code to your theme file
2. Update CSS variables in `globals.css`
3. Adjust syntax colors in `syntax.css` if needed
4. Test the theme across different devices and display modes
## Example Implementation
```css
:root {
--primary: 210 81% 56%; /* Primary color */
--secondary: 210 30% 90%; /* Secondary color */
/* ... and so on */
}
.dark {
--primary: 210 100% 65%; /* Dark mode primary color */
/* ... and so on */
}
```
## Tips and Recommendations
- Always test color contrast to ensure accessibility
- Maintain consistency with existing themes
- Use tools like [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) to verify contrast ratios
- Keep backups of previous theme versions before making major changes
## Troubleshooting
- If colors don't change, ensure CSS variables are properly imported
- Check CSS selector specificity if styles conflict
- Verify dark/light mode toggling is working correctly

View File

@@ -0,0 +1,21 @@
---
title: Frontend Hooks
description: Hooks affecting the storefront and checkout experience
date: 2024-01-31
---
## Filters
### woonoow_ssr_cache_ttl
Customize the Time-To-Live (TTL) for Server Side Rendered fragments.
**Parameters:**
* `$ttl` (int): Time in seconds (Default: 3600).
### woonoow_standard_checkout_field_keys
Modify the standard set of checkout fields recognized by the system.
**Parameters:**
* `$keys` (array): List of field aliases (e.g., ['billing_first_name', ...]).

View File

@@ -0,0 +1,27 @@
---
title: Hooks Overview
description: Overview of Actions and Filters available in WooNooW
date: 2024-01-31
---
## Introduction
WooNooW provides a rich set of actions and filters allowing developers to customize almost every aspect of the plugin without modifying core files.
### Hook Categories
* [Notifications](/docs/hooks/notifications) - Customize email templates, subjects, and channels.
* [Subscriptions](/docs/hooks/subscriptions) - Intercept payment logic and modify gateway behavior.
* [Frontend & checkout](/docs/hooks/frontend) - Adjust checkout fields and SSR caching.
* [Newsletter](/docs/hooks/newsletter) - Subscribe/unsubscribe events.
### Usage Example
```php
add_filter('woonoow_email_default_subject', function($subject, $recipient, $event) {
if ($event === 'subscription_renewal') {
return 'Your subscription is renewing soon!';
}
return $subject;
}, 10, 3);
```

View File

@@ -0,0 +1,9 @@
{
"pages": [
"index",
"notifications",
"subscriptions",
"frontend",
"newsletter"
]
}

View File

@@ -0,0 +1,21 @@
---
title: Newsletter Hooks
description: Hooks related to the Newsletter module
---
## Actions
### woonoow_newsletter_subscribed
Triggered when a user subscribes to the newsletter.
**Parameters:**
* `$email` (string): The subscriber's email address.
* `$user_id` (int|null): The user ID if logged in, or null.
### woonoow_newsletter_unsubscribed
Triggered when a user unsubscribes from the newsletter.
**Parameters:**
* `$email` (string): The subscriber's email address.

View File

@@ -0,0 +1,108 @@
---
title: Notifications Hooks
description: Hooks related to email and push notifications
date: 2024-01-31
---
## Filters
### woonoow_email_default_templates
Modify the list of available email templates.
**Parameters:**
* `$templates` (array): Associative array of template paths.
### woonoow_email_default_subject
Customize the default subject line for emails.
**Parameters:**
* `$subject` (string): The default subject.
* `$recipient` (string): The recipient type (e.g., 'customer', 'admin').
* `$event` (string): The event triggering the email.
### woonoow_notification_channels
Register or modify available notification channels.
**Parameters:**
* `$channels` (array): list of channels.
### woonoow_email_variables
Add custom variables to be replaced in email templates.
**Parameters:**
* `$variables` (array): Key-value pairs of variables.
* `$event_id` (string): The current event ID.
* `$data` (array): Function context data.
### woonoow_email_template
Override the resolved template path for an email.
**Parameters:**
* `$template_path` (string): Absolute path to the template file.
---
## Actions
### woonoow_email_sent
Triggered after an email is successfully sent.
**Parameters:**
* `$event_id` (string): The event ID.
* `$recipient_type` (string): Type of recipient.
* `$email` (string): The email address it was sent to.
### woonoow_send_push_notification
Triggered when the system determines a push notification should be sent.
**Parameters:**
* `$event_id` (string): The event ID.
* `$recipient` (object): The recipient user object.
* `$data` (array): Payload data.
* `$recipient_type` (string): Type of recipient.
* `$email` (string): The email address it was sent to.
### woonoow_send_push_notification
Triggered to send a push notification.
**Parameters:**
* `$event_id` (string): The event key.
* `$recipient` (WP_User): The recipient user object.
* `$data` (array): Notification payload data.
### woonoow_notification_events_registry
Filter to register custom notification events.
**Parameters:**
* `$events` (array): The array of registered events.
**Example:**
```php
add_filter('woonoow_notification_events_registry', function($events) {
$events['my_custom_event'] = [
'title' => 'My Custom Event',
'description' => 'Triggered when something happens',
'recipient' => 'customer',
];
return $events;
});
```
### woonoow_notification_event_updated
Triggered when a notification event's settings are updated via API.
**Parameters:**
* `$event_id` (string): The event key.
* `$channel_id` (string): The channel (email/push).
* `$enabled` (bool): New status.
* `$recipient` (string): Recipient type.

View File

@@ -0,0 +1,26 @@
---
title: Subscription Hooks
description: Hooks for customizing subscription flows and payments
date: 2024-01-31
---
## Filters
### woonoow_pre_process_subscription_payment
Intercept the subscription payment process before it begins.
**Parameters:**
* `$result` (null|mixed): Return non-null to short-circuit the process.
* `$subscription` (WC_Subscription): The subscription object.
* `$order` (WC_Order): The renewal order.
### woonoow_process_subscription_payment
Modify or handle the payment processing via a custom gateway logic.
**Parameters:**
* `$result` (null|bool): The result of the payment.
* `$gateway` (object): The payment gateway instance.
* `$order` (WC_Order): The order being paid.
* `$subscription` (WC_Subscription): The subscription object.

View File

@@ -0,0 +1,5 @@
{
"pages": [
"oauth-flow"
]
}

View File

@@ -0,0 +1,114 @@
---
title: OAuth Activation Flow
description: Implementation guide for secure user-verified license activation
date: 2024-01-31
---
## Overview
The Secure OAuth Activation flow ensures that licenses are only activated by their legitimate owners. Unlike simple API activation, this method requires the user to log in to the WooNooW portal and explicitly authorize the activation request.
### When to use OAuth?
* ✅ When you want strict control over license usage
* ✅ To prevent license key sharing (key + auth required)
* ✅ If specific user consent is legally required
---
## Authentication Flow
The flow involves three parties:
1. **Client Application**: The software requesting activation (e.g., a customer's WordPress site)
2. **Vendor Portal**: The WooNooW dashboard where the user manages licenses
3. **Vendor API**: The backend handling the activation logic
<Stepper>
<StepperItem title="Step 1: Client Requests Activation">
The client sends a request to the activation API with `activation_mode: "oauth"`.
```bash
POST /woonoow/v1/licenses/activate
{
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"domain": "https://client-site.com",
"activation_mode": "oauth"
}
```
</StepperItem>
<StepperItem title="Step 2: API Request Authorization">
The API responds with `oauth_required: true` and a redirect URL.
```json
{
"oauth_required": true,
"oauth_redirect": "https://woonoow.com/my-account/license-connect/...",
"state": "abc12345"
}
```
</StepperItem>
<StepperItem title="Step 3: User Authorizes Request">
The client redirects the user to the `oauth_redirect` URL. The user logs in and sees a confirmation screen:
> **Authorize this Request?**
> Site: https://client-site.com
> License: XXXX-YYYY-ZZZZ-WWWW
Once confirmed, the vendor generates a temporary **activation token**.
</StepperItem>
<StepperItem title="Step 4: Token Exchange">
The user is redirected back to the client site with the token. The client exchanges this token for the final activation.
```bash
POST /woonoow/v1/licenses/activate
{
"activation_token": "temporary-token-123"
}
```
</StepperItem>
</Stepper>
---
## Implementation Guide
### 1. Handling the Redirect
When your application receives the `oauth_redirect` response, you must open this URL in the user's browser.
<Note type="note" title="Security Check">
Always verify the `state` parameter when the user returns to your application to prevent CSRF attacks.
</Note>
### 2. Processing the Callback
Your application needs a callback route (e.g., `/admin.php?page=my-plugin&action=callback`). This URL must be provided in the initial `return_url` parameter.
The callback will receive:
* `activation_token`: The token needed to complete activation
* `license_key`: The license key being activated
* `nonce`: Random standard nonce for verification
### 3. Completing Activation
Once you have the `activation_token`, compare the `state` (if you stored it) and make the final request.
```javascript
const response = await fetch('https://api.woonoow.com/woonoow/v1/licenses/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
license_key: licenseKey,
activation_token: urlParams.get('activation_token')
})
});
const data = await response.json();
if (data.success) {
console.log('License Activated!', data);
}
```

9
contents/docs/meta.json Normal file
View File

@@ -0,0 +1,9 @@
{
"pages": [
"getting-started",
"licensing",
"hooks",
"api-reference",
"changelog"
]
}

View File

@@ -0,0 +1,114 @@
---
title: Frequently Asked Questions
description: Quick answers to common questions about WooNooW
date: 2024-01-31
---
## General
### What is WooNooW?
WooNooW is a WooCommerce plugin that transforms your store into a modern Single Page Application (SPA). It provides instant page loads, a beautiful UI, and seamless shopping experience.
### Do I need WooCommerce?
Yes. WooNooW is an enhancement layer for WooCommerce. You need WooCommerce installed and activated.
### Will WooNooW affect my existing products?
No. WooNooW reads from WooCommerce. Your products, orders, and settings remain untouched.
---
## SPA Mode
### What's the difference between Full and Disabled mode?
| Mode | Behavior |
|------|----------|
| **Full** | All WooCommerce pages redirect to SPA. Modern, fast experience. |
| **Disabled** | WooCommerce pages use native templates. WooNooW admin still works. |
### Can I switch modes anytime?
Yes. Go to **WooNooW → Appearance → General** and change the SPA Mode. Changes take effect immediately.
### Which mode should I use?
- **Full**: For the best customer experience with instant loads
- **Disabled**: If you have theme customizations you want to keep
---
## Compatibility
### Does WooNooW work with my theme?
WooNooW's SPA is independent of your WordPress theme. In Full mode, the SPA uses its own styling. Your theme affects the rest of your site normally.
### Does WooNooW work with page builders?
The SPA pages are self-contained. Page builders work on other pages of your site.
### Which payment gateways are supported?
WooNooW supports all WooCommerce-compatible payment gateways:
- PayPal
- Stripe
- Bank Transfer (BACS)
- Cash on Delivery
- And more...
---
## SEO
### Is WooNooW SEO-friendly?
Yes. WooNooW uses:
- Clean URLs (`/store/product/product-name`)
- Dynamic meta tags for social sharing
- Proper redirects (302) from WooCommerce URLs
### What about my existing SEO?
WooCommerce URLs remain the indexed source. WooNooW redirects users to the SPA but preserves SEO value.
### Will my product pages be indexed?
Yes. Search engines index the WooCommerce URLs. When users click from search results, they're redirected to the fast SPA experience.
---
## Performance
### Is WooNooW faster than regular WooCommerce?
Yes, for navigation. After the initial load, page transitions are instant because the SPA doesn't reload the entire page.
### Will WooNooW slow down my site?
The initial load is similar to regular WooCommerce. Subsequent navigation is much faster.
### Does WooNooW work with caching?
Yes. Use page caching and object caching for best results.
---
## Customization
### Can I customize colors and fonts?
Yes. Go to **WooNooW → Appearance** to customize:
- Primary, secondary, and accent colors
- Body and heading fonts
- Logo and layout options
### Can I add custom CSS?
Currently, use your theme's Additional CSS feature. A custom CSS field may be added in future versions.
### Can I modify the SPA templates?
The SPA is built with React. Advanced customizations require development knowledge.

View File

@@ -0,0 +1,175 @@
---
title: Troubleshooting
description: Common issues and their solutions
date: 2024-01-31
---
## Blank Pages
### Symptom
WooCommerce pages (shop, cart, checkout) show blank content.
### Solutions
**1. Check SPA Mode Setting**
- Go to **WooNooW → Appearance → General**
- Ensure **SPA Mode** is set to "Full"
- If you want native WooCommerce, set to "Disabled"
**2. Flush Permalinks**
- Go to **Settings → Permalinks**
- Click **Save Changes** (no changes needed)
- This refreshes rewrite rules
**3. Clear Cache**
If using a caching plugin:
- Clear page cache
- Clear object cache
- Purge CDN cache (if applicable)
---
## 404 Errors on SPA Routes
### Symptom
Visiting `/store/shop` or `/store/product/...` shows a 404 error.
### Solutions
**1. Flush Permalinks**
- Go to **Settings → Permalinks**
- Click **Save Changes**
**2. Check Store Page Exists**
- Go to **Pages**
- Verify "Store" page exists and is published
- The page should contain `[woonoow_spa]` shortcode
**3. Check SPA Page Setting**
- Go to **WooNooW → Appearance → General**
- Ensure **SPA Page** is set to the Store page
---
## Product Images Not Loading
### Symptom
Products show placeholder images instead of actual images.
### Solutions
**1. Regenerate Thumbnails**
- Install "Regenerate Thumbnails" plugin
- Run regeneration for all images
**2. Check Image URLs**
- Ensure images have valid URLs
- Check for mixed content (HTTP vs HTTPS)
---
## Slow Performance
### Symptom
SPA feels slow or laggy.
### Solutions
**1. Enable Caching**
- Install a caching plugin (WP Super Cache, W3 Total Cache)
- Enable object caching (Redis/Memcached)
**2. Optimize Images**
- Use WebP format
- Compress images before upload
- Use lazy loading
**3. Check Server Resources**
- Upgrade hosting if on shared hosting
- Consider VPS or managed WordPress hosting
---
## Checkout Not Working
### Symptom
Checkout page won't load or payment fails.
### Solutions
**1. Check Payment Gateway**
- Go to **WooCommerce → Settings → Payments**
- Verify payment method is enabled
- Check API credentials
**2. Check SSL Certificate**
- Checkout requires HTTPS
- Verify SSL is properly installed
**3. Check for JavaScript Errors**
- Open browser Developer Tools (F12)
- Check Console for errors
- Look for blocked scripts
---
## Emails Not Sending
### Symptom
Order confirmation emails not being received.
### Solutions
**1. Check Email Settings**
- Go to **WooNooW → Settings → Notifications**
- Verify email types are enabled
**2. Check WordPress Email**
- Test with a plugin like "Check & Log Email"
- Consider using SMTP plugin (WP Mail SMTP)
**3. Check Spam Folder**
- Emails may be in recipient's spam folder
- Add sender to whitelist
---
## Plugin Conflicts
### Symptom
WooNooW doesn't work after installing another plugin.
### Steps to Diagnose
1. **Deactivate other plugins** one by one
2. **Switch to default theme** (Twenty Twenty-Three)
3. **Check error logs** in `wp-content/debug.log`
### Common Conflicting Plugins
- Other WooCommerce template overrides
- Page builder plugins (sometimes)
- Heavy caching plugins (misconfigured)
---
## Getting More Help
If you can't resolve the issue:
1. **Collect Information**
- WordPress version
- WooCommerce version
- WooNooW version
- PHP version
- Error messages (from debug.log)
2. **Enable Debug Mode**
Add to `wp-config.php`:
```php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
```
3. **Contact Support**
Provide the collected information for faster resolution.

295
docu.json
View File

@@ -1,128 +1,225 @@
{ {
"navbar": { "$schema": "https://docubook.pro/docu.schema.json",
"logo": { "meta": {
"src": "/images/docu.svg", "baseURL": "https://docs.woonoow.com",
"alt": "DocuBook Logo" "title": "WooNooW Docs",
"description": "Official documentation for WooNooW Plugin - The ultimate WooCommerce enhancement suite.",
"favicon": "/favicon.ico"
},
"navbar": {
"logo": {
"src": "/images/logo.png",
"alt": "WooNooW Logo"
},
"logoText": "WooNooW Docs",
"menu": [
{
"title": "Home",
"href": "/"
}, },
"logoText": "DocuBook", {
"menu": [ "title": "Developer Docs",
{ "title": "Home", "href": "/" }, "href": "/docs"
{ "title": "Docs", "href": "/docs/getting-started/introduction" }, },
{ "title": "Community", "href": "https://docubook.pro" } {
] "title": "Plugin Site",
}, "href": "https://woonoow.com"
"footer": { }
"copyright": "DocuBook", ]
"social": [ },
{ "repository": {
"name": "Instagram", "url": "https://git.backoffice.biz.id/dwindown/WooNooW",
"url": "https://www.instagram.com/wildan.nrs", "editPathTemplate": "/blob/main/woonoow-docs/contents/{filePath}",
"iconName": "InstagramIcon" "editLink": false
}, },
{ "sponsor": {
"name": "Facebook", "title": "Powered by",
"url": "https://www.facebook.com/wildan.nrsh", "item": {
"iconName": "FacebookIcon" "title": "WooNooW",
}, "description": "Enhance your WooCommerce store.",
{ "image": "/images/logo.png",
"name": "Twitter", "url": "https://woonoow.com"
"url": "https://x.com/wildan_nrss", }
"iconName": "TwitterIcon" },
}, "footer": {
{ "copyright": "WooNooW",
"name": "Youtube", "social": []
"url": "https://www.youtube.com/@wildan.nrs_", },
"iconName": "YoutubeIcon" "routes": [
}
]
},
"meta": {
"baseURL": "https://docubook.pro",
"title": "DocuBook",
"description": "DocuBook is a modern documentation platform for building, deploying, and managing your docs with ease.",
"favicon": "/favicon.ico"
},
"repository": {
"url": "https://github.com/DocuBook/docubook",
"editPathTemplate": "/blob/main/{filePath}",
"editLink": true
},
"sponsor": {
"title": "Hosted on",
"item": {
"title": "Vercel",
"description": "Deploy your DocuBook app with zero configuration.",
"image": "/images/vercel.png",
"url": "https://vercel.com/import/project?template=https://github.com/DocuBook/docubook"
}
},
"routes": [
{ {
"title": "Getting Started", "title": "Getting Started",
"href": "/getting-started", "href": "/getting-started",
"noLink": true, "noLink": true,
"context": { "context": {
"icon": "Book", "icon": "Book",
"description": "Set up your Documentation", "description": "Guides and References",
"title": "Guides" "title": "Docs"
}, },
"items": [ "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": "Theme Colors", "title": "Introduction",
"href": "/theme-colors", "href": "/introduction"
"items": [ },
{ "title": "Default", "href": "/default" }, {
{ "title": "Fresh Lime", "href": "/freshlime" }, "title": "Installation",
{ "title": "Coffee", "href": "/coffee" }, "href": "/installation"
{ "title": "llms.txt", "href": "/llms" }
]
} }
] ]
}, },
{ {
"title": "Components", "title": "Configuration",
"href": "/components", "href": "/configuration",
"noLink": true, "noLink": true,
"context": { "context": {
"icon": "Layers", "icon": "Settings",
"description": "Write with Markdown", "description": "Setup & Options",
"title": "Markdown" "title": "Config"
}, },
"items": [ "items": [
{ "title": "Accordion", "href": "/accordion" }, {
{ "title": "Button", "href": "/button" }, "title": "SPA Mode",
{ "title": "Card", "href": "/card" }, "href": "/spa-mode"
{ "title": "Card Group", "href": "/card-group" }, },
{ "title": "Code Block", "href": "/code-block" }, {
{ "title": "File Tree", "href": "/file-tree" }, "title": "Appearance",
{ "title": "Image", "href": "/image" }, "href": "/appearance"
{ "title": "Keyboard", "href": "/keyboard" }, }
{ "title": "Link", "href": "/link" },
{ "title": "Note", "href": "/note" },
{ "title": "Release Note", "href": "/release-note" },
{ "title": "Stepper", "href": "/stepper" },
{ "title": "Tabs", "href": "/tabs" },
{ "title": "Tooltips", "href": "/tooltips" },
{ "title": "Youtube", "href": "/youtube" },
{ "title": "Custom", "href": "/custom" }
] ]
}, },
{ {
"title": "Release", "title": "Core Features",
"href": "/changelog", "href": "/features",
"noLink": true, "noLink": true,
"context": { "context": {
"icon": "History", "icon": "Layout",
"description": "Updates and changes", "description": "Store Functionality",
"title": "Changelog" "title": "Features"
}, },
"items": [ "items": [
{ "title": "Version 1.0+", "href": "/version-1" } {
"title": "Shop Page",
"href": "/shop"
},
{
"title": "Checkout",
"href": "/checkout"
},
{
"title": "Shortcodes",
"href": "/shortcodes"
}
]
},
{
"title": "Licensing & OAuth",
"href": "/licensing",
"noLink": true,
"context": {
"icon": "Key",
"description": "License Management",
"title": "Licensing"
},
"items": [
{
"title": "OAuth Flow",
"href": "/oauth-flow"
}
]
},
{
"title": "API Reference",
"href": "/api-reference",
"noLink": true,
"context": {
"icon": "Terminal",
"description": "API Endpoints",
"title": "API"
},
"items": [
{
"title": "Licensing API",
"href": "/licensing"
}
]
},
{
"title": "Developer Guides",
"href": "/developer",
"noLink": true,
"context": {
"icon": "Code",
"description": "Advanced Integration",
"title": "Dev"
},
"items": [
{
"title": "Addons: Bridge Pattern",
"href": "/addons/bridge-pattern"
},
{
"title": "Addons: React Integration",
"href": "/addons/react-integration"
},
{
"title": "Addons: Module Registry",
"href": "/addons/module-integration"
}
]
},
{
"title": "Hooks & Filters",
"href": "/hooks",
"noLink": true,
"context": {
"icon": "Anchor",
"description": "Actions and Filters",
"title": "Hooks"
},
"items": [
{
"title": "Overview",
"href": "/"
},
{
"title": "Notifications",
"href": "/notifications"
},
{
"title": "Subscriptions",
"href": "/subscriptions"
},
{
"title": "Frontend",
"href": "/frontend"
},
{
"title": "Newsletter",
"href": "/newsletter"
}
]
},
{
"title": "Resources",
"href": "/resources",
"noLink": true,
"context": {
"icon": "Box",
"description": "Tools and Assets",
"title": "Resources"
},
"items": [
{
"title": "FAQ",
"href": "/faq"
},
{
"title": "Troubleshooting",
"href": "/troubleshooting"
},
{
"title": "Changelog",
"href": "/changelog"
}
] ]
} }
] ]

35
eslint.config.mjs Normal file
View File

@@ -0,0 +1,35 @@
import { defineConfig } from "eslint/config";
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import nextTypescript from "eslint-config-next/typescript";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([{
extends: [
...nextCoreWebVitals,
...nextTypescript,
...compat.extends("plugin:@typescript-eslint/recommended")
],
rules: {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["warn", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
}],
"@typescript-eslint/no-empty-object-type": "off",
},
}]);

View File

@@ -16,6 +16,7 @@ export function useScrollPosition(threshold = 0.5) {
// Add scroll event listener // Add scroll event listener
useEffect(() => { useEffect(() => {
// Initial check // Initial check
// eslint-disable-next-line react-hooks/set-state-in-effect
handleScroll(); handleScroll();
window.addEventListener('scroll', handleScroll, { passive: true }); window.addEventListener('scroll', handleScroll, { passive: true });

View File

@@ -8,7 +8,7 @@ import rehypeSlug from "rehype-slug";
import rehypeCodeTitles from "rehype-code-titles"; import rehypeCodeTitles from "rehype-code-titles";
import { page_routes, ROUTES } from "./routes-config"; import { page_routes, ROUTES } from "./routes-config";
import { visit } from "unist-util-visit"; import { visit } from "unist-util-visit";
import type { Node } from "unist"; import type { Node, Parent } from "unist";
import matter from "gray-matter"; import matter from "gray-matter";
// Type definitions for unist-util-visit // Type definitions for unist-util-visit
@@ -16,6 +16,7 @@ interface Element extends Node {
type: string; type: string;
tagName?: string; tagName?: string;
properties?: Record<string, unknown> & { properties?: Record<string, unknown> & {
className?: string[];
raw?: string; raw?: string;
}; };
children?: Node[]; children?: Node[];
@@ -45,6 +46,7 @@ import CardGroup from "@/components/markdown/CardGroupMdx";
import Kbd from "@/components/markdown/KeyboardMdx"; import Kbd from "@/components/markdown/KeyboardMdx";
import { Release, Changes } from "@/components/markdown/ReleaseMdx"; import { Release, Changes } from "@/components/markdown/ReleaseMdx";
import { File, Files, Folder } from "@/components/markdown/FileTreeMdx"; import { File, Files, Folder } from "@/components/markdown/FileTreeMdx";
import AccordionGroup from "@/components/markdown/AccordionGroupMdx";
// add custom components // add custom components
const components = { const components = {
@@ -73,6 +75,50 @@ const components = {
File, File,
Files, Files,
Folder, Folder,
AccordionGroup
};
// helper function to handle rehype code titles, since by default we can't inject into the className of rehype-code-titles
const handleCodeTitles = () => (tree: Node) => {
visit(tree, "element", (node: Element, index: number | null, parent: Parent | null) => {
// Ensure the visited node is valid
if (!parent || index === null || node.tagName !== 'div') {
return;
}
// Check if this is the title div from rehype-code-titles
const isTitleDiv = node.properties?.className?.includes('rehype-code-title');
if (!isTitleDiv) {
return;
}
// Find the next <pre> element, skipping over other nodes like whitespace text
let nextElement = null;
for (let i = index + 1; i < parent.children.length; i++) {
const sibling = parent.children[i];
if (sibling.type === 'element') {
nextElement = sibling as Element;
break;
}
}
// If the next element is a <pre>, move the title to it
if (nextElement && nextElement.tagName === 'pre') {
const titleNode = node.children?.[0] as TextNode;
if (titleNode && titleNode.type === 'text') {
if (!nextElement.properties) {
nextElement.properties = {};
}
nextElement.properties['data-title'] = titleNode.value;
// Remove the original title div
parent.children.splice(index, 1);
// Return the same index to continue visiting from the correct position
return index;
}
}
});
}; };
// can be used for other pages like blogs, Guides etc // can be used for other pages like blogs, Guides etc
@@ -85,6 +131,7 @@ async function parseMdx<Frontmatter>(rawMdx: string) {
rehypePlugins: [ rehypePlugins: [
preProcess, preProcess,
rehypeCodeTitles, rehypeCodeTitles,
handleCodeTitles,
rehypePrism, rehypePrism,
rehypeSlug, rehypeSlug,
rehypeAutolinkHeadings, rehypeAutolinkHeadings,

View File

@@ -1,62 +1,67 @@
{ {
"name": "docubook", "name": "docubook",
"version": "1.15.1", "version": "2.0.0-beta.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "eslint ."
}, },
"dependencies": { "dependencies": {
"@docsearch/css": "3", "@docsearch/css": "^3.9.0",
"@docsearch/react": "^3.9.0", "@docsearch/react": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-toggle-group": "^1.1.11",
"algoliasearch": "^5.35.0", "algoliasearch": "^5.46.3",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "1.0.0", "cmdk": "^1.1.1",
"framer-motion": "^12.4.1", "framer-motion": "^12.26.2",
"geist": "^1.3.1", "geist": "^1.5.1",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"install": "^0.13.0",
"lucide-react": "^0.511.0", "lucide-react": "^0.511.0",
"next": "^14.2.6", "next": "16.1.3",
"next-mdx-remote": "^5.0.0", "next-mdx-remote": "^5.0.0",
"next-themes": "^0.3.0", "next-themes": "^0.4.4",
"react": "^18.3.1", "react": "19.2.3",
"react-dom": "^18.3.1", "react-dom": "19.2.3",
"react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0", "rehype-autolink-headings": "^7.1.0",
"rehype-code-titles": "^1.2.0", "rehype-code-titles": "^1.2.1",
"rehype-prism-plus": "^2.0.0", "rehype-prism-plus": "^2.0.1",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.1",
"sonner": "^1.4.3", "sonner": "^1.7.4",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"unist-util-visit": "^5.0.0" "unist-util-visit": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.14", "@tailwindcss/postcss": "^4.1.18",
"@types/node": "^20", "@tailwindcss/typography": "^0.5.19",
"@types/react": "^18", "@types/node": "^20.19.30",
"@types/react-dom": "^18", "@types/react": "19.2.8",
"autoprefixer": "^10.4.20", "@types/react-dom": "19.2.3",
"eslint": "^8", "autoprefixer": "^10.4.23",
"eslint-config-next": "^14.2.6", "eslint": "^9.39.2",
"postcss": "^8", "eslint-config-next": "16.1.3",
"tailwindcss": "^3.4.10", "postcss": "^8.5.6",
"typescript": "^5" "tailwindcss": "^4.1.18",
"typescript": "^5.9.3"
},
"overrides": {
"@types/react": "19.2.8",
"@types/react-dom": "19.2.3"
} }
} }

View File

@@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, '@tailwindcss/postcss': {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1,71 +1,191 @@
@import "@docsearch/css"; @import 'tailwindcss';
@import "./algolia.css"; @plugin '@tailwindcss/typography';
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("./syntax.css"); @custom-variant dark (&:is(.dark *));
/* Modern Blue Theme */
@layer base {
:root {
--background: 210 40% 98%;
--foreground: 220 30% 15%;
--card: 0 0% 100%;
--card-foreground: 220 30% 15%;
--popover: 0 0% 100%;
--popover-foreground: 220 30% 15%;
--primary: 210 81% 56%; /* #2281E3 */
--primary-foreground: 0 0% 100%;
--secondary: 210 30% 90%;
--secondary-foreground: 220 30% 15%;
--muted: 210 20% 92%;
--muted-foreground: 220 15% 50%;
--accent: 200 100% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 100%;
--border: 210 20% 85%;
--input: 210 20% 85%;
--ring: 210 81% 56%;
--radius: 0.5rem;
--chart-1: 210 81% 56%;
--chart-2: 200 100% 40%;
--chart-3: 220 76% 60%;
--chart-4: 190 90% 50%;
--chart-5: 230 86% 45%;
--line-number-color: rgba(0, 0, 0, 0.05);
}
.dark { @utility container {
--background: 220 25% 10%; margin-inline: auto;
--foreground: 210 30% 96%; padding-inline: 2rem;
--card: 220 25% 15%;
--card-foreground: 210 30% 96%; @media (width >=--theme(--breakpoint-sm)) {
--popover: 220 25% 15%; max-width: none;
--popover-foreground: 210 30% 96%; }
--primary: 210 100% 65%;
--primary-foreground: 220 25% 10%; @media (width >=1440px) {
--secondary: 215 25% 20%; max-width: 1440px;
--secondary-foreground: 210 30% 96%; }
--muted: 215 20% 25%;
--muted-foreground: 215 20% 65%;
--accent: 200 100% 60%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 70%;
--destructive-foreground: 0 0% 100%;
--border: 215 20% 25%;
--input: 215 20% 25%;
--ring: 210 100% 65%;
--chart-1: 210 100% 65%;
--chart-2: 200 100% 60%;
--chart-3: 220 90% 70%;
--chart-4: 190 100% 65%;
--chart-5: 230 90% 60%;
--line-number-color: rgba(255, 255, 255, 0.1);
}
} }
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-sidebar: hsl(var(--sidebar-background));
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
--color-sidebar-primary: hsl(var(--sidebar-primary));
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
--color-sidebar-accent: hsl(var(--sidebar-accent));
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
--color-sidebar-border: hsl(var(--sidebar-border));
--color-sidebar-ring: hsl(var(--sidebar-ring));
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
--font-code: var(--font-geist-mono);
--font-regular: var(--font-geist-sans);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-shiny-text: shiny-text 8s infinite;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes shiny-text {
0%,
90%,
100% {
background-position: calc(-100% - var(--shiny-width)) 0;
}
30%,
60% {
background-position: calc(100% + var(--shiny-width)) 0;
}
}
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility animate-shine {
--animate-shine: shine var(--duration) infinite linear;
animation: var(--animate-shine);
background-size: 200% 200%;
}
/* Fresh Lime Theme */
@layer base {
:root {
--background: 85 45% 98%;
--foreground: 85 30% 10%;
--card: 0 0% 100%;
--card-foreground: 85 30% 10%;
--popover: 0 0% 100%;
--popover-foreground: 85 30% 10%;
--primary: 85 70% 45%;
--primary-foreground: 0 0% 100%;
--secondary: 85 40% 90%;
--secondary-foreground: 85 30% 10%;
--muted: 85 30% 92%;
--muted-foreground: 85 15% 45%;
--accent: 85 60% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 100%;
--border: 85 25% 88%;
--input: 85 25% 88%;
--ring: 85 70% 45%;
--radius: 0.5rem;
--chart-1: 85 70% 45%;
--chart-2: 85 60% 40%;
--chart-3: 85 80% 40%;
--chart-4: 85 85% 35%;
--chart-5: 85 90% 30%;
--line-number-color: rgba(0, 0, 0, 0.05);
}
.dark {
--background: 85 20% 8%;
--foreground: 85 30% 96%;
--card: 85 20% 10%;
--card-foreground: 85 30% 96%;
--popover: 85 20% 10%;
--popover-foreground: 85 30% 96%;
--primary: 85 75% 55%;
--primary-foreground: 85 20% 8%;
--secondary: 85 25% 18%;
--secondary-foreground: 85 30% 96%;
--muted: 85 20% 20%;
--muted-foreground: 85 20% 70%;
--accent: 85 70% 50%;
--accent-foreground: 0 0% 100%;
--destructive: 0 85% 65%;
--destructive-foreground: 0 0% 100%;
--border: 85 25% 22%;
--input: 85 25% 22%;
--ring: 85 75% 55%;
--chart-1: 85 75% 55%;
--chart-2: 85 70% 50%;
--chart-3: 85 80% 45%;
--chart-4: 85 85% 40%;
--chart-5: 85 90% 35%;
--line-number-color: rgba(255, 255, 255, 0.1);
}
}
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
@@ -76,66 +196,66 @@
} }
} }
.prose { @layer utilities {
margin: 0 !important; .prose {
} margin: 0 !important;
}
pre { pre {
padding: 2px 0 !important; padding: 2px 0 !important;
width: inherit !important; width: inherit !important;
overflow-x: auto; overflow-x: auto;
} }
pre>code { pre>code {
display: grid; display: grid;
max-width: inherit !important; max-width: inherit !important;
padding: 14px 0 !important; padding: 14px 0 !important;
} border: 0 !important;
}
.code-line { .code-line {
padding: 0.75px 16px; padding: 0.75px 16px;
@apply border-l-2 border-transparent @apply border-l-2 border-transparent;
} }
.line-number::before { .line-number::before {
display: inline-block; display: inline-block;
width: 1rem; width: 1rem;
margin-right: 22px; margin-right: 22px;
margin-left: -2px; margin-left: -2px;
color: rgb(110, 110, 110); color: rgb(110, 110, 110);
content: attr(line); content: attr(line);
font-size: 13.5px; font-size: 13.5px;
text-align: right; text-align: right;
} }
.highlight-line { .highlight-line {
@apply bg-primary/5 border-l-2 border-primary/30; @apply bg-primary/5 border-l-2 border-primary/30;
} }
.rehype-code-title { .rehype-code-title {
@apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code; @apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code;
} }
.highlight-comp>code { .highlight-comp>code {
background-color: transparent !important; background-color: transparent !important;
}
} }
@layer utilities { @layer utilities {
.animate-shine {
--animate-shine: shine var(--duration) infinite linear; @keyframes shine {
animation: var(--animate-shine); 0% {
background-size: 200% 200%; background-position: 0% 0%;
} }
@keyframes shine { 50% {
0% { background-position: 100% 100%;
background-position: 0% 0%; }
}
50% { 100% {
background-position: 100% 100%; background-position: 0% 0%;
}
100% {
background-position: 0% 0%;
}
} }
} }
}

View File

@@ -93,3 +93,87 @@
border: none; border: none;
border-radius: 8px; /* Sudut melengkung pada iframe */ border-radius: 8px; /* Sudut melengkung pada iframe */
} }
/* ======================================================================== */
/* Custom styling for code blocks */
/* ======================================================================== */
.code-block-container {
position: relative;
margin: 1.5rem 0;
border: 1px solid hsl(var(--border));
overflow: hidden;
font-size: 0.875rem;
border-radius: 0.75rem;
}
.code-block-header {
display: flex;
align-items: center;
gap: 0.5rem;
background-color: hsl(var(--muted));
padding: 0.5rem 1rem;
border-bottom: 1px solid hsl(var(--border));
color: hsl(var(--muted-foreground));
font-family: monospace;
font-size: 0.8rem;
}
.code-block-actions {
position: absolute;
top: 0.5rem;
right: 0.75rem;
z-index: 10;
}
.code-block-actions button {
color: hsl(var(--muted-foreground));
transition: color 0.2s ease-in-out;
}
.code-block-actions button:hover {
color: hsl(var(--foreground));
}
.code-block-body pre[class*="language-"] {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
}
.line-numbers-wrapper {
position: absolute;
top: 0;
left: 0;
width: 3rem;
padding-top: 1rem;
text-align: right;
color: var(--line-number-color);
user-select: none;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
background: hsl(var(--primary) / 0.1);
border-left: 2px solid hsl(var(--primary));
pointer-events: none;
}
.code-block-body pre[data-line-numbers="true"] .line-highlight {
padding-left: 3.5rem;
}
.code-block-body::-webkit-scrollbar {
height: 8px;
}
.code-block-body::-webkit-scrollbar-track {
background: transparent;
}
.code-block-body::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 4px;
}
.code-block-body::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted));
}

View File

@@ -1,111 +1,113 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import tailwindAnimate from "tailwindcss-animate";
import typography from "@tailwindcss/typography";
const config = { const config = {
darkMode: ["class"], darkMode: "class",
content: [ content: [
"./pages/**/*.{ts,tsx}", "./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}",
], ],
prefix: "", prefix: "",
theme: { theme: {
container: { container: {
center: true, center: true,
padding: '2rem', padding: '2rem',
screens: { screens: {
'2xl': '1440px' '2xl': '1440px'
} }
}, },
extend: { extend: {
colors: { colors: {
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
input: 'hsl(var(--input))', input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))', ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))', foreground: 'hsl(var(--foreground))',
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))'
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))' foreground: 'hsl(var(--secondary-foreground))'
}, },
destructive: { destructive: {
DEFAULT: 'hsl(var(--destructive))', DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))' foreground: 'hsl(var(--destructive-foreground))'
}, },
muted: { muted: {
DEFAULT: 'hsl(var(--muted))', DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))' foreground: 'hsl(var(--muted-foreground))'
}, },
accent: { accent: {
DEFAULT: 'hsl(var(--accent))', DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))' foreground: 'hsl(var(--accent-foreground))'
}, },
popover: { popover: {
DEFAULT: 'hsl(var(--popover))', DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))' foreground: 'hsl(var(--popover-foreground))'
}, },
card: { card: {
DEFAULT: 'hsl(var(--card))', DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))' foreground: 'hsl(var(--card-foreground))'
}, },
sidebar: { sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))', DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))', foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))', primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))', accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))', border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))' ring: 'hsl(var(--sidebar-ring))'
} }
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)', md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)' sm: 'calc(var(--radius) - 4px)'
}, },
fontFamily: { fontFamily: {
code: ["var(--font-geist-mono)"], code: ["var(--font-geist-mono)"],
regular: ["var(--font-geist-sans)"] regular: ["var(--font-geist-sans)"]
}, },
keyframes: { keyframes: {
'accordion-down': { 'accordion-down': {
from: { from: {
height: '0' height: '0'
}, },
to: { to: {
height: 'var(--radix-accordion-content-height)' height: 'var(--radix-accordion-content-height)'
} }
}, },
'accordion-up': { 'accordion-up': {
from: { from: {
height: 'var(--radix-accordion-content-height)' height: 'var(--radix-accordion-content-height)'
}, },
to: { to: {
height: '0' height: '0'
} }
}, },
'shiny-text': { 'shiny-text': {
'0%, 90%, 100%': { '0%, 90%, 100%': {
'background-position': 'calc(-100% - var(--shiny-width)) 0' 'background-position': 'calc(-100% - var(--shiny-width)) 0'
}, },
'30%, 60%': { '30%, 60%': {
'background-position': 'calc(100% + var(--shiny-width)) 0' 'background-position': 'calc(100% + var(--shiny-width)) 0'
} }
} }
}, },
animation: { animation: {
'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out',
'shiny-text': 'shiny-text 8s infinite' 'shiny-text': 'shiny-text 8s infinite'
} }
} }
}, },
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], plugins: [tailwindAnimate, typography],
} satisfies Config; } satisfies Config;
export default config; export default config;

View File

@@ -1,6 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@@ -10,7 +14,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
@@ -18,9 +22,20 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": [
} "./*"
]
},
"target": "ES2017"
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }