diff --git a/src/App.tsx b/src/App.tsx index 5e77f70d..fec46f19 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,11 @@ import { AppDataProvider } from "./providers/app-data-provider"; -import { ThemeProvider } from "@/components/layout/theme-provider"; import Layout from "./pages/_layout"; function App() { return ( - - - - - + + + ); } export default App; diff --git a/src/components/base/NoticeManager.tsx b/src/components/base/NoticeManager.tsx deleted file mode 100644 index 88ec7281..00000000 --- a/src/components/base/NoticeManager.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import { Toaster, toast } from "sonner"; -import { useEffect, useSyncExternalStore } from "react"; -import { - getSnapshotNotices, - hideNotice, - subscribeNotices, -} from "@/services/noticeService"; - -export const NoticeManager = () => { - const currentNotices = useSyncExternalStore( - subscribeNotices, - getSnapshotNotices, - ); - - useEffect(() => { - for (const notice of currentNotices) { - const toastId = toast(notice.message, { - id: notice.id, - duration: notice.duration, - onDismiss: (t) => { - hideNotice(t.id as number); - }, - }); - } - }, [currentNotices]); - - return ; -}; diff --git a/src/components/base/index.ts b/src/components/base/index.ts index e0e1afb2..82686671 100644 --- a/src/components/base/index.ts +++ b/src/components/base/index.ts @@ -5,4 +5,3 @@ export { BaseLoading } from "./base-loading"; export { BaseErrorBoundary } from "./base-error-boundary"; export { Switch } from "./base-switch"; export { BaseLoadingOverlay } from "./base-loading-overlay"; -export { NoticeManager } from "./NoticeManager"; diff --git a/src/components/layout/use-custom-theme.ts b/src/components/layout/use-custom-theme.ts index 9513022f..50a909e3 100644 --- a/src/components/layout/use-custom-theme.ts +++ b/src/components/layout/use-custom-theme.ts @@ -1,47 +1,52 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useSetThemeMode, useThemeMode } from "@/services/states"; import { useVerge } from "@/hooks/use-verge"; -import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; +import { getCurrentWebviewWindow, WebviewWindow } from "@tauri-apps/api/webviewWindow"; import { Theme } from "@tauri-apps/api/window"; export const useCustomTheme = () => { - const appWindow = useMemo(() => getCurrentWebviewWindow(), []); + const appWindow: WebviewWindow = useMemo(() => getCurrentWebviewWindow(), []); const { verge } = useVerge(); const { theme_mode } = verge ?? {}; const mode = useThemeMode(); const setMode = useSetThemeMode(); + const [systemTheme, setSystemTheme] = useState( + () => window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" + ); + useEffect(() => { setMode( - theme_mode === "light" || theme_mode === "dark" ? theme_mode : "system", + theme_mode === "light" || theme_mode === "dark" ? theme_mode : "system", ); }, [theme_mode, setMode]); useEffect(() => { - const root = document.documentElement; + if (mode !== 'system') return; - const activeTheme = - mode === "system" - ? window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light" - : mode; + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = (e: MediaQueryListEvent) => { + setSystemTheme(e.matches ? "dark" : "light"); + }; - root.classList.remove("light", "dark"); - root.classList.add(activeTheme); - appWindow.setTheme(activeTheme as Theme).catch(console.error); - }, [mode, appWindow]); + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, [mode]); useEffect(() => { - if (theme_mode !== "system") return; - const unlistenPromise = appWindow.onThemeChanged(({ payload }) => { - setMode(payload); - }); - return () => { - unlistenPromise.then((f) => f()); - }; - }, [theme_mode, appWindow, setMode]); + const root = document.documentElement; + const activeTheme = mode === "system" ? systemTheme : mode; + root.classList.remove("light", "dark"); + root.classList.add(activeTheme); + + if (theme_mode === "system") { + appWindow.setTheme(null).catch(console.error); + } else { + appWindow.setTheme(activeTheme as Theme).catch(console.error); + } + + }, [mode, systemTheme, appWindow, theme_mode]); return {}; -}; +}; \ No newline at end of file diff --git a/src/index.css b/src/index.css index b265e9e6..875c37e9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,9 @@ @import "tailwindcss"; @import "tw-animate-css"; -@variant dark .dark &; +@theme { + --tailwind-darkMode: 'class'; +} @theme inline { --radius-sm: calc(var(--radius) - 4px); diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index b9bc197a..8a4fa5a1 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -21,7 +21,7 @@ import { useClashInfo } from "@/hooks/use-clash"; import { initGlobalLogService } from "@/services/global-log-service"; import { invoke } from "@tauri-apps/api/core"; import { showNotice } from "@/services/noticeService"; -import { NoticeManager } from "@/components/base/NoticeManager"; +import { Toaster } from "@/components/ui/sonner"; import { SidebarProvider, useSidebar } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/layout/sidebar"; import { useZoomControls } from "@/hooks/useZoomControls"; @@ -472,9 +472,9 @@ const Layout = () => { return ( - + ); diff --git a/src/services/noticeService.ts b/src/services/noticeService.ts index f5ef04ca..2e19d7bb 100644 --- a/src/services/noticeService.ts +++ b/src/services/noticeService.ts @@ -1,81 +1,25 @@ -import { ReactNode } from "react"; +import { toast } from "sonner"; -export interface NoticeItem { - id: number; - type: "success" | "error" | "info"; - message: ReactNode; - duration: number; - timerId?: ReturnType; -} +type NoticeType = 'success' | 'error' | 'info' | 'warning'; -type Listener = (notices: NoticeItem[]) => void; +export const showNotice = (type: NoticeType, message: string, duration?: number) => { + const options = duration ? { duration } : {}; -let nextId = 0; -let notices: NoticeItem[] = []; -const listeners: Set = new Set(); - -function notifyListeners() { - listeners.forEach((listener) => listener([...notices])); // Pass a copy -} - -// Shows a notification. - -export function showNotice( - type: "success" | "error" | "info", - message: ReactNode, - duration?: number, -): number { - const id = nextId++; - const effectiveDuration = - duration ?? (type === "error" ? 8000 : type === "info" ? 5000 : 3000); // Longer defaults - - const newNotice: NoticeItem = { - id, - type, - message, - duration: effectiveDuration, - }; - - // Auto-hide timer (only if duration is not null/0) - if (effectiveDuration > 0) { - newNotice.timerId = setTimeout(() => { - hideNotice(id); - }, effectiveDuration); + switch (type) { + case 'success': + toast.success(message, options); + break; + case 'error': + toast.error(message, options); + break; + case 'info': + toast.info(message, options); + break; + case 'warning': + toast.warning(message, options); + break; + default: + toast(message, options); + break; } - - notices = [...notices, newNotice]; - notifyListeners(); - return id; -} - -// Hides a specific notification by its ID. - -export function hideNotice(id: number) { - const notice = notices.find((n) => n.id === id); - if (notice?.timerId) { - clearTimeout(notice.timerId); // Clear timeout if manually closed - } - notices = notices.filter((n) => n.id !== id); - notifyListeners(); -} - -// Subscribes a listener function to notice state changes. - -export function subscribeNotices(listener: () => void) { - listeners.add(listener); - return () => { - listeners.delete(listener); - }; -} -export function getSnapshotNotices() { - return notices; -} - -// Function to clear all notices at once -export function clearAllNotices() { - notices.forEach((n) => { - if (n.timerId) clearTimeout(n.timerId); - }); - notices = []; - notifyListeners(); -} +}; \ No newline at end of file