@docubook/create 1.16.1 → 2.0.0-beta.2

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.16.1",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "CLI to create DocuBook projects",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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({ params: { slug = [] } }: PageProps) {
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({ params: { slug = [] } }: PageProps) {
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
 
@@ -85,17 +97,16 @@ export default async function DocsPage({ params: { slug = [] } }: PageProps) {
85
97
  <p className="-mt-4 text-muted-foreground text-[16.5px]">{description}</p>
86
98
  <div>{res.content}</div>
87
99
  <div
88
- className={`my-8 flex items-center border-b-2 border-dashed border-x-muted-foreground ${
89
- docuConfig.repository?.editLink ? "justify-between" : "justify-end"
90
- }`}
91
- >
100
+ className={`my-8 flex items-center border-b-2 border-dashed border-x-muted-foreground ${docuConfig.repository?.editLink ? "justify-between" : "justify-end"
101
+ }`}
102
+ >
92
103
  {docuConfig.repository?.editLink && <EditThisPage filePath={filePath} />}
93
104
  {date && (
94
- <p className="text-[13px] text-muted-foreground">
105
+ <p className="text-[13px] text-muted-foreground">
95
106
  Published on {formatDate2(date)}
96
- </p>
107
+ </p>
97
108
  )}
98
- </div>
109
+ </div>
99
110
  <Pagination pathname={pathName} />
100
111
  </Typography>
101
112
  </div>
@@ -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;
@@ -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>🚀 New Version - Release v1.16.1</span>
28
+ <span>🚀 Release v2.0.0-beta.2</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
- <div className="w-full max-w-[800px] pb-8">
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
- 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">
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
- </Link>
40
+ </Link>
41
41
  </p>
42
- </div>
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
- setSelectedIndex(0);
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) => setSearchedInput(e.target.value)}
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
- <DialogClose key={item.href} asChild>
142
- <Anchor
143
- ref={(el) => {
144
- itemRefs.current[index] = el as HTMLDivElement | null;
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
- "dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5",
148
- isActive && "bg-primary/20 dark:bg-primary/30",
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
- href={`/docs${item.href}`}
152
- tabIndex={-1}
153
- >
154
- <div
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
- </Anchor>
172
- </DialogClose>
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
  )}
@@ -95,7 +96,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
95
96
  key={route.href}
96
97
  onClick={() => router.push(contextPath)}
97
98
  className={cn(
98
- "relative flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm",
99
+ "relative flex w-full items-center gap-2 cursor-pointer rounded px-2 py-1.5 text-sm",
99
100
  "text-left outline-none transition-colors",
100
101
  isActive
101
102
  ? "bg-primary/20 text-primary dark:bg-accent/20 dark:text-accent"
@@ -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/dist/types";
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 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"
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 cursor-pointer"
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); // Pastikan children berupa array
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
- `grid-cols-1 sm:grid-cols-${cols}`,
25
+ gridColsClass,
18
26
  className
19
27
  )}
20
28
  >
