Skip to content

[Frontend] Implement Dark/Light Mode Toggle with Persistent Theme Preference #171

Description

@devJaja

🎯 Objective

Implement a dark/light mode toggle with persistent theme preference, resolving the hardcoded light-mode colors in dashboard and wallet components.


📁 Files to Create

Action File Path Description
Create frontend/src/context/ThemeContext.tsx Theme provider with dark/light/system modes
Create frontend/src/hooks/useTheme.ts Hook for theme access and toggle

📁 Files to Modify

Action File Path Description
Modify frontend/src/App.tsx Wrap with ThemeProvider
Modify frontend/src/components/layout/TopNav.tsx Add theme toggle button
Modify frontend/src/styles/global.css Add light theme CSS variables
Modify frontend/src/components/dashboard/KpiCard.module.css Use CSS variables
Modify frontend/src/components/dashboard/NetworkHealthBadge.module.css Use CSS variables
Modify frontend/src/components/dashboard/RecentTasksTable.module.css Use CSS variables
Modify frontend/src/components/wallet/SendXLMForm.module.css Use CSS variables
Modify frontend/src/components/wallet/TransactionTable.module.css Use CSS variables
Modify frontend/src/pages/WalletPage.module.css Use CSS variables
Modify frontend/src/components/agents/TaskSubmissionForm.tsx Use CSS variables

🔍 Current State

The app is dark-mode only. Many components use hardcoded light-mode colors that clash with the dark layout shell (see issue #152).


✅ Expected Implementation

1. Create ThemeContext.tsx

import { createContext, useContext, useState, useEffect } from 'react';

type Theme = 'dark' | 'light' | 'system';

interface ThemeContextValue {
  theme: Theme;
  resolvedTheme: 'dark' | 'light';
  setTheme: (theme: Theme) => void;
}

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setThemeState] = useState<Theme>(() => {
    return (localStorage.getItem('theme') as Theme) || 'system';
  });

  const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('dark');

  useEffect(() => {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

    const resolve = () => {
      if (theme === 'system') {
        setResolvedTheme(mediaQuery.matches ? 'dark' : 'light');
      } else {
        setResolvedTheme(theme);
      }
    };

    resolve();
    mediaQuery.addEventListener('change', resolve);
    return () => mediaQuery.removeEventListener('change', resolve);
  }, [theme]);

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', resolvedTheme);
    localStorage.setItem('theme', theme);
  }, [theme, resolvedTheme]);

  const setTheme = (t: Theme) => setThemeState(t);

  return (
    <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
}

2. Update global.css — Add Light Theme Variables

:root[data-theme="dark"] {
  --bg-primary: #0A0E14;
  --bg-secondary: #1A1F2E;
  --text-primary: #E2E8F0;
  --text-secondary: #94A3B8;
  --border-color: #2D3748;
}

:root[data-theme="light"] {
  --bg-primary: #FFFFFF;
  --bg-secondary: #F8FAFC;
  --text-primary: #0F172A;
  --text-secondary: #475569;
  --border-color: #E2E8F0;
}

3. Add Toggle Button (TopNav.tsx)

import { useTheme } from '../../context/ThemeContext';
import { Sun, Moon, Monitor } from 'lucide-react';

function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  const cycle = () => {
    const next = theme === 'dark' ? 'light' : theme === 'light' ? 'system' : 'dark';
    setTheme(next);
  };

  return (
    <button onClick={cycle} aria-label="Toggle theme">
      {theme === 'dark' && <Moon size={18} />}
      {theme === 'light' && <Sun size={18} />}
      {theme === 'system' && <Monitor size={18} />}
    </button>
  );
}

4. Update All Components (from issue #152)

Replace all hardcoded colors with CSS variable references in:

  • KpiCard.module.css
  • NetworkHealthBadge.module.css
  • RecentTasksTable.module.css
  • SendXLMForm.module.css
  • TransactionTable.module.css
  • WalletPage.module.css
  • TaskSubmissionForm.tsx (inline styles)

📁 Reference Files

File Path Purpose
frontend/src/App.tsx Wrap with ThemeProvider
frontend/src/components/layout/TopNav.tsx Add toggle button
frontend/src/styles/global.css CSS variables
frontend/src/components/common/ErrorBoundary.tsx Reference for context pattern
All CSS module files listed above Replace hardcoded colors

✅ Acceptance Criteria

  • Create ThemeContext.tsx with dark/light/system modes
  • Create useTheme() hook
  • Add light theme CSS variables to global.css
  • Add theme toggle button in TopNav.tsx
  • Persist theme preference in localStorage
  • Respect prefers-color-scheme for system mode
  • Update all 7 CSS module files to use CSS variables
  • Update TaskSubmissionForm.tsx inline styles
  • Add smooth 200ms transition animation between themes
  • Test on Chrome, Firefox, Safari
  • Test all three modes: dark, light, system
  • Verify theme persists across page refreshes

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions