diff --git a/frontend/src/index.css b/frontend/src/index.css index 31549e3b4..a74e62f3c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3855,3 +3855,19 @@ button, white-space: nowrap; } } + +/* ============================================ + Light Theme Overrides + ============================================ */ +.theme-light { + --bg-primary: #f8f9fa; + --bg-secondary: #ffffff; + --bg-tertiary: #f1f3f5; + --bg-elevated: #e9ecef; + --accent-silver: #6c757d; + --accent-silver-dim: #adb5bd; + --accent-silver-bright: #212529; + --text-primary: #212529; + --text-secondary: #495057; + --text-muted: #6c757d; +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 3b803a0d2..b5fe353cd 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ export default { + darkMode: 'class', content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", @@ -7,6 +8,15 @@ export default { theme: { extend: { colors: { + 'primary': 'var(--bg-primary)', + 'secondary': 'var(--bg-secondary)', + 'bg-primary': 'var(--bg-primary)', + 'bg-secondary': 'var(--bg-secondary)', + 'bg-tertiary': 'var(--bg-tertiary)', + 'bg-elevated': 'var(--bg-elevated)', + 'primary-text': 'var(--text-primary)', + 'secondary-text': 'var(--text-secondary)', + 'muted': 'var(--text-muted)', 'charcoal-dark': '#0a0a0c', charcoal: { light: '#1d1d21', diff --git a/frontend/testing/unit/components/ThemeToggle.test.tsx b/frontend/testing/unit/components/ThemeToggle.test.tsx index e678b92d7..c8c2bb8b2 100644 --- a/frontend/testing/unit/components/ThemeToggle.test.tsx +++ b/frontend/testing/unit/components/ThemeToggle.test.tsx @@ -55,6 +55,34 @@ describe('ThemeToggle', () => { expect(button).toHaveAttribute('aria-pressed', 'true') }) + it('applies the dark class to document root and removes theme-light', async () => { + localStorage.setItem(STORAGE_KEY, 'light') + document.documentElement.classList.remove('dark') + document.documentElement.classList.add('theme-light') + const user = userEvent.setup() + renderWithTheme() + + const button = screen.getByRole('button') + await user.click(button) + + expect(document.documentElement.classList.contains('dark')).toBe(true) + expect(document.documentElement.classList.contains('theme-light')).toBe(false) + }) + + it('applies the theme-light class to document root and removes dark', async () => { + localStorage.setItem(STORAGE_KEY, 'dark') + document.documentElement.classList.add('dark') + document.documentElement.classList.remove('theme-light') + const user = userEvent.setup() + renderWithTheme() + + const button = screen.getByRole('button') + await user.click(button) + + expect(document.documentElement.classList.contains('theme-light')).toBe(true) + expect(document.documentElement.classList.contains('dark')).toBe(false) + }) + it('aria-label reflects the target theme, not the current one', () => { localStorage.setItem(STORAGE_KEY, 'dark') renderWithTheme()