From 40e2040fb05717283d0ee2b7d0a6b3315abecf73 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Tue, 12 May 2026 16:07:40 +0700 Subject: [PATCH] feat: refactor theme management and remove next-themes dependency - Replaced the next-themes package with a custom ThemeProvider component for enhanced theme management. - Implemented local storage handling for theme persistence and system theme detection. - Updated the Toaster component to utilize the new theme context. - Removed unused next-themes references from package.json and package-lock.json. --- components/app-sidebar.tsx | 4 +- components/theme-provider.tsx | 175 ++++++++++++++++++++++++++++++---- components/ui/sonner.tsx | 2 +- package-lock.json | 27 +----- package.json | 1 - 5 files changed, 159 insertions(+), 50 deletions(-) diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 947e739..d62599d 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -209,10 +209,10 @@ export function AppSidebar({ ...props }: React.ComponentProps) { title: it.title, url: it.url, icon: it.icon, - badge: badgeFor(it.url), + badge: mounted ? badgeFor(it.url) : undefined, })), })) - }, [mode, backendStatus, liveCounts, mockBadges, evo.enabled, evo.snapshot]) + }, [mounted, mode, backendStatus, liveCounts, mockBadges, evo.enabled, evo.snapshot]) const isDark = (resolvedTheme ?? "dark") === "dark" diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx index 93212c2..ca5074a 100644 --- a/components/theme-provider.tsx +++ b/components/theme-provider.tsx @@ -1,25 +1,160 @@ "use client" -import { ThemeProvider as NextThemesProvider } from "next-themes" -import type { ComponentProps } from "react" +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react" +import { useServerInsertedHTML } from "next/navigation" -/** Обёртка над next-themes: тот же storageKey `rl-theme`, класс на ``. */ -export function ThemeProvider({ - children, - ...props -}: ComponentProps) { - return ( - - {children} - - ) +const STORAGE_KEY = "rl-theme" +const MEDIA_QUERY = "(prefers-color-scheme: dark)" +const THEMES = ["light", "dark", "system"] as const + +type Theme = (typeof THEMES)[number] +type ResolvedTheme = "light" | "dark" + +const THEME_INIT_SCRIPT = `(function(){try{var k="${STORAGE_KEY}",d="dark",e=document.documentElement,t=localStorage.getItem(k)||d,s=function(){return window.matchMedia("${MEDIA_QUERY}").matches?"dark":"light"}();if(t==="system")t=s;e.classList.remove("light","dark");e.classList.add(t);e.style.colorScheme=t}catch(n){}})()` + +type ThemeContextValue = { + theme: Theme | undefined + setTheme: (theme: Theme | ((prev: Theme) => Theme)) => void + resolvedTheme: ResolvedTheme | undefined + systemTheme: ResolvedTheme | undefined + themes: readonly Theme[] } -export { useTheme } from "next-themes" +const ThemeContext = createContext(undefined) + +type ThemeProviderProps = { + children: ReactNode + defaultTheme?: Theme + enableSystem?: boolean + disableTransitionOnChange?: boolean +} + +function readStoredTheme(defaultTheme: Theme, enableSystem: boolean): Theme { + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored === "light" || stored === "dark") return stored + if (enableSystem && stored === "system") return stored + } catch { + // ignore + } + return defaultTheme +} + +function resolveTheme(theme: Theme, systemTheme: ResolvedTheme): ResolvedTheme { + return theme === "system" ? systemTheme : theme +} + +function applyThemeClass(resolved: ResolvedTheme, disableTransitionOnChange: boolean) { + const root = document.documentElement + if (disableTransitionOnChange) { + const style = document.createElement("style") + style.appendChild( + document.createTextNode( + "*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}", + ), + ) + document.head.appendChild(style) + window.getComputedStyle(document.body) + setTimeout(() => { + document.head.removeChild(style) + }, 1) + } + root.classList.remove("light", "dark") + root.classList.add(resolved) + root.style.colorScheme = resolved +} + +export function ThemeProvider({ + children, + defaultTheme = "dark", + enableSystem = true, + disableTransitionOnChange = false, +}: ThemeProviderProps) { + useServerInsertedHTML(() => ( +