diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx
index 704bd2f..6cd149d 100644
--- a/app/(dashboard)/layout.tsx
+++ b/app/(dashboard)/layout.tsx
@@ -5,6 +5,7 @@ import {
SidebarTrigger,
} from "@/components/ui/sidebar";
import { Skeleton } from "@/components/ui/skeleton";
+import { ThemeSwitch } from "@/components/theme-switch";
import { AppSidebar } from "./_components/app-sidebar";
import { SidebarUser } from "./_components/sidebar-user";
@@ -15,6 +16,7 @@ export default function DashboardLayout({ children }: LayoutProps<"/">) {
footer={
}>
+
}
/>
diff --git a/app/layout.tsx b/app/layout.tsx
index 9852c15..49e3346 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import { ThemeProvider } from "next-themes";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -22,8 +23,18 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
-
{children}
+
+
+ {children}
+
+
);
}
diff --git a/components/theme-switch.tsx b/components/theme-switch.tsx
new file mode 100644
index 0000000..53f8465
--- /dev/null
+++ b/components/theme-switch.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { useSyncExternalStore } from "react";
+import { MoonIcon } from "lucide-react";
+import { useTheme } from "next-themes";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+
+const subscribe = () => () => {};
+
+export function ThemeSwitch() {
+ const { resolvedTheme, setTheme } = useTheme();
+ // Tema baru diketahui di browser; false di server dan saat hydration supaya HTML-nya sama.
+ const mounted = useSyncExternalStore(
+ subscribe,
+ () => true,
+ () => false,
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx
new file mode 100644
index 0000000..cf81474
--- /dev/null
+++ b/components/ui/switch.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
+import { cn } from "cn"
+
+function Switch({
+ className,
+ size = "default",
+ ...props
+}: SwitchPrimitive.Root.Props & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/package.json b/package.json
index 67dad33..6dc2fa2 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"lucide-react": "^1.59.0",
"mysql2": "^3.24.6",
"next": "16.4.2",
+ "next-themes": "^0.4.6",
"pg": "^8.23.1",
"react": "19.3.0",
"react-dom": "19.3.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7c66673..8046538 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
next:
specifier: 16.4.2
version: 16.4.2(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
pg:
specifier: ^8.23.1
version: 8.23.1
@@ -3019,6 +3022,12 @@ packages:
resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==}
engines: {node: '>=18'}
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
next@16.4.2:
resolution: {integrity: sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==}
engines: {node: '>=20.9.0'}
@@ -6514,6 +6523,11 @@ snapshots:
dependencies:
content-type: 2.1.0
+ next-themes@0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
+ dependencies:
+ react: 19.3.0
+ react-dom: 19.3.0(react@19.3.0)
+
next@16.4.2(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
dependencies:
'@next/env': 16.4.2