@docubook/create 1.16.0 → 2.0.0-beta.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 +1 -1
- package/src/dist/app/docs/[[...slug]]/page.tsx +16 -4
- package/src/dist/app/layout.tsx +3 -0
- package/src/dist/app/page.tsx +6 -6
- package/src/dist/components/SearchModal.tsx +41 -37
- package/src/dist/components/context-popover.tsx +2 -1
- package/src/dist/components/contexts/theme-provider.tsx +1 -2
- package/src/dist/components/leftbar.tsx +1 -1
- package/src/dist/components/markdown/AccordionMdx.tsx +6 -6
- package/src/dist/components/markdown/CardGroupMdx.tsx +10 -2
- package/src/dist/components/markdown/FileTreeMdx.tsx +2 -2
- package/src/dist/components/markdown/ImageMdx.tsx +5 -1
- package/src/dist/components/markdown/PreMdx.tsx +102 -12
- package/src/dist/components/mob-toc.tsx +1 -1
- package/src/dist/components/scroll-to-top.tsx +1 -0
- package/src/dist/components/sublink.tsx +1 -0
- package/src/dist/components/theme-toggle.tsx +38 -37
- package/src/dist/eslint.config.mjs +35 -0
- package/src/dist/hooks/useScrollPosition.ts +6 -5
- package/src/dist/lib/markdown.ts +46 -1
- package/src/dist/package.json +45 -40
- package/src/dist/postcss.config.js +1 -1
- package/src/dist/styles/globals.css +231 -111
- package/src/dist/styles/syntax.css +84 -0
- package/src/dist/tailwind.config.ts +107 -105
- package/src/dist/tsconfig.json +21 -6
- package/src/dist/components/ui/icon-cloud.tsx +0 -324
package/package.json
CHANGED
|
@@ -12,13 +12,19 @@ import MobToc from "@/components/mob-toc";
|
|
|
12
12
|
const { meta } = docuConfig;
|
|
13
13
|
|
|
14
14
|
type PageProps = {
|
|
15
|
-
params: {
|
|
15
|
+
params: Promise<{
|
|
16
16
|
slug: string[];
|
|
17
|
-
}
|
|
17
|
+
}>;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// Function to generate metadata dynamically
|
|
21
|
-
export async function generateMetadata(
|
|
21
|
+
export async function generateMetadata(props: PageProps) {
|
|
22
|
+
const params = await props.params;
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
slug = []
|
|
26
|
+
} = params;
|
|
27
|
+
|
|
22
28
|
const pathName = slug.join("/");
|
|
23
29
|
const res = await getDocsForSlug(pathName);
|
|
24
30
|
|
|
@@ -62,7 +68,13 @@ export async function generateMetadata({ params: { slug = [] } }: PageProps) {
|
|
|
62
68
|
};
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
export default async function DocsPage(
|
|
71
|
+
export default async function DocsPage(props: PageProps) {
|
|
72
|
+
const params = await props.params;
|
|
73
|
+
|
|
74
|
+
const {
|
|
75
|
+
slug = []
|
|
76
|
+
} = params;
|
|
77
|
+
|
|
66
78
|
const pathName = slug.join("/");
|
|
67
79
|
const res = await getDocsForSlug(pathName);
|
|
68
80
|
|
package/src/dist/app/layout.tsx
CHANGED
|
@@ -6,6 +6,9 @@ import { GeistMono } from "geist/font/mono";
|
|
|
6
6
|
import { Footer } from "@/components/footer";
|
|
7
7
|
import docuConfig from "@/docu.json";
|
|
8
8
|
import { Toaster } from "@/components/ui/sonner";
|
|
9
|
+
import "@docsearch/css";
|
|
10
|
+
import "@/styles/algolia.css";
|
|
11
|
+
import "@/styles/syntax.css";
|
|
9
12
|
import "@/styles/globals.css";
|
|
10
13
|
|
|
11
14
|
const { meta } = docuConfig;
|
package/src/dist/app/page.tsx
CHANGED
|
@@ -25,21 +25,21 @@ 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>🚀
|
|
28
|
+
<span>🚀 Release v2.0.0-beta.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>
|
|
32
32
|
</div>
|
|
33
33
|
</Link>
|
|
34
|
-
|
|
34
|
+
<div className="w-full max-w-[800px] pb-8">
|
|
35
35
|
<h1 className="mb-4 text-2xl font-bold sm:text-5xl">DocuBook Starter Templates</h1>
|
|
36
36
|
<p className="mb-8 sm:text-xl text-muted-foreground">
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
Get started by editing app/page.tsx . Save and see your changes instantly.{' '}
|
|
38
|
+
<Link className="text-primary underline" href="https://www.docubook.pro/docs/getting-started/introduction" target="_blank">
|
|
39
39
|
Read Documentations
|
|
40
|
-
|
|
40
|
+
</Link>
|
|
41
41
|
</p>
|
|
42
|
-
|
|
42
|
+
</div>
|
|
43
43
|
<div className="flex flex-row items-center gap-6 mb-10">
|
|
44
44
|
<Link
|
|
45
45
|
href={`/docs${page_routes[0].href}`}
|
|
@@ -50,6 +50,7 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
|
|
|
50
50
|
|
|
51
51
|
useEffect(() => {
|
|
52
52
|
if (!isOpen) {
|
|
53
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
53
54
|
setSearchedInput("");
|
|
54
55
|
}
|
|
55
56
|
}, [isOpen]);
|
|
@@ -71,9 +72,9 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
|
|
|
71
72
|
return advanceSearch(trimmedInput) as unknown as SearchResult[];
|
|
72
73
|
}, [searchedInput]);
|
|
73
74
|
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
}, [filteredResults]);
|
|
75
|
+
// useEffect(() => {
|
|
76
|
+
// setSelectedIndex(0);
|
|
77
|
+
// }, [filteredResults]);
|
|
77
78
|
|
|
78
79
|
useEffect(() => {
|
|
79
80
|
const handleNavigation = (event: KeyboardEvent) => {
|
|
@@ -114,10 +115,13 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
|
|
|
114
115
|
<DialogTitle className="sr-only">Search Documentation</DialogTitle>
|
|
115
116
|
<DialogDescription className="sr-only">Search through the documentation</DialogDescription>
|
|
116
117
|
</DialogHeader>
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
<input
|
|
119
120
|
value={searchedInput}
|
|
120
|
-
onChange={(e) =>
|
|
121
|
+
onChange={(e) => {
|
|
122
|
+
setSearchedInput(e.target.value);
|
|
123
|
+
setSelectedIndex(0);
|
|
124
|
+
}}
|
|
121
125
|
placeholder="Type something to search..."
|
|
122
126
|
autoFocus
|
|
123
127
|
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) {
|
|
|
138
142
|
const isActive = index === selectedIndex;
|
|
139
143
|
|
|
140
144
|
return (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
<DialogClose key={item.href} asChild>
|
|
146
|
+
<Anchor
|
|
147
|
+
ref={(el) => {
|
|
148
|
+
itemRefs.current[index] = el as HTMLDivElement | null;
|
|
149
|
+
}}
|
|
150
|
+
className={cn(
|
|
151
|
+
"dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5",
|
|
152
|
+
isActive && "bg-primary/20 dark:bg-primary/30",
|
|
153
|
+
paddingClass
|
|
154
|
+
)}
|
|
155
|
+
href={`/docs${item.href}`}
|
|
156
|
+
tabIndex={-1}
|
|
157
|
+
>
|
|
158
|
+
<div
|
|
146
159
|
className={cn(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
paddingClass
|
|
160
|
+
"flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
|
|
161
|
+
level > 1 && "border-l pl-4"
|
|
150
162
|
)}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
className={cn(
|
|
156
|
-
"flex items-center w-full h-full py-3 gap-1.5 px-2 justify-between",
|
|
157
|
-
level > 1 && "border-l pl-4"
|
|
158
|
-
)}
|
|
159
|
-
>
|
|
160
|
-
<div className="flex items-center">
|
|
161
|
-
<FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
|
|
162
|
-
<span>{item.title}</span>
|
|
163
|
-
</div>
|
|
164
|
-
{isActive && (
|
|
165
|
-
<div className="hidden md:flex items-center text-xs text-muted-foreground">
|
|
166
|
-
<span>Return</span>
|
|
167
|
-
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
163
|
+
>
|
|
164
|
+
<div className="flex items-center">
|
|
165
|
+
<FileTextIcon className="h-[1.1rem] w-[1.1rem] mr-1" />
|
|
166
|
+
<span>{item.title}</span>
|
|
170
167
|
</div>
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
{isActive && (
|
|
169
|
+
<div className="hidden md:flex items-center text-xs text-muted-foreground">
|
|
170
|
+
<span>Return</span>
|
|
171
|
+
<CornerDownLeftIcon className="h-3 w-3 ml-1" />
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
</Anchor>
|
|
176
|
+
</DialogClose>
|
|
173
177
|
);
|
|
174
178
|
})}
|
|
175
179
|
</div>
|
|
@@ -177,14 +181,14 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
|
|
|
177
181
|
<DialogFooter className="md:flex md:justify-start hidden h-14 px-6 bg-transparent border-t text-[14px] outline-none">
|
|
178
182
|
<div className="flex items-center gap-2">
|
|
179
183
|
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
180
|
-
<ArrowUpIcon className="w-3 h-3"/>
|
|
184
|
+
<ArrowUpIcon className="w-3 h-3" />
|
|
181
185
|
</span>
|
|
182
186
|
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
183
|
-
<ArrowDownIcon className="w-3 h-3"/>
|
|
187
|
+
<ArrowDownIcon className="w-3 h-3" />
|
|
184
188
|
</span>
|
|
185
189
|
<p className="text-muted-foreground">to navigate</p>
|
|
186
190
|
<span className="dark:bg-accent/15 bg-slate-200 border rounded p-2">
|
|
187
|
-
<CornerDownLeftIcon className="w-3 h-3"/>
|
|
191
|
+
<CornerDownLeftIcon className="w-3 h-3" />
|
|
188
192
|
</span>
|
|
189
193
|
<p className="text-muted-foreground">to select</p>
|
|
190
194
|
<span className="dark:bg-accent/15 bg-slate-200 border rounded px-2 py-1">
|
|
@@ -45,6 +45,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
|
|
|
45
45
|
|
|
46
46
|
useEffect(() => {
|
|
47
47
|
if (pathname.startsWith("/docs")) {
|
|
48
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
48
49
|
setActiveRoute(getActiveContextRoute(pathname));
|
|
49
50
|
} else {
|
|
50
51
|
setActiveRoute(undefined);
|
|
@@ -61,7 +62,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
|
|
|
61
62
|
<Button
|
|
62
63
|
variant="ghost"
|
|
63
64
|
className={cn(
|
|
64
|
-
"w-full max-w-[240px] flex items-center justify-between font-semibold text-foreground px-0 pt-8",
|
|
65
|
+
"w-full max-w-[240px] cursor-pointer flex items-center justify-between font-semibold text-foreground px-0 pt-8",
|
|
65
66
|
"hover:bg-transparent hover:text-foreground",
|
|
66
67
|
className
|
|
67
68
|
)}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import * as React from "react";
|
|
4
3
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
5
|
-
import { type ThemeProviderProps } from "next-themes
|
|
4
|
+
import { type ThemeProviderProps } from "next-themes";
|
|
6
5
|
|
|
7
6
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
8
7
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
@@ -29,7 +29,7 @@ export function ToggleButton({
|
|
|
29
29
|
<Button
|
|
30
30
|
size="icon"
|
|
31
31
|
variant="outline"
|
|
32
|
-
className="hover:bg-transparent hover:text-inherit border-none text-muted-foreground"
|
|
32
|
+
className="cursor-pointer hover:bg-transparent hover:text-inherit border-none text-muted-foreground"
|
|
33
33
|
onClick={onToggle}
|
|
34
34
|
>
|
|
35
35
|
{collapsed ? (
|
|
@@ -4,7 +4,7 @@ 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
|
+
import { AccordionGroupContext } from '@/components/contexts/AccordionContext';
|
|
8
8
|
|
|
9
9
|
type AccordionProps = {
|
|
10
10
|
title: string;
|
|
@@ -27,7 +27,7 @@ const Accordion: React.FC<AccordionProps> = ({
|
|
|
27
27
|
// The main wrapper div for the accordion.
|
|
28
28
|
// All styling logic for the accordion container is handled here.
|
|
29
29
|
return (
|
|
30
|
-
<div
|
|
30
|
+
<div
|
|
31
31
|
className={cn(
|
|
32
32
|
// Style for STANDALONE: full card with border & shadow
|
|
33
33
|
!isInGroup && "border rounded-lg shadow-sm",
|
|
@@ -38,16 +38,16 @@ const Accordion: React.FC<AccordionProps> = ({
|
|
|
38
38
|
<button
|
|
39
39
|
type="button"
|
|
40
40
|
onClick={() => setIsOpen(!isOpen)}
|
|
41
|
-
className="flex items-center
|
|
41
|
+
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"
|
|
42
42
|
>
|
|
43
43
|
<ChevronRight
|
|
44
44
|
className={cn(
|
|
45
|
-
"w-4 h-4 text-muted-foreground transition-transform duration-200",
|
|
45
|
+
"w-4 h-4 text-muted-foreground transition-transform duration-200 flex-shrink-0",
|
|
46
46
|
isOpen && "rotate-90"
|
|
47
47
|
)}
|
|
48
48
|
/>
|
|
49
|
-
{Icon && <Icon className="text-foreground w-4 h-4"/>
|
|
50
|
-
<h3 className="font-medium text-base text-foreground m-0">{title}</h3>
|
|
49
|
+
{Icon && <Icon className="text-foreground w-4 h-4 flex-shrink-0" />}
|
|
50
|
+
<h3 className="font-medium text-base text-foreground !m-0 leading-none">{title}</h3>
|
|
51
51
|
</button>
|
|
52
52
|
|
|
53
53
|
{isOpen && (
|
|
@@ -8,13 +8,21 @@ interface CardGroupProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const CardGroup: React.FC<CardGroupProps> = ({ children, cols = 2, className }) => {
|
|
11
|
-
const cardsArray = React.Children.toArray(children);
|
|
11
|
+
const cardsArray = React.Children.toArray(children);
|
|
12
|
+
|
|
13
|
+
// Static grid column classes for Tailwind v4 compatibility
|
|
14
|
+
const gridColsClass = {
|
|
15
|
+
1: "grid-cols-1",
|
|
16
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
17
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
18
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
|
|
19
|
+
}[cols] || "grid-cols-1 sm:grid-cols-2";
|
|
12
20
|
|
|
13
21
|
return (
|
|
14
22
|
<div
|
|
15
23
|
className={clsx(
|
|
16
24
|
"grid gap-4 text-foreground",
|
|
17
|
-
|
|
25
|
+
gridColsClass,
|
|
18
26
|
className
|
|
19
27
|
)}
|
|
20
28
|
>
|
|
@@ -100,8 +100,8 @@ export const Files = ({ children }: { children: ReactNode }) => {
|
|
|
100
100
|
return (
|
|
101
101
|
<div
|
|
102
102
|
className="
|
|
103
|
-
rounded-xl border border-muted/
|
|
104
|
-
bg-card/
|
|
103
|
+
rounded-xl border border-muted/20
|
|
104
|
+
bg-card/20 backdrop-blur-sm
|
|
105
105
|
shadow-sm overflow-hidden
|
|
106
106
|
transition-all duration-200
|
|
107
107
|
hover:shadow-md hover:border-muted/60
|
|
@@ -4,13 +4,17 @@ import NextImage from "next/image";
|
|
|
4
4
|
type Height = ComponentProps<typeof NextImage>["height"];
|
|
5
5
|
type Width = ComponentProps<typeof NextImage>["width"];
|
|
6
6
|
|
|
7
|
+
type ImageProps = Omit<ComponentProps<"img">, "src"> & {
|
|
8
|
+
src?: ComponentProps<typeof NextImage>["src"];
|
|
9
|
+
};
|
|
10
|
+
|
|
7
11
|
export default function Image({
|
|
8
12
|
src,
|
|
9
13
|
alt = "alt",
|
|
10
14
|
width = 800,
|
|
11
15
|
height = 350,
|
|
12
16
|
...props
|
|
13
|
-
}:
|
|
17
|
+
}: ImageProps) {
|
|
14
18
|
if (!src) return null;
|
|
15
19
|
return (
|
|
16
20
|
<NextImage
|
|
@@ -1,19 +1,109 @@
|
|
|
1
|
-
import { ComponentProps } from "react";
|
|
1
|
+
import { type ComponentProps, type JSX } 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="
|
|
11
|
-
<div className="
|
|
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
|
-
|
|
15
|
-
<
|
|
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
|
+
}
|
|
@@ -14,7 +14,7 @@ interface MobTocProps {
|
|
|
14
14
|
tocs: TocItem[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const useClickOutside = (ref: React.RefObject<HTMLElement>, callback: () => void) => {
|
|
17
|
+
const useClickOutside = (ref: React.RefObject<HTMLElement | null>, callback: () => void) => {
|
|
18
18
|
const handleClick = React.useCallback((event: MouseEvent) => {
|
|
19
19
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
20
20
|
callback();
|
|
@@ -44,6 +44,7 @@ export default function SubLink({
|
|
|
44
44
|
// Auto-expand if current path is a child of this item
|
|
45
45
|
useEffect(() => {
|
|
46
46
|
if (items && (path.startsWith(fullHref) && path !== fullHref)) {
|
|
47
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
47
48
|
setIsOpen(true);
|
|
48
49
|
}
|
|
49
50
|
}, [path, fullHref, items]);
|
|
@@ -1,68 +1,69 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Moon, Sun
|
|
4
|
+
import { Moon, Sun } from "lucide-react";
|
|
5
5
|
import { useTheme } from "next-themes";
|
|
6
6
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
7
7
|
|
|
8
8
|
export function ModeToggle() {
|
|
9
|
-
const { theme, setTheme } = useTheme();
|
|
10
|
-
const [
|
|
9
|
+
const { theme, setTheme, resolvedTheme } = useTheme();
|
|
10
|
+
const [mounted, setMounted] = React.useState(false);
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// Untuk menghindari hydration mismatch
|
|
13
13
|
React.useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
setMounted(true);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
// Jika belum mounted, jangan render apapun untuk menghindari mismatch
|
|
18
|
+
if (!mounted) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1">
|
|
21
|
+
<div className="rounded-full p-1 w-8 h-8" />
|
|
22
|
+
<div className="rounded-full p-1 w-8 h-8" />
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Tentukan theme yang aktif: gunakan resolvedTheme untuk menampilkan ikon yang sesuai
|
|
28
|
+
// jika theme === "system", resolvedTheme akan menjadi "light" atau "dark" sesuai device
|
|
29
|
+
const activeTheme = theme === "system" || !theme ? resolvedTheme : theme;
|
|
30
|
+
|
|
31
|
+
const handleToggle = () => {
|
|
32
|
+
// Toggle antara light dan dark
|
|
33
|
+
// Jika sekarang light, ganti ke dark, dan sebaliknya
|
|
34
|
+
if (activeTheme === "light") {
|
|
35
|
+
setTheme("dark");
|
|
16
36
|
} else {
|
|
17
|
-
|
|
37
|
+
setTheme("light");
|
|
18
38
|
}
|
|
19
|
-
}
|
|
39
|
+
};
|
|
20
40
|
|
|
21
41
|
return (
|
|
22
42
|
<ToggleGroup
|
|
23
43
|
type="single"
|
|
24
|
-
value={
|
|
25
|
-
onValueChange={
|
|
26
|
-
if (value) {
|
|
27
|
-
setTheme(value);
|
|
28
|
-
setSelectedTheme(value);
|
|
29
|
-
}
|
|
30
|
-
}}
|
|
44
|
+
value={activeTheme}
|
|
45
|
+
onValueChange={handleToggle}
|
|
31
46
|
className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1 transition-all"
|
|
32
47
|
>
|
|
33
48
|
<ToggleGroupItem
|
|
34
49
|
value="light"
|
|
35
50
|
size="sm"
|
|
36
51
|
aria-label="Light Mode"
|
|
37
|
-
className={`rounded-full p-1 transition-all ${
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}`}
|
|
52
|
+
className={`rounded-full p-1 transition-all ${activeTheme === "light"
|
|
53
|
+
? "bg-primary text-primary-foreground"
|
|
54
|
+
: "bg-transparent hover:bg-muted/50"
|
|
55
|
+
}`}
|
|
42
56
|
>
|
|
43
57
|
<Sun className="h-4 w-4" />
|
|
44
58
|
</ToggleGroupItem>
|
|
45
|
-
<ToggleGroupItem
|
|
46
|
-
value="system"
|
|
47
|
-
size="sm"
|
|
48
|
-
aria-label="System Mode"
|
|
49
|
-
className={`rounded-full p-1 transition-all ${
|
|
50
|
-
selectedTheme === "system"
|
|
51
|
-
? "bg-primary text-primary-foreground"
|
|
52
|
-
: "bg-transparent hover:bg-muted/50"
|
|
53
|
-
}`}
|
|
54
|
-
>
|
|
55
|
-
<Monitor className="h-4 w-4" />
|
|
56
|
-
</ToggleGroupItem>
|
|
57
59
|
<ToggleGroupItem
|
|
58
60
|
value="dark"
|
|
59
61
|
size="sm"
|
|
60
62
|
aria-label="Dark Mode"
|
|
61
|
-
className={`rounded-full p-1 transition-all ${
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}`}
|
|
63
|
+
className={`rounded-full p-1 transition-all ${activeTheme === "dark"
|
|
64
|
+
? "bg-primary text-primary-foreground"
|
|
65
|
+
: "bg-transparent hover:bg-muted/50"
|
|
66
|
+
}`}
|
|
66
67
|
>
|
|
67
68
|
<Moon className="h-4 w-4" />
|
|
68
69
|
</ToggleGroupItem>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from "eslint/config";
|
|
2
|
+
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTypescript from "eslint-config-next/typescript";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import js from "@eslint/js";
|
|
7
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const compat = new FlatCompat({
|
|
12
|
+
baseDirectory: __dirname,
|
|
13
|
+
recommendedConfig: js.configs.recommended,
|
|
14
|
+
allConfig: js.configs.all
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export default defineConfig([{
|
|
18
|
+
extends: [
|
|
19
|
+
...nextCoreWebVitals,
|
|
20
|
+
...nextTypescript,
|
|
21
|
+
...compat.extends("plugin:@typescript-eslint/recommended")
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
rules: {
|
|
25
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
26
|
+
|
|
27
|
+
"@typescript-eslint/no-unused-vars": ["warn", {
|
|
28
|
+
argsIgnorePattern: "^_",
|
|
29
|
+
varsIgnorePattern: "^_",
|
|
30
|
+
caughtErrorsIgnorePattern: "^_",
|
|
31
|
+
}],
|
|
32
|
+
|
|
33
|
+
"@typescript-eslint/no-empty-object-type": "off",
|
|
34
|
+
},
|
|
35
|
+
}]);
|