@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/create",
3
- "version": "1.16.0",
3
+ "version": "2.0.0-beta.1",
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
 
@@ -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.0</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
- <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
  )}
@@ -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"
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
  >
@@ -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
@@ -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
- }: ComponentProps<"img">) {
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="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
+ }
@@ -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
@@ -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, Monitor } from "lucide-react";
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 [selectedTheme, setSelectedTheme] = React.useState<string>("system");
9
+ const { theme, setTheme, resolvedTheme } = useTheme();
10
+ const [mounted, setMounted] = React.useState(false);
11
11
 
12
- // Pastikan toggle tetap di posisi yang benar setelah reload
12
+ // Untuk menghindari hydration mismatch
13
13
  React.useEffect(() => {
14
- if (theme) {
15
- setSelectedTheme(theme);
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
- setSelectedTheme("system"); // Default ke system jika undefined
37
+ setTheme("light");
18
38
  }
19
- }, [theme]);
39
+ };
20
40
 
21
41
  return (
22
42
  <ToggleGroup
23
43
  type="single"
24
- value={selectedTheme}
25
- onValueChange={(value) => {
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
- selectedTheme === "light"
39
- ? "bg-primary text-primary-foreground"
40
- : "bg-transparent hover:bg-muted/50"
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
- selectedTheme === "dark"
63
- ? "bg-primary text-primary-foreground"
64
- : "bg-transparent hover:bg-muted/50"
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
+ }]);