From f9664d48e7f9d8f5f473938d5074b56bba3ff034 Mon Sep 17 00:00:00 2001
From: WanQuanXie <i2cherry941219@gmail.com>
Date: Fri, 24 May 2024 18:20:15 +0800
Subject: [PATCH] feat: setup theme context config

---
 ui/app/layout.tsx                | 25 +++++++------
 ui/components/theme/Provider.tsx | 14 +++++++
 ui/components/theme/Switcher.tsx | 63 ++++++++++++++++++++++++++++++++
 ui/tailwind.config.ts            |  1 +
 4 files changed, 92 insertions(+), 11 deletions(-)
 create mode 100644 ui/components/theme/Provider.tsx
 create mode 100644 ui/components/theme/Switcher.tsx

diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx
index b3f5005..87144cf 100644
--- a/ui/app/layout.tsx
+++ b/ui/app/layout.tsx
@@ -4,6 +4,7 @@ import './globals.css';
 import { cn } from '@/lib/utils';
 import Sidebar from '@/components/Sidebar';
 import { Toaster } from 'sonner';
+import { ThemeProviderComponent } from '@/components/theme/Provider';
 
 const montserrat = Montserrat({
   weight: ['300', '400', '500', '700'],
@@ -24,18 +25,20 @@ export default function RootLayout({
   children: React.ReactNode;
 }>) {
   return (
-    <html className="h-full" lang="en">
+    <html className="h-full" lang="en" suppressHydrationWarning>
       <body className={cn('h-full', montserrat.className)}>
-        <Sidebar>{children}</Sidebar>
-        <Toaster
-          toastOptions={{
-            unstyled: true,
-            classNames: {
-              toast:
-                'bg-[#111111] text-white rounded-lg p-4 flex flex-row items-center space-x-2',
-            },
-          }}
-        />
+        <ThemeProviderComponent>
+          <Sidebar>{children}</Sidebar>
+          <Toaster
+            toastOptions={{
+              unstyled: true,
+              classNames: {
+                toast:
+                  'dark:dark:bg-[#111111] text-white rounded-lg p-4 flex flex-row items-center space-x-2',
+              },
+            }}
+          />
+        </ThemeProviderComponent>
       </body>
     </html>
   );
diff --git a/ui/components/theme/Provider.tsx b/ui/components/theme/Provider.tsx
new file mode 100644
index 0000000..2e110f6
--- /dev/null
+++ b/ui/components/theme/Provider.tsx
@@ -0,0 +1,14 @@
+'use client';
+import { ThemeProvider } from 'next-themes';
+
+export function ThemeProviderComponent({
+  children,
+}: {
+  children: React.ReactNode;
+}) {
+  return (
+    <ThemeProvider attribute="class" enableSystem={false} defaultTheme="dark">
+      {children}
+    </ThemeProvider>
+  );
+}
diff --git a/ui/components/theme/Switcher.tsx b/ui/components/theme/Switcher.tsx
new file mode 100644
index 0000000..d1f44a3
--- /dev/null
+++ b/ui/components/theme/Switcher.tsx
@@ -0,0 +1,63 @@
+'use client';
+import { useTheme } from 'next-themes';
+import { SunIcon, MoonIcon, MonitorIcon } from 'lucide-react';
+import { useCallback, useEffect, useState } from 'react';
+
+type Theme = 'dark' | 'light' | 'system';
+
+export function ThemeSwitcher() {
+  const [mounted, setMounted] = useState(false);
+
+  const { theme, setTheme } = useTheme();
+
+  const isTheme = useCallback((t: Theme) => t === theme, [theme]);
+
+  const handleThemeSwitch = (theme: Theme) => {
+    setTheme(theme);
+  };
+
+  useEffect(() => {
+    setMounted(true);
+  }, []);
+
+  useEffect(() => {
+    if (isTheme('system')) {
+      const preferDarkScheme = window.matchMedia(
+        '(prefers-color-scheme: dark)',
+      );
+
+      const detectThemeChange = (event: MediaQueryListEvent) => {
+        const theme: Theme = event.matches ? 'dark' : 'light';
+        setTheme(theme);
+      };
+
+      preferDarkScheme.addEventListener('change', detectThemeChange);
+
+      return () => {
+        preferDarkScheme.removeEventListener('change', detectThemeChange);
+      };
+    }
+  }, [isTheme, setTheme, theme]);
+
+  // Avoid Hydration Mismatch
+  if (!mounted) {
+    return null;
+  }
+
+  return isTheme('dark') ? (
+    <SunIcon
+      className="cursor-pointer"
+      onClick={() => handleThemeSwitch('light')}
+    />
+  ) : isTheme('light') ? (
+    <MoonIcon
+      className="cursor-pointer"
+      onClick={() => handleThemeSwitch('dark')}
+    />
+  ) : (
+    <MonitorIcon
+      className="cursor-pointer"
+      onClick={() => handleThemeSwitch('system')}
+    />
+  );
+}
diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts
index 05f107d..a757263 100644
--- a/ui/tailwind.config.ts
+++ b/ui/tailwind.config.ts
@@ -6,6 +6,7 @@ const config: Config = {
     './components/**/*.{js,ts,jsx,tsx,mdx}',
     './app/**/*.{js,ts,jsx,tsx,mdx}',
   ],
+  darkMode: 'class',
   theme: {
     extend: {},
   },