From 2abead43943bdea1228193c6618614bb73ee00ad Mon Sep 17 00:00:00 2001 From: Bayge <71931113+af-afk@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:55:49 +0900 Subject: [PATCH 1/2] feat(web): add dark mode toggle --- web/src/app/globals.css | 61 +++++++++ web/src/app/layout.tsx | 5 +- web/src/components/cardShadow.tsx | 2 +- web/src/components/header.tsx | 20 ++- web/src/components/mobileMenu.tsx | 2 +- web/src/components/navMenu.tsx | 2 +- web/src/components/pointsMenuButton.tsx | 2 +- web/src/components/theme/themeScript.tsx | 25 ++++ .../components/theme/themeToggleButton.tsx | 31 +++++ web/src/components/themed/modal.tsx | 2 +- web/src/providers/index.tsx | 31 +++-- web/src/providers/theme.tsx | 124 ++++++++++++++++++ web/tailwind.config.ts | 3 + 13 files changed, 288 insertions(+), 22 deletions(-) create mode 100644 web/src/components/theme/themeScript.tsx create mode 100644 web/src/components/theme/themeToggleButton.tsx create mode 100644 web/src/providers/theme.tsx diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 43183948..91ca97e5 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -2,6 +2,10 @@ @tailwind components; @tailwind utilities; +:root { + color-scheme: light dark; +} + section { flex-grow: 1; } @@ -10,6 +14,10 @@ section { background-color: #cccccc; /* Background color of the track */ } +.dark ::-webkit-scrollbar-track { + background-color: #1f1f1f; +} + /* Scrollbar Thumb */ ::-webkit-scrollbar-thumb { background-color: #999999; /* Background color of the thumb */ @@ -17,6 +25,11 @@ section { border: 2px solid #cccccc; } +.dark ::-webkit-scrollbar-thumb { + background-color: #4d4d4d; + border-color: #1f1f1f; +} + /* Scrollbar Width */ ::-webkit-scrollbar { width: 7px; /* Set scrollbar width */ @@ -48,6 +61,10 @@ input[type="range"]::-webkit-slider-runnable-track { background-color: #0c0c0c; height: 9px; } + +.dark input[type="range"]::-webkit-slider-runnable-track { + background-color: #f5f5f5; +} input[type="range"]:disabled { opacity: 0.5; } @@ -72,6 +89,14 @@ input[type="range"]::-webkit-slider-thumb { box-shadow: -2px -2px 0 rgba(68, 68, 68, 0.2) inset; } +.dark input[type="range"]::-webkit-slider-thumb { + border: 1px solid #f5f5f5; + background: + url("/icons/horizontal-arrows.svg") no-repeat center, + #1a1a1a; + box-shadow: -2px -2px 0 rgba(0, 0, 0, 0.35) inset; +} + input[type="range"]:focus::-webkit-slider-thumb { border: 1px solid #0c0c0c; border-radius: 3px; @@ -84,6 +109,10 @@ input[type="range"]::-moz-range-track { height: 9px; } +.dark input[type="range"]::-moz-range-track { + background-color: #f5f5f5; +} + /* slider thumb */ input[type="range"]::-moz-range-thumb { border: none; /*Removes extra border that FF applies*/ @@ -100,6 +129,14 @@ input[type="range"]::-moz-range-thumb { box-shadow: -2px -2px 0 rgba(68, 68, 68, 0.2) inset; } +.dark input[type="range"]::-moz-range-thumb { + border: 1px solid #f5f5f5; + background: + url("/icons/horizontal-arrows.svg") no-repeat center, + #1a1a1a; + box-shadow: -2px -2px 0 rgba(0, 0, 0, 0.35) inset; +} + input[type="range"]:focus::-moz-range-thumb { border: 1px solid #0c0c0c; border-radius: 3px; @@ -128,6 +165,12 @@ div[id^="radix"][role="dialog"][data-state="open"] { @apply font-chicago; } +.dark div[id^="radix"][role="dialog"][data-state="open"] { + background-color: #1a1a1a; + border-color: #444444; + box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.6); +} + div[id^="radix"][role="dialog"][data-state="open"] button { border-color: #0c0c0c; border: 1px solid #0c0c0c; @@ -138,6 +181,13 @@ div[id^="radix"][role="dialog"][data-state="open"] button { 2px 2px 0 rgba(0, 0, 0, 0.25); } +.dark div[id^="radix"][role="dialog"][data-state="open"] button { + border-color: #f5f5f5; + box-shadow: + -2px -2px 0 rgba(255, 255, 255, 0.1) inset, + 2px 2px 0 rgba(0, 0, 0, 0.4); +} + div[id^="radix"][role="dialog"][data-state="open"] input { border-color: #0c0c0c; border: 1px solid #0c0c0c !important; @@ -148,10 +198,21 @@ div[id^="radix"][role="dialog"][data-state="open"] input { margin-right: 8px; } +.dark div[id^="radix"][role="dialog"][data-state="open"] input { + border-color: #f5f5f5 !important; + box-shadow: + -2px -2px 0 rgba(255, 255, 255, 0.08) inset, + 2px 2px 0 rgba(0, 0, 0, 0.4); +} + div[style="pointer-events: auto;"][data-state="open"] { @apply bg-9blueLight/70; } +.dark div[style="pointer-events: auto;"][data-state="open"] { + background-color: rgba(0, 0, 0, 0.6); +} + div[id^="radix"][role="dialog"][data-state="open"] div[data-error] { @apply border-none shadow-none focus:outline-none; } diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index de39933f..1c8522ab 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -6,6 +6,7 @@ import GoogleAnalytics from "@/components/googleAnalytics"; import appConfig from "@/config"; import CustomToaster from "@/components/customToaster"; import CookieBanner from "@/components/cookieBanner"; +import ThemeScript from "@/components/theme/themeScript"; import Providers from "@/providers"; const chicago = localFont({ @@ -71,8 +72,10 @@ export default async function RootLayout({ arial.className, arial.variable, ])} + suppressHydrationWarning > - + + {children} diff --git a/web/src/components/cardShadow.tsx b/web/src/components/cardShadow.tsx index e8dc4689..7e2de55d 100644 --- a/web/src/components/cardShadow.tsx +++ b/web/src/components/cardShadow.tsx @@ -12,7 +12,7 @@ export default function ShadowCard({
{children} diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index b40d6c6b..1efb45b1 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -1,6 +1,9 @@ +"use client"; + import ConnectButton from "@/components/connectButton"; import NavigationMenu from "@/components/navMenu"; import Link from "next/link"; +import { combineClass } from "@/utils/combineClass"; import HeaderLogo from "./headerLogo"; import CreateCampaingButton from "./createCampaign/createCampaignHeaderButton"; import DisclaimerButton from "./disclaimerButton"; @@ -9,10 +12,16 @@ import ReferralButton from "./referral/referralButton"; import MobileMenu from "./mobileMenu"; import SimpleModeButton from "./simple/simpleModeButton"; import AdvancedModeButton from "./advanced/advancedModeButton"; +import ThemeToggleButton from "./theme/themeToggleButton"; export default function Header({ simple = false }: { simple?: boolean }) { return ( -
+
{simple ? : }
-
+
+ {simple ? : } diff --git a/web/src/components/mobileMenu.tsx b/web/src/components/mobileMenu.tsx index 53026288..0b4ada5b 100644 --- a/web/src/components/mobileMenu.tsx +++ b/web/src/components/mobileMenu.tsx @@ -18,7 +18,7 @@ export default function MobileMenu({ simple = false }: { simple?: boolean }) { }, [pathname]); return (
setIsMobileMenuOpen(true)} > menu diff --git a/web/src/components/navMenu.tsx b/web/src/components/navMenu.tsx index c2a8230e..2945034b 100644 --- a/web/src/components/navMenu.tsx +++ b/web/src/components/navMenu.tsx @@ -28,7 +28,7 @@ export default function NavigationMenu({ key={item.page} href={item.page} className={combineClass( - "font-chicago text-neutral-800 hover:underline", + "font-chicago text-neutral-800 transition-colors hover:underline dark:text-9gray", item.disabled && "pointer-events-none opacity-30", )} > diff --git a/web/src/components/pointsMenuButton.tsx b/web/src/components/pointsMenuButton.tsx index 566993ab..3094001a 100644 --- a/web/src/components/pointsMenuButton.tsx +++ b/web/src/components/pointsMenuButton.tsx @@ -7,7 +7,7 @@ export default function PointMenuButton() { return ( - + Points { + try { + const storageKey = "9lives-theme"; + const storedTheme = window.localStorage.getItem(storageKey); + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = storedTheme === "light" || storedTheme === "dark" + ? storedTheme + : prefersDark + ? "dark" + : "light"; + + if (theme === "dark") { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } + document.documentElement.dataset.theme = theme; + } catch (error) { + console.warn("Unable to hydrate theme", error); + } +})();`; + +export default function ThemeScript() { + return