@@ -19,7 +19,7 @@ export default function Copy({ content }: { content: string }) {
19
19
  return (
20
20
  <Button
21
21
  variant="secondary"
22
- className="border"
22
+ className="border cursor-copy"
23
23
  size="xs"
24
24
  onClick={handleCopy}
25
25
  >
@@ -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/50
104
- bg-card/50 backdrop-blur-sm
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
@@ -1,25 +1,129 @@
1
- import { ComponentProps } from "react";
1
+ "use client";
2
+
3
+ import { ComponentProps, useState, useEffect } from "react";
2
4
  import NextImage from "next/image";
5
+ import { createPortal } from "react-dom";
6
+ import { motion, AnimatePresence } from "framer-motion";
7
+ import { X, ZoomIn } from "lucide-react";
3
8
 
4
9
  type Height = ComponentProps<typeof NextImage>["height"];
5
10
  type Width = ComponentProps<typeof NextImage>["width"];
6
11
 
12
+ type ImageProps = Omit<ComponentProps<"img">, "src"> & {
13
+ src?: ComponentProps<typeof NextImage>["src"];
14
+ };
15
+
7
16
  export default function Image({
8
- src,
9
- alt = "alt",
10
- width = 800,
11
- height = 350,
12
- ...props
13
- }: ComponentProps<"img">) {
14
- if (!src) return null;
15
- return (
16
- <NextImage
17
- src={src}
18
- alt={alt}
19
- width={width as Width}
20
- height={height as Height}
21
- quality={40}
22
- {...props}
23
- />
24
- );
17
+ src,
18
+ alt = "alt",
19
+ width = 800,
20
+ height = 350,
21
+ ...props
22
+ }: ImageProps) {
23
+ const [isOpen, setIsOpen] = useState(false);
24
+
25
+ // Lock scroll when open
26
+ useEffect(() => {
27
+ if (isOpen) {
28
+ document.body.style.overflow = "hidden";
29
+ // Check for Escape key
30
+ const handleEsc = (e: KeyboardEvent) => {
31
+ if (e.key === "Escape") setIsOpen(false);
32
+ };
33
+ window.addEventListener("keydown", handleEsc);
34
+ return () => {
35
+ document.body.style.overflow = "auto";
36
+ window.removeEventListener("keydown", handleEsc);
37
+ };
38
+ }
39
+ }, [isOpen]);
40
+
41
+ if (!src) return null;
42
+
43
+ return (
44
+ <>
45
+ <button
46
+ type="button"
47
+ className="relative group cursor-zoom-in my-6 w-full flex justify-center rounded-lg"
48
+ onClick={() => setIsOpen(true)}
49
+ aria-label="Zoom image"
50
+ >
51
+ <span className="absolute inset-0 bg-black/0 group-hover:bg-black/5 transition-colors z-10 flex items-center justify-center opacity-0 group-hover:opacity-100 rounded-lg">
52
+ <ZoomIn className="w-8 h-8 text-white drop-shadow-md" />
53
+ </span>
54
+ <NextImage
55
+ src={src}
56
+ alt={alt}
57
+ width={width as Width}
58
+ height={height as Height}
59
+ quality={85}
60
+ className="w-full h-auto rounded-lg transition-transform duration-300 group-hover:scale-[1.01]"
61
+ {...props}
62
+ />
63
+ </button>
64
+
65
+ <AnimatePresence>
66
+ {isOpen && (
67
+ <Portal>
68
+ <motion.div
69
+ initial={{ opacity: 0 }}
70
+ animate={{ opacity: 1 }}
71
+ exit={{ opacity: 0 }}
72
+ className="fixed inset-0 z-[99999] flex items-center justify-center bg-black/90 backdrop-blur-md p-4 md:p-10 cursor-zoom-out"
73
+ onClick={() => setIsOpen(false)}
74
+ >
75
+ {/* Close Button */}
76
+ <button
77
+ className="absolute top-4 right-4 z-50 p-2 text-white/70 hover:text-white bg-black/20 hover:bg-white/10 rounded-full transition-colors"
78
+ onClick={(e) => {
79
+ e.stopPropagation();
80
+ setIsOpen(false);
81
+ }}
82
+ >
83
+ <X className="w-6 h-6" />
84
+ </button>
85
+
86
+ {/* Image Container */}
87
+ <motion.div
88
+ initial={{ scale: 0.9, opacity: 0 }}
89
+ animate={{ scale: 1, opacity: 1 }}
90
+ exit={{ scale: 0.9, opacity: 0 }}
91
+ transition={{ type: "spring", damping: 25, stiffness: 300 }}
92
+ className="relative max-w-7xl w-full h-full flex items-center justify-center"
93
+ onClick={(e) => e.stopPropagation()}
94
+ >
95
+ <div className="relative w-full h-full flex items-center justify-center" onClick={() => setIsOpen(false)}>
96
+ <NextImage
97
+ src={src}
98
+ alt={alt}
99
+ width={1920}
100
+ height={1080}
101
+ className="object-contain max-h-[90vh] w-auto h-auto rounded-md shadow-2xl"
102
+ quality={95}
103
+ />
104
+ </div>
105
+ </motion.div>
106
+
107
+ {/* Caption */}
108
+ {alt && alt !== "alt" && (
109
+ <motion.div
110
+ initial={{ y: 20, opacity: 0 }}
111
+ animate={{ y: 0, opacity: 1 }}
112
+ className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-black/60 text-white px-4 py-2 rounded-full text-sm font-medium backdrop-blur-md border border-white/10"
113
+ >
114
+ {alt}
115
+ </motion.div>
116
+ )}
117
+
118
+ </motion.div>
119
+ </Portal>
120
+ )}
121
+ </AnimatePresence>
122
+ </>
123
+ );
25
124
  }
125
+
126
+ const Portal = ({ children }: { children: React.ReactNode }) => {
127
+ if (typeof window === "undefined") return null;
128
+ return createPortal(children, document.body);
129
+ };
@@ -1,52 +1,69 @@
1
+ "use client";
2
+
1
3
  import { cn } from "@/lib/utils";
2
- import clsx from "clsx";
3
- import { PropsWithChildren } from "react";
4
+ import { cva, type VariantProps } from "class-variance-authority";
4
5
  import {
5
6
  Info,
6
7
  AlertTriangle,
7
8
  ShieldAlert,
8
- CheckCircle,
9
+ CheckCircle2,
9
10
  } from "lucide-react";
11
+ import React from "react";
10
12
 
11
- type NoteProps = PropsWithChildren & {
12
- title?: string;
13
- type?: "note" | "danger" | "warning" | "success";
14
- };
13
+ const noteVariants = cva(
14
+ "relative w-full rounded-lg border border-l-4 p-4 mb-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
15
+ {
16
+ variants: {
17
+ variant: {
18
+ note: "bg-muted/30 border-border border-l-primary/50 text-foreground [&>svg]:text-primary",
19
+ danger: "border-destructive/20 border-l-destructive/60 bg-destructive/5 text-destructive [&>svg]:text-destructive dark:border-destructive/30",
20
+ warning: "border-orange-500/20 border-l-orange-500/60 bg-orange-500/5 text-orange-600 dark:text-orange-400 [&>svg]:text-orange-600 dark:[&>svg]:text-orange-400",
21
+ success: "border-emerald-500/20 border-l-emerald-500/60 bg-emerald-500/5 text-emerald-600 dark:text-emerald-400 [&>svg]:text-emerald-600 dark:[&>svg]:text-emerald-400",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "note",
26
+ },
27
+ }
28
+ );
15
29
 
16
30
  const iconMap = {
17
- note: <Info size={16} className="text-blue-500" />,
18
- danger: <ShieldAlert size={16} className="text-red-500" />,
19
- warning: <AlertTriangle size={16} className="text-orange-500" />,
20
- success: <CheckCircle size={16} className="text-green-500" />,
31
+ note: Info,
32
+ danger: ShieldAlert,
33
+ warning: AlertTriangle,
34
+ success: CheckCircle2,
21
35
  };
22
36
 
37
+ interface NoteProps
38
+ extends React.HTMLAttributes<HTMLDivElement>,
39
+ VariantProps<typeof noteVariants> {
40
+ title?: string;
41
+ type?: "note" | "danger" | "warning" | "success";
42
+ }
43
+
23
44
  export default function Note({
24
- children,
45
+ className,
25
46
  title = "Note",
26
47
  type = "note",
48
+ children,
49
+ ...props
27
50
  }: NoteProps) {
28
- const noteClassNames = clsx({
29
- "dark:bg-stone-950/25 bg-stone-50": type === "note",
30
- "dark:bg-red-950 bg-red-100 border-red-200 dark:border-red-900":
31
- type === "danger",
32
- "bg-orange-50 border-orange-200 dark:border-orange-900 dark:bg-orange-900/50":
33
- type === "warning",
34
- "dark:bg-green-950 bg-green-100 border-green-200 dark:border-green-900":
35
- type === "success",
36
- });
51
+ const Icon = iconMap[type] || Info;
37
52
 
38
53
  return (
39
54
  <div
40
- className={cn(
41
- "border rounded-md px-5 pb-0.5 mt-5 mb-7 text-sm tracking-wide",
42
- noteClassNames
43
- )}
55
+ className={cn(noteVariants({ variant: type }), className)}
56
+ {...props}
44
57
  >
45
- <div className="flex items-center gap-2 font-bold -mb-2.5 pt-6">
46
- {iconMap[type]}
47
- <span className="text-base">{title}:</span>
58
+ <Icon className="h-5 w-5" />
59
+ <div className="pl-8">
60
+ <h5 className="mb-1 font-medium leading-none tracking-tight">
61
+ {title}
62
+ </h5>
63
+ <div className="text-sm [&_p]:leading-relaxed opacity-90">
64
+ {children}
65
+ </div>
48
66
  </div>
49
- {children}
50
67
  </div>
51
68
  );
52
69
  }
@@ -1,4 +1,4 @@
1
- import { type ComponentProps } from "react";
1
+ import { type ComponentProps, type JSX } from "react";
2
2
  import Copy from "./CopyMdx";
3
3
  import {
4
4
  SiJavascript,
@@ -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();
@@ -32,6 +32,7 @@ export function ScrollToTop({
32
32
 
33
33
  useEffect(() => {
34
34
  // Initial check
35
+ // eslint-disable-next-line react-hooks/set-state-in-effect
35
36
  checkScroll();
36
37
 
37
38
  // Set up scroll listener with debounce for better performance