diff --git a/dashboard/web/src/app/globals.css b/dashboard/web/src/app/globals.css index 5705f97..ac6a154 100644 --- a/dashboard/web/src/app/globals.css +++ b/dashboard/web/src/app/globals.css @@ -5,6 +5,8 @@ html, body { font-family: var(--ml-font-body), system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: var(--bl-bg-canvas); + color: var(--bl-text-primary); } @media (max-width: 767px) { @@ -12,3 +14,97 @@ body { padding-top: 3.5rem; } } + +/* + Legacy pages still use Tailwind gray/white utilities directly. Keep them + theme-aware while those pages are migrated to shared @bytelyst/ui primitives. +*/ +[data-theme="dark"] .bg-white { + background-color: var(--bl-surface-card) !important; +} + +[data-theme="dark"] .bg-gray-50, +[data-theme="dark"] .bg-gray-100 { + background-color: var(--bl-bg-canvas) !important; +} + +[data-theme="dark"] .bg-gray-200, +[data-theme="dark"] .bg-gray-300 { + background-color: var(--bl-surface-muted) !important; +} + +[data-theme="dark"] .bg-gray-700, +[data-theme="dark"] .bg-gray-800 { + background-color: var(--bl-surface-card) !important; +} + +[data-theme="dark"] .bg-blue-50, +[data-theme="dark"] .bg-green-50, +[data-theme="dark"] .bg-yellow-50, +[data-theme="dark"] .bg-red-50, +[data-theme="dark"] .bg-purple-50, +[data-theme="dark"] .bg-red-50\/40 { + background-color: var(--bl-surface-muted) !important; +} + +[data-theme="dark"] .text-gray-900, +[data-theme="dark"] .text-gray-800, +[data-theme="dark"] .text-gray-700 { + color: var(--bl-text-primary) !important; +} + +[data-theme="dark"] .text-gray-600, +[data-theme="dark"] .text-gray-500, +[data-theme="dark"] .text-gray-400, +[data-theme="dark"] .text-gray-300 { + color: var(--bl-text-secondary) !important; +} + +[data-theme="dark"] .border-gray-100, +[data-theme="dark"] .border-gray-200, +[data-theme="dark"] .border-gray-300, +[data-theme="dark"] .border-gray-600 { + border-color: var(--bl-border) !important; +} + +[data-theme="dark"] .border-blue-200, +[data-theme="dark"] .border-green-200, +[data-theme="dark"] .border-yellow-200, +[data-theme="dark"] .border-red-200, +[data-theme="dark"] .border-purple-200 { + border-color: var(--bl-border) !important; +} + +[data-theme="dark"] input, +[data-theme="dark"] select, +[data-theme="dark"] textarea { + background-color: var(--bl-input) !important; + border-color: var(--bl-border) !important; + color: var(--bl-text-primary) !important; +} + +[data-theme="dark"] input::placeholder, +[data-theme="dark"] textarea::placeholder { + color: var(--bl-text-tertiary) !important; +} + +[data-theme="dark"] .shadow, +[data-theme="dark"] .shadow-md, +[data-theme="dark"] .shadow-lg, +[data-theme="dark"] .shadow-xl, +[data-theme="dark"] .shadow-sm { + box-shadow: var(--bl-shadow-sm) !important; +} + +@media (hover: hover) { + [data-theme="dark"] .hover\:bg-gray-50:hover, + [data-theme="dark"] .hover\:bg-gray-100:hover, + [data-theme="dark"] .hover\:bg-gray-200:hover { + background-color: var(--bl-surface-muted) !important; + } + + [data-theme="dark"] .hover\:text-gray-600:hover, + [data-theme="dark"] .hover\:text-gray-700:hover { + color: var(--bl-text-primary) !important; + } +} diff --git a/dashboard/web/src/components/sidebar-nav.tsx b/dashboard/web/src/components/sidebar-nav.tsx index 3a82183..332311c 100644 --- a/dashboard/web/src/components/sidebar-nav.tsx +++ b/dashboard/web/src/components/sidebar-nav.tsx @@ -23,6 +23,7 @@ import { useAuth } from '@/lib/auth'; const THEME_STORAGE_KEY = 'bytelyst.theme.v1'; const SIDEBAR_STORAGE_KEY = 'bytelyst.devops.sidebar.collapsed.v1'; +const THEME_EVENT = 'bytelyst-theme-change'; type Theme = 'light' | 'dark'; const navItems = [ @@ -46,6 +47,14 @@ export function SidebarNav() { const [theme, setTheme] = useState('dark'); useEffect(() => { + function handleThemeChange(event: Event) { + const next = (event as CustomEvent).detail; + if (next === 'light' || next === 'dark') { + setTheme(next); + applyTheme(next); + } + } + try { const savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY) as Theme | null; const legacyTheme = window.localStorage.getItem('theme') as Theme | null; @@ -61,6 +70,9 @@ export function SidebarNav() { } catch { applyTheme('dark'); } + + window.addEventListener(THEME_EVENT, handleThemeChange); + return () => window.removeEventListener(THEME_EVENT, handleThemeChange); }, []); function applyTheme(next: Theme) { @@ -75,6 +87,7 @@ export function SidebarNav() { try { window.localStorage.setItem(THEME_STORAGE_KEY, next); window.localStorage.setItem('theme', next); + window.dispatchEvent(new CustomEvent(THEME_EVENT, { detail: next })); } catch { // localStorage can be unavailable in private browsing. } diff --git a/dashboard/web/src/components/theme-toggle.tsx b/dashboard/web/src/components/theme-toggle.tsx index 0a63b21..2ccaa17 100644 --- a/dashboard/web/src/components/theme-toggle.tsx +++ b/dashboard/web/src/components/theme-toggle.tsx @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/Primitives'; // `app/layout.tsx`. const STORAGE_KEY = 'bytelyst.theme.v1'; +const THEME_EVENT = 'bytelyst-theme-change'; type Theme = 'dark' | 'light'; const THEMES: Theme[] = ['dark', 'light']; @@ -30,6 +31,7 @@ function readPersisted(): Theme { function applyTheme(theme: Theme) { if (typeof document === 'undefined') return; document.documentElement.setAttribute('data-theme', theme); + document.documentElement.classList.toggle('dark', theme === 'dark'); } export function ThemeToggle() { @@ -37,14 +39,31 @@ export function ThemeToggle() { const [theme, setTheme] = useState('dark'); useEffect(() => { - setTheme(readPersisted()); + const persisted = readPersisted(); + setTheme(persisted); + applyTheme(persisted); + + function handleThemeChange(event: Event) { + const next = (event as CustomEvent).detail; + if ((THEMES as string[]).includes(next)) { + setTheme(next); + applyTheme(next); + } + } + + window.addEventListener(THEME_EVENT, handleThemeChange); + return () => window.removeEventListener(THEME_EVENT, handleThemeChange); }, []); const toggle = useCallback(() => { setTheme((prev) => { const next = prev === 'dark' ? 'light' : 'dark'; try { - if (typeof window !== 'undefined') window.localStorage.setItem(STORAGE_KEY, next); + if (typeof window !== 'undefined') { + window.localStorage.setItem(STORAGE_KEY, next); + window.localStorage.setItem('theme', next); + window.dispatchEvent(new CustomEvent(THEME_EVENT, { detail: next })); + } } catch { // ignore }