@docubook/create 1.15.3 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/create",
3
- "version": "1.15.3",
3
+ "version": "1.16.1",
4
4
  "description": "CLI to create DocuBook projects",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -25,7 +25,7 @@ export default function Home() {
25
25
  )}
26
26
  >
27
27
  <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">
28
- <span>🚀 New Version - Release v1.15.2</span>
28
+ <span>🚀 New Version - Release v1.16.1</span>
29
29
  <ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
30
30
  </AnimatedShinyText>
31
31
  </div>
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+
3
+ // Create a context to check if a component is inside an accordion group
4
+ export const AccordionGroupContext = createContext<{ inGroup: boolean } | null>(null);
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import React, { ReactNode } from "react";
4
+ import clsx from "clsx";
5
+ import { AccordionGroupContext } from "@/components/contexts/AccordionContext";
6
+
7
+ interface AccordionGroupProps {
8
+ children: ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ const AccordionGroup: React.FC<AccordionGroupProps> = ({ children, className }) => {
13
+
14
+ return (
15
+ // Wrap all children with the AccordionGroupContext.Provider
16
+ // so that any nested accordions know they are inside a group.
17
+ // This enables group-specific behavior in child components.
18
+ <AccordionGroupContext.Provider value={{ inGroup: true }}>
19
+ <div
20
+ className={clsx(
21
+ "border rounded-lg overflow-hidden",
22
+ className
23
+ )}
24
+ >
25
+ {children}
26
+ </div>
27
+ </AccordionGroupContext.Provider>
28
+ );
29
+ };
30
+
31
+ export default AccordionGroup;
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { ReactNode, useState } from 'react';
3
+ import { ReactNode, useState, useContext } from 'react';
4
4
  import { ChevronRight } from 'lucide-react';
5
5
  import * as Icons from "lucide-react";
6
6
  import { cn } from '@/lib/utils';
7
+ import { AccordionGroupContext } from '@/components/contexts/AccordionContext';
7
8
 
8
9
  type AccordionProps = {
9
10
  title: string;
@@ -18,16 +19,26 @@ const Accordion: React.FC<AccordionProps> = ({
18
19
  defaultOpen = false,
19
20
  icon,
20
21
  }: AccordionProps) => {
22
+ const groupContext = useContext(AccordionGroupContext);
23
+ const isInGroup = groupContext?.inGroup === true;
21
24
  const [isOpen, setIsOpen] = useState(defaultOpen);
22
-
23
25
  const Icon = icon ? (Icons[icon] as React.FC<{ className?: string }>) : null;
24
26
 
27
+ // The main wrapper div for the accordion.
28
+ // All styling logic for the accordion container is handled here.
25
29
  return (
26
- <div className={cn("border rounded-lg overflow-hidden")}>
30
+ <div
31
+ className={cn(
32
+ // Style for STANDALONE: full card with border & shadow
33
+ !isInGroup && "border rounded-lg shadow-sm",
34
+ // Style for IN GROUP: only a bottom border separator
35
+ isInGroup && "border-b last:border-b-0 border-border"
36
+ )}
37
+ >
27
38
  <button
28
39
  type="button"
29
40
  onClick={() => setIsOpen(!isOpen)}
30
- className="flex items-center space-x-2 w-full px-4 h-12 transition-colors bg-background dark:hover:bg-muted/50 hover:bg-muted/15"
41
+ className="flex items-center space-x-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"
31
42
  >
32
43
  <ChevronRight
33
44
  className={cn(
@@ -40,7 +51,7 @@ const Accordion: React.FC<AccordionProps> = ({
40
51
  </button>
41
52
 
42
53
  {isOpen && (
43
- <div className="px-4 py-3 border-t dark:bg-muted/50 bg-muted/15">
54
+ <div className="px-4 py-3 dark:bg-muted/10 bg-muted/15">
44
55
  {children}
45
56
  </div>
46
57
  )}
@@ -48,4 +59,4 @@ const Accordion: React.FC<AccordionProps> = ({
48
59
  );
49
60
  };
50
61
 
51
- export default Accordion;
62
+ export default Accordion;
@@ -1,19 +1,109 @@
1
- import { ComponentProps } from "react";
1
+ import { type ComponentProps } from "react";
2
2
  import Copy from "./CopyMdx";
3
+ import {
4
+ SiJavascript,
5
+ SiTypescript,
6
+ SiReact,
7
+ SiPython,
8
+ SiGo,
9
+ SiPhp,
10
+ SiRuby,
11
+ SiSwift,
12
+ SiKotlin,
13
+ SiHtml5,
14
+ SiCss3,
15
+ SiSass,
16
+ SiPostgresql,
17
+ SiGraphql,
18
+ SiYaml,
19
+ SiToml,
20
+ SiDocker,
21
+ SiNginx,
22
+ SiGit,
23
+ SiGnubash,
24
+ SiMarkdown,
25
+ } from "react-icons/si";
26
+ import { FaJava, FaCode } from "react-icons/fa";
27
+ import { TbJson } from "react-icons/tb";
28
+
29
+ type PreProps = ComponentProps<"pre"> & {
30
+ raw?: string;
31
+ "data-title"?: string;
32
+ };
33
+
34
+ // Component to display an icon based on the programming language
35
+ const LanguageIcon = ({ lang }: { lang: string }) => {
36
+ const iconProps = { className: "w-4 h-4" };
37
+ const languageToIconMap: Record<string, JSX.Element> = {
38
+ gitignore: <SiGit {...iconProps} />,
39
+ docker: <SiDocker {...iconProps} />,
40
+ dockerfile: <SiDocker {...iconProps} />,
41
+ nginx: <SiNginx {...iconProps} />,
42
+ sql: <SiPostgresql {...iconProps} />,
43
+ graphql: <SiGraphql {...iconProps} />,
44
+ yaml: <SiYaml {...iconProps} />,
45
+ yml: <SiYaml {...iconProps} />,
46
+ toml: <SiToml {...iconProps} />,
47
+ json: <TbJson {...iconProps} />,
48
+ md: <SiMarkdown {...iconProps} />,
49
+ markdown: <SiMarkdown {...iconProps} />,
50
+ bash: <SiGnubash {...iconProps} />,
51
+ sh: <SiGnubash {...iconProps} />,
52
+ shell: <SiGnubash {...iconProps} />,
53
+ swift: <SiSwift {...iconProps} />,
54
+ kotlin: <SiKotlin {...iconProps} />,
55
+ kt: <SiKotlin {...iconProps} />,
56
+ kts: <SiKotlin {...iconProps} />,
57
+ rb: <SiRuby {...iconProps} />,
58
+ ruby: <SiRuby {...iconProps} />,
59
+ php: <SiPhp {...iconProps} />,
60
+ go: <SiGo {...iconProps} />,
61
+ py: <SiPython {...iconProps} />,
62
+ python: <SiPython {...iconProps} />,
63
+ java: <FaJava {...iconProps} />,
64
+ tsx: <SiReact {...iconProps} />,
65
+ typescript: <SiTypescript {...iconProps} />,
66
+ ts: <SiTypescript {...iconProps} />,
67
+ jsx: <SiReact {...iconProps} />,
68
+ js: <SiJavascript {...iconProps} />,
69
+ javascript: <SiJavascript {...iconProps} />,
70
+ html: <SiHtml5 {...iconProps} />,
71
+ css: <SiCss3 {...iconProps} />,
72
+ scss: <SiSass {...iconProps} />,
73
+ sass: <SiSass {...iconProps} />,
74
+ };
75
+ return languageToIconMap[lang] || <FaCode {...iconProps} />;
76
+ };
77
+
78
+ // Function to extract the language from className
79
+ function getLanguage(className: string = ""): string {
80
+ const match = className.match(/language-(\w+)/);
81
+ return match ? match[1] : "default";
82
+ }
83
+
84
+ export default function Pre({ children, raw, ...rest }: PreProps) {
85
+ const { "data-title": title, className, ...restProps } = rest;
86
+ const language = getLanguage(className);
87
+ const hasTitle = !!title;
3
88
 
4
- export default function Pre({
5
- children,
6
- raw,
7
- ...rest
8
- }: ComponentProps<"pre"> & { raw?: string }) {
9
89
  return (
10
- <div className="my-5 relative">
11
- <div className="absolute top-3 right-2.5 z-10 sm:block hidden">
12
- <Copy content={raw!} />
90
+ <div className="code-block-container">
91
+ <div className="code-block-actions">
92
+ {raw && <Copy content={raw} />}
13
93
  </div>
14
- <div className="relative">
15
- <pre {...rest}>{children}</pre>
94
+ {hasTitle && (
95
+ <div className="code-block-header">
96
+ <div className="flex items-center gap-2">
97
+ <LanguageIcon lang={language} />
98
+ <span>{title}</span>
99
+ </div>
100
+ </div>
101
+ )}
102
+ <div className="code-block-body">
103
+ <pre className={className} {...restProps}>
104
+ {children}
105
+ </pre>
16
106
  </div>
17
107
  </div>
18
108
  );
19
- }
109
+ }
@@ -8,7 +8,7 @@ import rehypeSlug from "rehype-slug";
8
8
  import rehypeCodeTitles from "rehype-code-titles";
9
9
  import { page_routes, ROUTES } from "./routes-config";
10
10
  import { visit } from "unist-util-visit";
11
- import type { Node } from "unist";
11
+ import type { Node, Parent } from "unist";
12
12
  import matter from "gray-matter";
13
13
 
14
14
  // Type definitions for unist-util-visit
@@ -16,6 +16,7 @@ interface Element extends Node {
16
16
  type: string;
17
17
  tagName?: string;
18
18
  properties?: Record<string, unknown> & {
19
+ className?: string[];
19
20
  raw?: string;
20
21
  };
21
22
  children?: Node[];
@@ -45,6 +46,7 @@ import CardGroup from "@/components/markdown/CardGroupMdx";
45
46
  import Kbd from "@/components/markdown/KeyboardMdx";
46
47
  import { Release, Changes } from "@/components/markdown/ReleaseMdx";
47
48
  import { File, Files, Folder } from "@/components/markdown/FileTreeMdx";
49
+ import AccordionGroup from "@/components/markdown/AccordionGroupMdx";
48
50
 
49
51
  // add custom components
50
52
  const components = {
@@ -73,6 +75,50 @@ const components = {
73
75
  File,
74
76
  Files,
75
77
  Folder,
78
+ AccordionGroup
79
+ };
80
+
81
+ // helper function to handle rehype code titles, since by default we can't inject into the className of rehype-code-titles
82
+ const handleCodeTitles = () => (tree: Node) => {
83
+ visit(tree, "element", (node: Element, index: number | null, parent: Parent | null) => {
84
+ // Ensure the visited node is valid
85
+ if (!parent || index === null || node.tagName !== 'div') {
86
+ return;
87
+ }
88
+
89
+ // Check if this is the title div from rehype-code-titles
90
+ const isTitleDiv = node.properties?.className?.includes('rehype-code-title');
91
+ if (!isTitleDiv) {
92
+ return;
93
+ }
94
+
95
+ // Find the next <pre> element, skipping over other nodes like whitespace text
96
+ let nextElement = null;
97
+ for (let i = index + 1; i < parent.children.length; i++) {
98
+ const sibling = parent.children[i];
99
+ if (sibling.type === 'element') {
100
+ nextElement = sibling as Element;
101
+ break;
102
+ }
103
+ }
104
+
105
+ // If the next element is a <pre>, move the title to it
106
+ if (nextElement && nextElement.tagName === 'pre') {
107
+ const titleNode = node.children?.[0] as TextNode;
108
+ if (titleNode && titleNode.type === 'text') {
109
+ if (!nextElement.properties) {
110
+ nextElement.properties = {};
111
+ }
112
+ nextElement.properties['data-title'] = titleNode.value;
113
+
114
+ // Remove the original title div
115
+ parent.children.splice(index, 1);
116
+
117
+ // Return the same index to continue visiting from the correct position
118
+ return index;
119
+ }
120
+ }
121
+ });
76
122
  };
77
123
 
78
124
  // can be used for other pages like blogs, Guides etc
@@ -85,6 +131,7 @@ async function parseMdx<Frontmatter>(rawMdx: string) {
85
131
  rehypePlugins: [
86
132
  preProcess,
87
133
  rehypeCodeTitles,
134
+ handleCodeTitles,
88
135
  rehypePrism,
89
136
  rehypeSlug,
90
137
  rehypeAutolinkHeadings,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docubook",
3
- "version": "1.15.3",
3
+ "version": "1.16.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -30,13 +30,13 @@
30
30
  "framer-motion": "^12.4.1",
31
31
  "geist": "^1.3.1",
32
32
  "gray-matter": "^4.0.3",
33
- "install": "^0.13.0",
34
33
  "lucide-react": "^0.511.0",
35
34
  "next": "^14.2.6",
36
35
  "next-mdx-remote": "^5.0.0",
37
36
  "next-themes": "^0.3.0",
38
37
  "react": "^18.3.1",
39
38
  "react-dom": "^18.3.1",
39
+ "react-icons": "^5.5.0",
40
40
  "rehype-autolink-headings": "^7.1.0",
41
41
  "rehype-code-titles": "^1.2.0",
42
42
  "rehype-prism-plus": "^2.0.0",
@@ -90,6 +90,7 @@ pre>code {
90
90
  display: grid;
91
91
  max-width: inherit !important;
92
92
  padding: 14px 0 !important;
93
+ border: 0 !important;
93
94
  }
94
95
 
95
96
  .code-line {
@@ -138,4 +139,4 @@ pre>code {
138
139
  background-position: 0% 0%;
139
140
  }
140
141
  }
141
- }
142
+ }
@@ -93,3 +93,87 @@
93
93
  border: none;
94
94
  border-radius: 8px; /* Sudut melengkung pada iframe */
95
95
  }
96
+
97
+ /* ======================================================================== */
98
+ /* Custom styling for code blocks */
99
+ /* ======================================================================== */
100
+
101
+ .code-block-container {
102
+ position: relative;
103
+ margin: 1.5rem 0;
104
+ border: 1px solid hsl(var(--border));
105
+ overflow: hidden;
106
+ font-size: 0.875rem;
107
+ border-radius: 0.75rem;
108
+ }
109
+
110
+ .code-block-header {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.5rem;
114
+ background-color: hsl(var(--muted));
115
+ padding: 0.5rem 1rem;
116
+ border-bottom: 1px solid hsl(var(--border));
117
+ color: hsl(var(--muted-foreground));
118
+ font-family: monospace;
119
+ font-size: 0.8rem;
120
+ }
121
+
122
+ .code-block-actions {
123
+ position: absolute;
124
+ top: 0.5rem;
125
+ right: 0.75rem;
126
+ z-index: 10;
127
+ }
128
+ .code-block-actions button {
129
+ color: hsl(var(--muted-foreground));
130
+ transition: color 0.2s ease-in-out;
131
+ }
132
+ .code-block-actions button:hover {
133
+ color: hsl(var(--foreground));
134
+ }
135
+
136
+
137
+ .code-block-body pre[class*="language-"] {
138
+ margin: 0 !important;
139
+ padding: 0 !important;
140
+ background: transparent !important;
141
+ }
142
+
143
+ .line-numbers-wrapper {
144
+ position: absolute;
145
+ top: 0;
146
+ left: 0;
147
+ width: 3rem;
148
+ padding-top: 1rem;
149
+ text-align: right;
150
+ color: var(--line-number-color);
151
+ user-select: none;
152
+ }
153
+
154
+ .line-highlight {
155
+ position: absolute;
156
+ left: 0;
157
+ right: 0;
158
+ background: hsl(var(--primary) / 0.1);
159
+ border-left: 2px solid hsl(var(--primary));
160
+ pointer-events: none;
161
+ }
162
+
163
+ .code-block-body pre[data-line-numbers="true"] .line-highlight {
164
+ padding-left: 3.5rem;
165
+ }
166
+
167
+ .code-block-body::-webkit-scrollbar {
168
+ height: 8px;
169
+ }
170
+ .code-block-body::-webkit-scrollbar-track {
171
+ background: transparent;
172
+ }
173
+ .code-block-body::-webkit-scrollbar-thumb {
174
+ background: hsl(var(--border));
175
+ border-radius: 4px;
176
+ }
177
+ .code-block-body::-webkit-scrollbar-thumb:hover {
178
+ background: hsl(var(--muted));
179
+ }