From 254f6c4e9abdd0224aa41999122ba66402c778ce Mon Sep 17 00:00:00 2001
From: AliMahmoudDev <123aliactionx5@gmail.com>
Date: Tue, 14 Jul 2026 16:27:06 +0000
Subject: [PATCH] feat: add dark mode toggle to repository dashboard
Add light/dark/system theme toggle with CSS custom properties for theming.
Defaults to system preference, persists choice in localStorage, and
applies .dark class for Tailwind CSS dark mode.
- New ThemeToggle component with 3 modes (light/dark/system)
- CSS custom properties for card backgrounds, borders, text colors
- Updated dashboard layout to use CSS variables for theming
- Smooth transition between themes
- Respects prefers-color-scheme media query
Closes #36
---
apps/web/src/app/dashboard/layout.tsx | 14 ++--
apps/web/src/app/globals.css | 23 ++++--
apps/web/src/components/theme-toggle.tsx | 101 +++++++++++++++++++++++
3 files changed, 128 insertions(+), 10 deletions(-)
create mode 100644 apps/web/src/components/theme-toggle.tsx
diff --git a/apps/web/src/app/dashboard/layout.tsx b/apps/web/src/app/dashboard/layout.tsx
index 0e42402..7f91fc5 100644
--- a/apps/web/src/app/dashboard/layout.tsx
+++ b/apps/web/src/app/dashboard/layout.tsx
@@ -1,20 +1,24 @@
import { UserButton } from "@clerk/nextjs";
import Link from "next/link";
+import { ThemeToggle } from "@/components/theme-toggle";
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
-
-
- ForgeLens
+
+
+ ForgeLens
-
+
+
+
+
{children}
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index a2dc41e..2608917 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -3,6 +3,13 @@
:root {
--background: #ffffff;
--foreground: #171717;
+ --card-bg: #ffffff;
+ --card-border: #e5e7eb;
+ --text-primary: #111827;
+ --text-secondary: #6b7280;
+ --text-muted: #9ca3af;
+ --surface: #f9fafb;
+ --surface-hover: #f3f4f6;
}
@theme inline {
@@ -12,15 +19,21 @@
--font-mono: var(--font-geist-mono);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+.dark {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ --card-bg: #1a1a2e;
+ --card-border: #2d2d44;
+ --text-primary: #f9fafb;
+ --text-secondary: #d1d5db;
+ --text-muted: #9ca3af;
+ --surface: #111827;
+ --surface-hover: #1f2937;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
+ transition: background-color 0.2s ease, color 0.2s ease;
}
diff --git a/apps/web/src/components/theme-toggle.tsx b/apps/web/src/components/theme-toggle.tsx
new file mode 100644
index 0000000..284d57e
--- /dev/null
+++ b/apps/web/src/components/theme-toggle.tsx
@@ -0,0 +1,101 @@
+"use client";
+
+import { useState, useEffect } from "react";
+
+type Theme = "light" | "dark" | "system";
+
+function getSystemPreference(): "light" | "dark" {
+ if (typeof window === "undefined") return "light";
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+}
+
+function applyTheme(theme: Theme) {
+ const resolved = theme === "system" ? getSystemPreference() : theme;
+ const root = document.documentElement;
+ if (resolved === "dark") {
+ root.classList.add("dark");
+ } else {
+ root.classList.remove("dark");
+ }
+}
+
+export function ThemeToggle() {
+ const [theme, setTheme] = useState("system");
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ const stored = localStorage.getItem("forgelens-theme") as Theme | null;
+ if (stored && ["light", "dark", "system"].includes(stored)) {
+ setTheme(stored);
+ applyTheme(stored);
+ } else {
+ setTheme("system");
+ applyTheme("system");
+ }
+ setMounted(true);
+ }, []);
+
+ useEffect(() => {
+ if (!mounted) return;
+
+ if (theme === "system") {
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
+ const handler = () => applyTheme("system");
+ mq.addEventListener("change", handler);
+ applyTheme("system");
+ return () => mq.removeEventListener("change", handler);
+ } else {
+ applyTheme(theme);
+ }
+ }, [theme, mounted]);
+
+ const cycleTheme = () => {
+ const themes: Theme[] = ["light", "dark", "system"];
+ const next = themes[(themes.indexOf(theme) + 1) % themes.length];
+ setTheme(next);
+ localStorage.setItem("forgelens-theme", next);
+ };
+
+ if (!mounted) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}