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