diff --git a/.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch b/.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch deleted file mode 100644 index 1dc65fe13..000000000 --- a/.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/dist/esm/useLocalStorage/useLocalStorage.js b/dist/esm/useLocalStorage/useLocalStorage.js -index b0d584d4df29953551dfcf8febac002f89fa7acd..920ae5c52d28af73e3a892bdb935a7805a0f8224 100644 ---- a/dist/esm/useLocalStorage/useLocalStorage.js -+++ b/dist/esm/useLocalStorage/useLocalStorage.js -@@ -14,7 +14,7 @@ function useLocalStorage(key, initialValue) { - return initialValue; - } - }, [initialValue, key]); -- const [storedValue, setStoredValue] = useState(readValue); -+ const [storedValue, setStoredValue] = useState(initialValue); - const setValue = useEventCallback(value => { - if (typeof window === 'undefined') { - console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`); diff --git a/package.json b/package.json index 8fafb49d6..254287244 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,5 @@ "devDependencies": { "husky": "^8.0.1", "lint-staged": "^13.0.3" - }, - "resolutions": { - "usehooks-ts@^2.7.2": "patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch" } } diff --git a/packages/nextjs/app/debug/_components/DebugContracts.tsx b/packages/nextjs/app/debug/_components/DebugContracts.tsx index 195246ca0..82bc18a0c 100644 --- a/packages/nextjs/app/debug/_components/DebugContracts.tsx +++ b/packages/nextjs/app/debug/_components/DebugContracts.tsx @@ -15,6 +15,7 @@ export function DebugContracts() { const [selectedContract, setSelectedContract] = useLocalStorage( selectedContractStorageKey, contractNames[0], + { initializeWithValue: false }, ); useEffect(() => { diff --git a/packages/nextjs/app/layout.tsx b/packages/nextjs/app/layout.tsx index 824f4f3fb..c0efb0cbe 100644 --- a/packages/nextjs/app/layout.tsx +++ b/packages/nextjs/app/layout.tsx @@ -1,6 +1,7 @@ import "@rainbow-me/rainbowkit/styles.css"; import { Metadata } from "next"; import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders"; +import { ThemeProvider } from "~~/components/ThemeProvider"; import "~~/styles/globals.css"; const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL @@ -43,9 +44,11 @@ export const metadata: Metadata = { const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { return ( - + - {children} + + {children} + ); diff --git a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx index 1eb3bd3a9..56a82f6de 100644 --- a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx +++ b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx @@ -1,7 +1,8 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit"; +import { useTheme } from "next-themes"; import { Toaster } from "react-hot-toast"; import { WagmiConfig } from "wagmi"; import { Footer } from "~~/components/Footer"; @@ -9,7 +10,6 @@ import { Header } from "~~/components/Header"; import { BlockieAvatar } from "~~/components/scaffold-eth"; import { ProgressBar } from "~~/components/scaffold-eth/ProgressBar"; import { useNativeCurrencyPrice } from "~~/hooks/scaffold-eth"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; import { useGlobalState } from "~~/services/store/store"; import { wagmiConfig } from "~~/services/web3/wagmiConfig"; import { appChains } from "~~/services/web3/wagmiConnectors"; @@ -37,7 +37,13 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { }; export const ScaffoldEthAppWithProviders = ({ children }: { children: React.ReactNode }) => { - const { isDarkMode } = useDarkMode(); + const { resolvedTheme } = useTheme(); + const isDarkMode = resolvedTheme === "dark"; + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); return ( @@ -45,7 +51,7 @@ export const ScaffoldEthAppWithProviders = ({ children }: { children: React.Reac {children} diff --git a/packages/nextjs/components/SwitchTheme.tsx b/packages/nextjs/components/SwitchTheme.tsx index b53dd6c21..0c2a63819 100644 --- a/packages/nextjs/components/SwitchTheme.tsx +++ b/packages/nextjs/components/SwitchTheme.tsx @@ -1,34 +1,44 @@ "use client"; -import { useEffect } from "react"; -import { useIsMounted } from "usehooks-ts"; +import { useEffect, useState } from "react"; +import { useTheme } from "next-themes"; import { MoonIcon, SunIcon } from "@heroicons/react/24/outline"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; export const SwitchTheme = ({ className }: { className?: string }) => { - const { isDarkMode, toggle } = useDarkMode(); - const isMounted = useIsMounted(); + const { setTheme, resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + const isDarkMode = resolvedTheme === "dark"; + + const handleToggle = () => { + if (isDarkMode) { + setTheme("light"); + return; + } + setTheme("dark"); + }; useEffect(() => { - const body = document.body; - body.setAttribute("data-theme", isDarkMode ? "scaffoldEthDark" : "scaffoldEth"); - }, [isDarkMode]); + setMounted(true); + }, []); + + if (!mounted) return null; return ( -
+
- {isMounted() && ( + { - )} + }
); }; diff --git a/packages/nextjs/components/ThemeProvider.tsx b/packages/nextjs/components/ThemeProvider.tsx new file mode 100644 index 000000000..3a7798f26 --- /dev/null +++ b/packages/nextjs/components/ThemeProvider.tsx @@ -0,0 +1,9 @@ +"use client"; + +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ThemeProviderProps } from "next-themes/dist/types"; + +export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => { + return {children}; +}; diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx index b81fe1a3e..570cbb9a9 100644 --- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx +++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx @@ -1,7 +1,7 @@ +import { useTheme } from "next-themes"; import { useNetwork, useSwitchNetwork } from "wagmi"; import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid"; import { getNetworkColor } from "~~/hooks/scaffold-eth"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; import { getTargetNetworks } from "~~/utils/scaffold-eth"; const allowedNetworks = getTargetNetworks(); @@ -11,9 +11,10 @@ type NetworkOptionsProps = { }; export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => { - const { isDarkMode } = useDarkMode(); const { switchNetwork } = useSwitchNetwork(); const { chain } = useNetwork(); + const { resolvedTheme } = useTheme(); + const isDarkMode = resolvedTheme === "dark"; return ( <> diff --git a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts b/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts index f0f9129a7..549c7febd 100644 --- a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts +++ b/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts @@ -58,7 +58,9 @@ const getInitialConnector = ( */ export const useAutoConnect = (): void => { const wagmiWalletValue = useReadLocalStorage(WAGMI_WALLET_STORAGE_KEY); - const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? ""); + const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? "", { + initializeWithValue: false, + }); const connectState = useConnect(); useAccount({ onConnect({ connector }) { diff --git a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts index bb757061d..0538fc35e 100644 --- a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts +++ b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts @@ -57,7 +57,9 @@ type BurnerAccount = { * Creates a burner wallet */ export const useBurnerWallet = (): BurnerAccount => { - const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPrivateKey); + const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPrivateKey, { + initializeWithValue: false, + }); const publicClient = usePublicClient(); const [walletClient, setWalletClient] = useState>(); diff --git a/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts b/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts deleted file mode 100644 index c0ac96ef0..000000000 --- a/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useEffect, useState } from "react"; -import { useLocalStorage, useMediaQuery, useReadLocalStorage } from "usehooks-ts"; - -const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"; - -interface UseDarkModeOutput { - isDarkMode: boolean; - toggle: () => void; - enable: () => void; - disable: () => void; -} - -const LOCAL_STORAGE_THEME_KEY = "usehooks-ts-dark-mode"; - -export function useDarkMode(defaultValue?: boolean): UseDarkModeOutput { - const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY); - const [prevIsDarkOs, setPrevIsDarkOs] = useState(isDarkOS); - - const initialStorageValue: boolean | null = useReadLocalStorage(LOCAL_STORAGE_THEME_KEY); - const [isDarkMode, setIsDarkMode] = useLocalStorage(LOCAL_STORAGE_THEME_KEY, Boolean(defaultValue)); - - // set if no init value - useEffect(() => { - if (initialStorageValue === null) { - setIsDarkMode(defaultValue || isDarkOS); - } - }, [defaultValue, isDarkOS, setIsDarkMode, initialStorageValue]); - - // update on os color change - useEffect(() => { - if (isDarkOS !== prevIsDarkOs) { - setPrevIsDarkOs(isDarkOS); - setIsDarkMode(isDarkOS); - } - }, [isDarkOS, prevIsDarkOs, setIsDarkMode]); - - return { - isDarkMode, - toggle: () => setIsDarkMode(prev => !prev), - enable: () => setIsDarkMode(true), - disable: () => setIsDarkMode(false), - }; -} diff --git a/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts b/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts index 4f83f3f23..ec9c77060 100644 --- a/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts +++ b/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts @@ -1,5 +1,5 @@ -import { useDarkMode } from "./useDarkMode"; import { useTargetNetwork } from "./useTargetNetwork"; +import { useTheme } from "next-themes"; import { ChainWithAttributes } from "~~/utils/scaffold-eth"; export const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"]; @@ -13,8 +13,10 @@ export function getNetworkColor(network: ChainWithAttributes, isDarkMode: boolea * Gets the color of the target network */ export const useNetworkColor = () => { - const { isDarkMode } = useDarkMode(); + const { resolvedTheme } = useTheme(); const { targetNetwork } = useTargetNetwork(); + const isDarkMode = resolvedTheme === "dark"; + return getNetworkColor(targetNetwork, isDarkMode); }; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 45348b2fd..49c70229f 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -22,6 +22,7 @@ "blo": "^1.0.1", "daisyui": "^4.4.19", "next": "^14.0.4", + "next-themes": "^0.2.1", "nprogress": "^0.2.0", "qrcode.react": "^3.1.0", "react": "^18.2.0", @@ -29,7 +30,7 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.0", "use-debounce": "^8.0.4", - "usehooks-ts": "^2.7.2", + "usehooks-ts": "^2.13.0", "viem": "1.19.9", "wagmi": "1.4.12", "zustand": "^4.1.2" diff --git a/packages/nextjs/tailwind.config.js b/packages/nextjs/tailwind.config.js index e1ca91dd9..d0e358b21 100644 --- a/packages/nextjs/tailwind.config.js +++ b/packages/nextjs/tailwind.config.js @@ -2,12 +2,12 @@ module.exports = { content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"], plugins: [require("daisyui")], - darkTheme: "scaffoldEthDark", + darkTheme: "dark", // DaisyUI theme colors daisyui: { themes: [ { - scaffoldEth: { + light: { primary: "#93BBFB", "primary-content": "#212638", secondary: "#DAE8FF", @@ -39,7 +39,7 @@ module.exports = { }, }, { - scaffoldEthDark: { + dark: { primary: "#212638", "primary-content": "#F9FBFF", secondary: "#323f61", diff --git a/yarn.lock b/yarn.lock index 39775567f..64887abe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1877,6 +1877,7 @@ __metadata: eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 next: ^14.0.4 + next-themes: ^0.2.1 nprogress: ^0.2.0 postcss: ^8.4.16 prettier: ^2.8.4 @@ -1889,7 +1890,7 @@ __metadata: type-fest: ^4.6.0 typescript: ^5.1.6 use-debounce: ^8.0.4 - usehooks-ts: ^2.7.2 + usehooks-ts: ^2.13.0 vercel: ^32.4.1 viem: 1.19.9 wagmi: 1.4.12 @@ -9562,6 +9563,13 @@ __metadata: languageName: node linkType: hard +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: a3f527d22c548f43ae31c861ada88b2637eb48ac6aa3eb56e82d44917971b8aa96fbb37aa60efea674dc4ee8c42074f90f7b1f772e9db375435f6c83a19b3bc6 + languageName: node + linkType: hard + "lodash.defaults@npm:^4.2.0": version: 4.2.0 resolution: "lodash.defaults@npm:4.2.0" @@ -10307,6 +10315,17 @@ __metadata: languageName: node linkType: hard +"next-themes@npm:^0.2.1": + version: 0.2.1 + resolution: "next-themes@npm:0.2.1" + peerDependencies: + next: "*" + react: "*" + react-dom: "*" + checksum: ebc248b956138e73436c4ed0a0f0a877a0a48a33156db577029b8b8469e48b5c777d61abf2baeb75953378febea74e067a4869ff90b4a3e94fce123289b862ba + languageName: node + linkType: hard + "next@npm:^14.0.4": version: 14.0.4 resolution: "next@npm:14.0.4" @@ -13996,23 +14015,14 @@ __metadata: languageName: node linkType: hard -"usehooks-ts@npm:^2.7.2": - version: 2.9.1 - resolution: "usehooks-ts@npm:2.9.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 36f1e4142ce23bc019b81d2e93aefd7f2c350abcf255598c21627114a69a2f2f116b35dc3a353375f09c6e4c9b704a04f104e3d10e98280545c097feca66c30a - languageName: node - linkType: hard - -"usehooks-ts@patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch::locator=se-2%40workspace%3A.": - version: 2.9.1 - resolution: "usehooks-ts@patch:usehooks-ts@npm%3A2.9.1#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch::version=2.9.1&hash=68bde7&locator=se-2%40workspace%3A." +"usehooks-ts@npm:^2.13.0": + version: 2.13.0 + resolution: "usehooks-ts@npm:2.13.0" + dependencies: + lodash.debounce: ^4.0.8 peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 6807ae0e6ffd158790e5018d3a322efd0fe97853b6b83751725e5e81d0962cb6230ca65a24341bb5cee3f1bc330721611440c399fc950723153c5cc61eb74b04 + react: ^16.8.0 || ^17 || ^18 + checksum: ad07930e1b5c70392603eb8b3f199f44349c75406fe31013f79b0fb7fdece59f47f8dba09b6f1fafaa00d68f43240dbb13cdc1afb89b647f1d53504599a51ca0 languageName: node linkType: hard